package org.springframework.roo.addon.dbre.addon.model; import java.io.InputStream; import java.util.EmptyStackException; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.apache.commons.lang3.StringUtils; import org.springframework.roo.model.JavaPackage; import org.springframework.roo.support.util.XmlUtils; import org.w3c.dom.Comment; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * Assists converting a {@link Database} to and from XML using DOM or SAX. * * @author Alan Stewart * @author Juan Carlos GarcĂ­a * @since 1.1 */ public abstract class DatabaseXmlUtils { public static enum IndexType { INDEX, UNIQUE } public static final String DESCRIPTION = "description"; public static final String FOREIGN = "foreign"; public static final String FOREIGN_TABLE = "foreignTable"; public static final String LOCAL = "local"; public static final String NAME = "name"; public static final String ON_DELETE = "onDelete"; public static final String ON_UPDATE = "onUpdate"; public static final String REFERENCE = "reference"; /** * Adds an <option key="foo" value="true"> element as a child of the given * parent element * * @param document the XML document containing the parent and child * (required) * @param parent the parent element to which to add a child (required) * @param key the option key/name (required) * @param value the option value */ private static void addBooleanOptionElement(final Document document, final Element parent, final String key, final boolean value) { parent.appendChild(createOptionElement(key, String.valueOf(value), document)); } private static void addForeignKeyElements(final Set<ForeignKey> foreignKeys, final boolean exported, final Element tableElement, final Document document) { for (final ForeignKey foreignKey : foreignKeys) { final Element foreignKeyElement = document.createElement("foreign-key"); foreignKeyElement.setAttribute(NAME, foreignKey.getName()); foreignKeyElement.setAttribute(FOREIGN_TABLE, foreignKey.getForeignTableName()); foreignKeyElement.setAttribute(ON_DELETE, foreignKey.getOnDelete().getCode()); foreignKeyElement.setAttribute(ON_UPDATE, foreignKey.getOnUpdate().getCode()); final String foreignSchemaName = foreignKey.getForeignSchemaName(); if (!DbreModelService.NO_SCHEMA_REQUIRED.equals(foreignSchemaName)) { foreignKeyElement.appendChild(createOptionElement("foreignSchemaName", foreignSchemaName, document)); } foreignKeyElement.appendChild(createOptionElement("exported", String.valueOf(exported), document)); for (final Reference reference : foreignKey.getReferences()) { final Element referenceElement = document.createElement(REFERENCE); referenceElement.setAttribute(FOREIGN, reference.getForeignColumnName()); referenceElement.setAttribute(LOCAL, reference.getLocalColumnName()); foreignKeyElement.appendChild(referenceElement); } tableElement.appendChild(foreignKeyElement); } } private static void addIndices(final Table table, final Element tableElement, final IndexType indexType) { final List<Element> elements = XmlUtils.findElements(indexType.name().toLowerCase(), tableElement); for (final Element element : elements) { final Index index = new Index(element.getAttribute(NAME)); index.setUnique(indexType == IndexType.UNIQUE); final List<Element> indexColumnElements = XmlUtils.findElements(indexType.name().toLowerCase() + "-column", element); for (final Element indexColumnElement : indexColumnElements) { final IndexColumn indexColumn = new IndexColumn(indexColumnElement.getAttribute(NAME)); index.addColumn(indexColumn); } table.addIndex(index); } } private static Element createOptionElement(final String key, final String value, final Document document) { final Element option = document.createElement("option"); option.setAttribute("key", key); option.setAttribute("value", value); return option; } public static Document getDatabaseDocument(final Database database) { final Document document = XmlUtils.getDocumentBuilder().newDocument(); final Comment comment = document .createComment("WARNING: DO NOT EDIT THIS FILE. THIS FILE IS MANAGED BY SPRING ROO."); document.appendChild(comment); final Element databaseElement = document.createElement("database"); databaseElement.setAttribute(NAME, "deprecated"); if (database.getDestinationPackage() != null) { databaseElement.setAttribute("package", database.getDestinationPackage() .getFullyQualifiedPackageName()); } databaseElement.appendChild(createOptionElement("moduleName", database.getModuleName(), document)); addBooleanOptionElement(document, databaseElement, "repository", database.isRepository()); addBooleanOptionElement(document, databaseElement, "service", database.isService()); addBooleanOptionElement(document, databaseElement, "includeNonPortableAttributes", database.isIncludeNonPortableAttributes()); addBooleanOptionElement(document, databaseElement, "disableVersionFields", database.isDisableVersionFields()); addBooleanOptionElement(document, databaseElement, "disableGeneratedIdentifiers", database.isDisableGeneratedIdentifiers()); addBooleanOptionElement(document, databaseElement, "testAutomatically", database.isTestAutomatically()); for (final Table table : database.getTables()) { final Element tableElement = document.createElement("table"); tableElement.setAttribute(NAME, table.getName()); final String schemaName = table.getSchema().getName(); if (!DbreModelService.NO_SCHEMA_REQUIRED.equals(schemaName)) { tableElement.setAttribute("alias", schemaName); } if (StringUtils.isNotBlank(table.getDescription())) { tableElement.setAttribute(DESCRIPTION, table.getDescription()); } for (final Column column : table.getColumns()) { final Element columnElement = document.createElement("column"); columnElement.setAttribute(NAME, column.getName()); if (StringUtils.isNotBlank(column.getDescription())) { columnElement.setAttribute(DESCRIPTION, column.getDescription()); } columnElement.setAttribute("primaryKey", String.valueOf(column.isPrimaryKey())); columnElement.setAttribute("required", String.valueOf(column.isRequired())); columnElement.setAttribute("size", String.valueOf(column.getColumnSize())); columnElement.setAttribute("scale", String.valueOf(column.getScale())); columnElement.setAttribute("type", column.getDataType() + "," + column.getTypeName()); tableElement.appendChild(columnElement); } addForeignKeyElements(table.getImportedKeys(), false, tableElement, document); addForeignKeyElements(table.getExportedKeys(), true, tableElement, document); for (final Index index : table.getIndices()) { final Element indexElement = document.createElement(index.isUnique() ? IndexType.UNIQUE.name().toLowerCase() : IndexType.INDEX.name().toLowerCase()); indexElement.setAttribute(NAME, index.getName()); for (final IndexColumn indexColumn : index.getColumns()) { final Element indexColumnElement = document.createElement((index.isUnique() ? IndexType.UNIQUE.name().toLowerCase() : IndexType.INDEX.name().toLowerCase()) + "-column"); indexColumnElement.setAttribute(NAME, indexColumn.getName()); indexElement.appendChild(indexColumnElement); } tableElement.appendChild(indexElement); } databaseElement.appendChild(tableElement); } document.appendChild(databaseElement); // ROO-2355: transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, // "http://db.apache.org/torque/dtd/database_3_3.dtd"); return document; } static Database readDatabase(final InputStream inputStream) { try { final SAXParserFactory spf = SAXParserFactory.newInstance(); final SAXParser parser = spf.newSAXParser(); final DatabaseContentHandler contentHandler = new DatabaseContentHandler(); parser.parse(inputStream, contentHandler); return contentHandler.getDatabase(); } catch (final EmptyStackException e) { throw new IllegalStateException("Unable to read database from XML file", e); } catch (final Exception e) { if (e.getMessage().contains("Invalid byte")) { throw new IllegalStateException( "Invalid content in XML file. There may hidden characters in the file, such as the byte order mark (BOM). Try re-saving the xml in a text editor", e); } throw new IllegalStateException(e); } } static Database readDatabaseWithDom(final InputStream inputStream) { final Document document = XmlUtils.readXml(inputStream); final Element databaseElement = document.getDocumentElement(); final Set<Table> tables = new LinkedHashSet<Table>(); final List<Element> tableElements = XmlUtils.findElements("table", databaseElement); for (final Element tableElement : tableElements) { final String alias = tableElement.getAttribute("alias"); final String schemaName = StringUtils.defaultIfEmpty(alias, databaseElement.getAttribute(NAME)); final Table table = new Table(tableElement.getAttribute(NAME), new Schema(schemaName)); if (StringUtils.isNotBlank(tableElement.getAttribute(DESCRIPTION))) { table.setDescription(tableElement.getAttribute(DESCRIPTION)); } final List<Element> columnElements = XmlUtils.findElements("column", tableElement); for (final Element columnElement : columnElements) { final String type = columnElement.getAttribute("type"); final String[] dataTypeAndName = StringUtils.split(type, ","); final int dataType = Integer.parseInt(dataTypeAndName[0]); final String typeName = dataTypeAndName[1]; final int columnSize = Integer.parseInt(columnElement.getAttribute("size")); final int scale = Integer.parseInt(columnElement.getAttribute("scale")); final Column column = new Column(columnElement.getAttribute(NAME), dataType, typeName, columnSize, scale); column.setDescription(columnElement.getAttribute(DESCRIPTION)); column.setPrimaryKey(Boolean.parseBoolean(columnElement.getAttribute("primaryKey"))); column.setRequired(Boolean.parseBoolean(columnElement.getAttribute("required"))); table.addColumn(column); } final List<Element> foreignKeyElements = XmlUtils.findElements("foreign-key", tableElement); for (final Element foreignKeyElement : foreignKeyElements) { final ForeignKey foreignKey = new ForeignKey(foreignKeyElement.getAttribute(NAME), foreignKeyElement.getAttribute(FOREIGN_TABLE)); foreignKey.setOnDelete(CascadeAction.getCascadeAction(foreignKeyElement .getAttribute(ON_DELETE))); foreignKey.setOnUpdate(CascadeAction.getCascadeAction(foreignKeyElement .getAttribute(ON_UPDATE))); final List<Element> optionElements = XmlUtils.findElements("option", foreignKeyElement); for (final Element optionElement : optionElements) { final String key = optionElement.getAttribute("key"); final String value = optionElement.getAttribute("value"); if (key.equals("exported")) { foreignKey.setExported(Boolean.parseBoolean(value)); } if (key.equals("foreignSchemaName")) { foreignKey.setForeignSchemaName(value); } } final List<Element> referenceElements = XmlUtils.findElements(REFERENCE, foreignKeyElement); for (final Element referenceElement : referenceElements) { final Reference reference = new Reference(referenceElement.getAttribute(LOCAL), referenceElement.getAttribute(FOREIGN)); foreignKey.addReference(reference); } table.addImportedKey(foreignKey); } addIndices(table, tableElement, IndexType.INDEX); addIndices(table, tableElement, IndexType.UNIQUE); tables.add(table); } JavaPackage destinationPackage = null; if (StringUtils.isNotBlank(databaseElement.getAttribute("package"))) { destinationPackage = new JavaPackage(databaseElement.getAttribute("package")); } final Database database = new Database(tables); database.setDestinationPackage(destinationPackage); final List<Element> optionElements = XmlUtils.findElements("option", databaseElement); for (final Element optionElement : optionElements) { final String key = optionElement.getAttribute("key"); final String value = optionElement.getAttribute("value"); if (key.equals("moduleName")) { database.setModuleName(value); } if (key.equals("repository")) { database.setRepository(Boolean.parseBoolean(value)); } if (key.equals("service")) { database.setService(Boolean.parseBoolean(value)); } if (key.equals("testAutomatically")) { database.setTestAutomatically(Boolean.parseBoolean(value)); } if (key.equals("includeNonPortableAttributes")) { database.setIncludeNonPortableAttributes(Boolean.parseBoolean(value)); } if (key.equals("disableVersionFields")) { database.setDisableVersionFields(Boolean.parseBoolean(value)); } if (key.equals("disableGeneratedIdentifiers")) { database.setDisableGeneratedIdentifiers(Boolean.parseBoolean(value)); } } return database; } }