Java tutorial
/** * ShapeChange - processing application schemas for geographic information * * This file is part of ShapeChange. ShapeChange takes a ISO 19109 * Application Schema from a UML model and translates it into a * GML Application Schema or other implementation representations. * * Additional information about the software can be found at * http://shapechange.net/ * * (c) 2002-2015 interactive instruments GmbH, Bonn, Germany * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * Contact: * interactive instruments GmbH * Trierer Strasse 70-72 * 53115 Bonn * Germany */ package de.interactive_instruments.ShapeChange.Target.SQL; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import de.interactive_instruments.ShapeChange.MapEntryParamInfos; import de.interactive_instruments.ShapeChange.MessageSource; import de.interactive_instruments.ShapeChange.Options; import de.interactive_instruments.ShapeChange.ProcessMapEntry; import de.interactive_instruments.ShapeChange.ShapeChangeAbortException; import de.interactive_instruments.ShapeChange.ShapeChangeResult; import de.interactive_instruments.ShapeChange.ShapeChangeResult.MessageContext; import de.interactive_instruments.ShapeChange.StructuredNumber; import de.interactive_instruments.ShapeChange.TargetIdentification; import de.interactive_instruments.ShapeChange.Model.AssociationInfo; import de.interactive_instruments.ShapeChange.Model.ClassInfo; import de.interactive_instruments.ShapeChange.Model.Model; import de.interactive_instruments.ShapeChange.Model.PackageInfo; import de.interactive_instruments.ShapeChange.Model.PropertyInfo; import de.interactive_instruments.ShapeChange.Target.Target; /** * Creates SQL DDL for an application schema. * * @author Johannes Echterhoff (echterhoff <at> interactive-instruments * <dot> de) * */ public class SqlDdl implements Target, MessageSource { public static final String PLATFORM = "sql"; /* ------------------------------------------- */ /* --- configuration parameter identifiers --- */ /* ------------------------------------------- */ /** * Name for the identifier column when generating table creation statements. * This parameter is optional. The default is * {@value #DEFAULT_ID_COLUMN_NAME}. */ public static final String PARAM_ID_COLUMN_NAME = "idColumnName"; /** * Suffix to append to the name of columns that contain foreign keys (except * if it references a table that represents a data type). This parameter is * optional. The default is the empty string. */ public static final String PARAM_FOREIGN_KEY_COLUMN_SUFFIX = "foreignKeyColumnSuffix"; /** * Suffix to append to the name of columns that contain foreign keys * referencing tables that represent data types. This parameter is optional. * The default is the empty string. */ public static final String PARAM_FOREIGN_KEY_COLUMN_SUFFIX_DATATYPE = "foreignKeyColumnSuffixDatatype"; /** * Datatype to use for foreign key fields, for example 'bigint' in case of a PostgreSQL database. * The default is the primary key type defined by the database strategy. */ public static final String PARAM_FOREIGN_KEY_COLUMN_DATA_TYPE = "foreignKeyColumnDataType"; /** * Size for fields representing textual properties with limited length, to * be used in case that the property represented by the field does not have * a 'size' tagged value; default is {@value #DEFAULT_SIZE} */ public static final String PARAM_SIZE = "size"; /** * EPSG code of the spatial reference system to use for geometries; default * is {@value #DEFAULT_SRID} */ public static final String PARAM_SRID = "srid"; /** * Flag to indicate that foreign key creation is desired (true); default is * false. */ public static final String PARAM_CREATE_REFERENCES = "createReferences"; /** * Flag to indicate that model documentation shall be added to the DDL via * comments; default is true. */ public static final String PARAM_CREATE_DOCUMENTATION = "createDocumentation"; /** * Identifier of the database system for which SQL DDL shall be created. * Supported systems - and also relevant identifiers - are: * <ul> * <li>PostgreSQL</li> * <li>Oracle</li> * </ul> * The default is PostgreSQL. */ public static final String PARAM_DATABASE_SYSTEM = "databaseSystem"; /** * Optional changes to the default documentation template and the default strings for descriptors without value */ public static final String PARAM_DOCUMENTATION_TEMPLATE = "documentationTemplate"; public static final String PARAM_DOCUMENTATION_NOVALUE = "documentationNoValue"; /* ------------------------ */ /* --- rule identifiers --- */ /* ------------------------ */ /** * Ensures that table creation statements are generated for feature types. */ public static final String RULE_TGT_SQL_CLS_FEATURE_TYPES = "rule-sql-cls-feature-types"; /** * Ensures that table creation statements are generated for object types. */ public static final String RULE_TGT_SQL_CLS_OBJECT_TYPES = "rule-sql-cls-object-types"; /** * Ensures that CHECK constraints are created for fields representing * enumeration values. */ public static final String RULE_TGT_SQL_PROP_CHECK_CONSTRAINTS_FOR_ENUMERATIONS = "rule-sql-prop-check-constraints-for-enumerations"; /** * Ensures that table creation statements are generated for complex data * types. */ public static final String RULE_TGT_SQL_CLS_DATATYPES = "rule-sql-cls-data-types"; /** * If this rule is enabled, then a property whose type is neither covered by * a type mapping entry nor contained in the currently processed schema - * but in the overall model - is still encoded as a field with a foreign key * - if other rules allow table creation for this type. Otherwise the field * is encoded using a textual data type. */ public static final String RULE_TGT_SQL_CLS_REFERENCES_TO_EXTERNAL_TYPES = "rule-sql-cls-references-to-external-types"; /** * This rule ensures that associative tables are created for cases in which * an n:m relationship exists between types. * * The name of the associative table is taken from the tagged value * {@value #TV_ASSOCIATIVETABLE} - which exists either on an association or * an attribute. If the tagged value is not present or empty, the name is * created as follows: * <ul> * <li>If the table represents an n:m relationship represented by an * association, then: * <ul> * <li>for a bi-directional association: the name of the class (from both * ends of the association) that is lower in alphabetical order is used, * concatenated with the according property name</li> * <li>for a uni-directional association: the name of the inClass of the * navigable property is used, concatenated with the property name</li> * </ul> * </li> * <li>If the table represents an n:m relationship that is caused by an * attribute with max multiplicity greater than one, then the name of the * class that the attribute belongs to is used, concatenated with the * property name</li> * </ul> */ public static final String RULE_TGT_SQL_ALL_ASSOCIATIVETABLES = "rule-sql-all-associativetables"; /** * If this rule is enabled derived properties will be ignored. */ public static final String RULE_TGT_SQL_PROP_EXCLUDE_DERIVED = "rule-sql-prop-exclude-derived"; /* --------------------- */ /* --- Tagged Values --- */ /* --------------------- */ public static final String TV_ASSOCIATIVETABLE = "associativeTable"; /* -------------------- */ /* --- other fields --- */ /* -------------------- */ public static final String DEFAULT_ID_COLUMN_NAME = "_id"; public static final String DEFAULT_FOREIGN_KEY_COLUMN_SUFFIX = ""; public static final String DEFAULT_FOREIGN_KEY_COLUMN_SUFFIX_DATATYPE = ""; public static final int DEFAULT_SIZE = 1024; public static final int DEFAULT_SRID = 4326; /** * Name of the parameter to indicate (via the 'param' attribute) that a map * entry contains information about a geometry type. */ public static final String ME_PARAM_GEOMETRY = "geometry"; /** * Name of the parameter to indicate (via the 'param' attribute) that the * type of a map entry is represented by a table. */ public static final String ME_PARAM_TABLE = "table"; /** * A characteristic for the parameter {@value #ME_PARAM_TABLE} that gives * information about the category of the conceptual type that is identified * by the map entry. * * Recognized values are (currently there is only one): * <ul> * <li>datatype</li> * </ul> */ public static final String ME_PARAM_TABLE_CHARACT_REP_CAT = "representedCategory"; /** * Regular expression (?i:datatype) to check that a given string is one of a * list of allowed values (NOTE1: check is case-insensitive; NOTE2: at the * moment there is only one valid value). */ public static final String ME_PARAM_TABLE_CHARACT_REP_CAT_VALIDATION_REGEX = "(?i:datatype)"; public static final String MAP_TARGETTYPE_COND_PART = "cond:"; public static final String MAP_TARGETTYPE_COND_TEXTORCHARACTERVARYING = "textOrCharacterVarying"; public static final String CRLF = Options.CRLF; public static final String IDT = "\t"; protected TreeMap<String, ProcessMapEntry> mapEntryByType = new TreeMap<String, ProcessMapEntry>(); private ShapeChangeResult result = null; private PackageInfo schema = null; private Model model = null; private Options options = null; private boolean printed = false; private boolean diagnosticsOnly = false; private String documentationTemplate = null; private String documentationNoValue = null; private String outputDirectory; private String idColumnName; private String foreignKeyColumnSuffix; private String foreignKeyColumnSuffixDatatype; private String foreignKeyColumnDataType; private int defaultSize; private int srid; private boolean createReferences = false; private boolean createDocumentation = true; private boolean createAssociativeTables = false; /** * This map contains the general table definitions for each class. * * key: class name (not normalized) * * value: table creation statement according to the class (normalized * property names) */ private TreeMap<String, String> tablesByClassName = new TreeMap<String, String>(); /** * This map contains the definitions for all associative tables. * * key: name of associative table (not normalized) * * value: table creation statement */ private TreeMap<String, String> tablesByAssociativeTableName = new TreeMap<String, String>(); /** * This map is used to cache information for building geometry indexes * * key: table name (not normalized) * * value: geometry properties belonging to the table */ private TreeMap<String, List<PropertyInfo>> geometryPropsByTableName = new TreeMap<String, List<PropertyInfo>>(); /** * This map is used to cache information for altering tables to add * reference columns * * key: table name (not normalized) * * value: ALTER TABLE statements with full column definition of the * reference */ private TreeMap<String, List<String>> referenceColumnDefinitionsByTableName = new TreeMap<String, List<String>>(); private TreeMap<String, List<String>> checkConstraintsForPropsWithEnumValueTypeByTableName = new TreeMap<String, List<String>>(); private List<String> geometryMetadataUpdateStatements; private List<String> geometryIndexCreationStatements; /** * Contains information parsed from the 'param' attributes of each map entry * defined for this target. */ private MapEntryParamInfos mepp = null; private TreeMap<String, String> alterTableStatementsByClassName = new TreeMap<String, String>(); /** * Used to keep track of the associations for which an associative table * creation statement has already been generated. * <p> * Does not have an influence on the order in which statements are written. */ private Set<AssociationInfo> associationsWithAssociativeTable = new HashSet<AssociationInfo>(); private DatabaseStrategy databaseStrategy; /** * @see de.interactive_instruments.ShapeChange.Target.Target#initialise(de.interactive_instruments.ShapeChange.Model.PackageInfo, * de.interactive_instruments.ShapeChange.Model.Model, * de.interactive_instruments.ShapeChange.Options, * de.interactive_instruments.ShapeChange.ShapeChangeResult, boolean) */ public void initialise(PackageInfo pi, Model m, Options o, ShapeChangeResult r, boolean diagOnly) throws ShapeChangeAbortException { schema = pi; model = m; options = o; result = r; diagnosticsOnly = diagOnly; result.addDebug(this, 1, pi.name()); outputDirectory = options.parameter(this.getClass().getName(), "outputDirectory"); if (outputDirectory == null) outputDirectory = options.parameter("outputDirectory"); if (outputDirectory == null) outputDirectory = options.parameter("."); // create output directory, if necessary if (!this.diagnosticsOnly) { // Check whether we can use the given output directory File outputDirectoryFile = new File(outputDirectory); boolean exi = outputDirectoryFile.exists(); if (!exi) { outputDirectoryFile.mkdirs(); exi = outputDirectoryFile.exists(); } boolean dir = outputDirectoryFile.isDirectory(); boolean wrt = outputDirectoryFile.canWrite(); boolean rea = outputDirectoryFile.canRead(); if (!exi || !dir || !wrt || !rea) { result.addFatalError(this, 3, outputDirectory); return; } } if (pi.matches(RULE_TGT_SQL_ALL_ASSOCIATIVETABLES)) { this.createAssociativeTables = true; } String databaseSystem = options.parameter(this.getClass().getName(), PARAM_DATABASE_SYSTEM); if (databaseSystem == null || "postgresql".equalsIgnoreCase(databaseSystem)) { databaseStrategy = new PostgreSQLStrategy(); } else if ("oracle".equalsIgnoreCase(databaseSystem)) { databaseStrategy = new OracleStrategy(result); } else { databaseStrategy = new NullDatabaseStrategy(); result.addFatalError(this, 6, databaseSystem); } idColumnName = options.parameter(this.getClass().getName(), PARAM_ID_COLUMN_NAME); if (idColumnName == null) { idColumnName = DEFAULT_ID_COLUMN_NAME; } foreignKeyColumnSuffix = options.parameter(this.getClass().getName(), PARAM_FOREIGN_KEY_COLUMN_SUFFIX); if (foreignKeyColumnSuffix == null) { foreignKeyColumnSuffix = DEFAULT_FOREIGN_KEY_COLUMN_SUFFIX; } foreignKeyColumnSuffixDatatype = options.parameter(this.getClass().getName(), PARAM_FOREIGN_KEY_COLUMN_SUFFIX_DATATYPE); if (foreignKeyColumnSuffixDatatype == null) { foreignKeyColumnSuffixDatatype = DEFAULT_FOREIGN_KEY_COLUMN_SUFFIX_DATATYPE; } foreignKeyColumnDataType = options.parameter(this.getClass().getName(), PARAM_FOREIGN_KEY_COLUMN_DATA_TYPE); if (foreignKeyColumnDataType == null) { foreignKeyColumnDataType = databaseStrategy.primaryKeyDataType(); } String defaultSizeByConfig = options.parameter(this.getClass().getName(), PARAM_SIZE); if (defaultSizeByConfig == null) { defaultSize = DEFAULT_SIZE; } else { try { defaultSize = Integer.parseInt(defaultSizeByConfig); } catch (NumberFormatException e) { MessageContext mc = result.addWarning(this, 4, PARAM_SIZE, e.getMessage(), "" + DEFAULT_SIZE); mc.addDetail(this, 0); defaultSize = DEFAULT_SIZE; } } String defaultSridByConfig = options.parameter(this.getClass().getName(), PARAM_SRID); if (defaultSridByConfig == null) { srid = DEFAULT_SRID; } else { try { srid = Integer.parseInt(defaultSridByConfig); } catch (NumberFormatException e) { MessageContext mc = result.addWarning(this, 4, PARAM_SRID, e.getMessage(), "" + DEFAULT_SRID); mc.addDetail(this, 0); srid = DEFAULT_SRID; } } String createReferencesByConfig = options.parameter(this.getClass().getName(), PARAM_CREATE_REFERENCES); if (createReferencesByConfig != null) { createReferences = Boolean.parseBoolean(createReferencesByConfig.trim()); } String createDocumentationByConfig = options.parameter(this.getClass().getName(), PARAM_CREATE_DOCUMENTATION); if (createDocumentationByConfig != null) { createDocumentation = Boolean.parseBoolean(createDocumentationByConfig.trim()); } // change the default documentation template? documentationTemplate = options.parameter(this.getClass().getName(), PARAM_DOCUMENTATION_TEMPLATE); documentationNoValue = options.parameter(this.getClass().getName(), PARAM_DOCUMENTATION_NOVALUE); // reset processed flags on all classes in the schema for (Iterator<ClassInfo> k = model.classes(pi).iterator(); k.hasNext();) { ClassInfo ci = k.next(); ci.processed(getTargetID(), false); } // identify map entries defined in the target configuration List<ProcessMapEntry> mapEntries = options.getCurrentProcessConfig().getMapEntries(); if (mapEntries == null || mapEntries.isEmpty()) { /* * It is unlikely but not impossible that an application schema does * not make use of types that require a type mapping in order to be * converted into a database schema. */ result.addWarning(this, 15); } else { /* * Store map entries in map with key being the 'type'. */ for (ProcessMapEntry pme : mapEntries) { this.mapEntryByType.put(pme.getType(), pme); } /* * Parse all parameter information */ mepp = new MapEntryParamInfos(result, mapEntries); validateMapEntryParamInfos(mepp); } } private void validateMapEntryParamInfos(MapEntryParamInfos mepp2) { // first up validation of common parameters if (mapEntryByType != null) { for (String type : mapEntryByType.keySet()) { if (mepp.hasCharacteristic(type, ME_PARAM_TABLE, ME_PARAM_TABLE_CHARACT_REP_CAT)) { String representedCategory = mepp.getCharacteristic(type, ME_PARAM_TABLE, ME_PARAM_TABLE_CHARACT_REP_CAT); if (representedCategory == null) { result.addError(this, 19, type, ME_PARAM_TABLE_CHARACT_REP_CAT, ME_PARAM_TABLE); } else if (!representedCategory.matches(ME_PARAM_TABLE_CHARACT_REP_CAT_VALIDATION_REGEX)) { result.addError(this, 20, type, ME_PARAM_TABLE_CHARACT_REP_CAT, ME_PARAM_TABLE, ME_PARAM_TABLE_CHARACT_REP_CAT_VALIDATION_REGEX); } } } } // now the database specific validation this.databaseStrategy.validate(mapEntryByType, mepp); } public void process(ClassInfo ci) { if (ci == null || ci.pkg() == null) return; if (ci.processed(getTargetID())) return; result.addDebug(this, 2, ci.name()); // Create table creation statements if ((ci.category() == Options.OBJECT && ci.matches(RULE_TGT_SQL_CLS_OBJECT_TYPES)) || (ci.category() == Options.FEATURE && ci.matches(RULE_TGT_SQL_CLS_FEATURE_TYPES)) || (ci.category() == Options.DATATYPE && ci.matches(RULE_TGT_SQL_CLS_DATATYPES))) { generateTableCreationAndAlterStatements(ci); if (createReferences) { generateForeignKeyDefinitions(ci); } } else if (ci.category() == Options.ENUMERATION) { // fine - tables won't be created for enumerations } else { result.addInfo(this, 17, ci.name()); } ci.processed(getTargetID(), true); } /** * Creates ALTER TABLE commands for any class property that refers to * another class and shall thus receive a foreign key constraint. * * @param ci */ private void generateForeignKeyDefinitions(ClassInfo ci) { for (PropertyInfo pi : ci.properties().values()) { if (refersToTypeRepresentedByTable(pi)) { if (pi.cardinality().maxOccurs == 1) { createForeignKeyDefinition(ci.name(), pi); // } else if (!pi.isAttribute() // && pi.reverseProperty().cardinality().maxOccurs == 1) { // // createForeignKeyDefinition( // pi.reverseProperty().inClass().name(), // pi.reverseProperty()); } else { if (createAssociativeTables) { /* * fine - foreign key constraints have been created * while creating associative tables */ } else { /* * no need to warn again here, because a warning has * already been issued during table creation */ // String rel1Class = pi.inClass().name(); // String rel2Class = pi.typeInfo().name; // // result.addWarning(this, 8, rel1Class, rel2Class, // pi.name()); } } } } } public void createForeignKeyDefinition(String className, PropertyInfo pi) { String res = "ALTER TABLE " + normalizeName(className) + " ADD CONSTRAINT " + getForeignKeyIdentifier(pi) + " FOREIGN KEY (" + normalizeName(pi.name() + identifyForeignKeyColumnSuffix(pi)) + ") REFERENCES " + determineTableNameForValueType(pi) + ";" + CRLF; if (!this.referenceColumnDefinitionsByTableName.containsKey(className)) { this.referenceColumnDefinitionsByTableName.put(className, new ArrayList<String>()); } this.referenceColumnDefinitionsByTableName.get(className).add(res); } private String identifyForeignKeyColumnSuffix(PropertyInfo pi) { String typeName = pi.typeInfo().name; ProcessMapEntry pme = this.mapEntryByType.get(typeName); if (pme != null && mepp.hasCharacteristic(typeName, ME_PARAM_TABLE, ME_PARAM_TABLE_CHARACT_REP_CAT)) { String repCat = mepp.getCharacteristic(typeName, ME_PARAM_TABLE, ME_PARAM_TABLE_CHARACT_REP_CAT); if (repCat != null && repCat.equalsIgnoreCase("datatype")) { return foreignKeyColumnSuffixDatatype; } else { return foreignKeyColumnSuffix; } } else if (pi.categoryOfValue() == Options.DATATYPE) { return foreignKeyColumnSuffixDatatype; } else { return foreignKeyColumnSuffix; } } /** * @param tableName * (does not need to be normalized) * @param fieldName * (does not need to be normalized) * @param targetTableName * (does not need to be normalized) */ public void createForeignKeyDefinition(String tableName, String fieldName, String targetTableName) { String res = "ALTER TABLE " + normalizeName(tableName) + " ADD CONSTRAINT " + getForeignKeyIdentifier(tableName, fieldName) + " FOREIGN KEY (" + normalizeName(fieldName) + ") REFERENCES " + normalizeName(targetTableName) + ";" + CRLF; if (!this.referenceColumnDefinitionsByTableName.containsKey(tableName)) { this.referenceColumnDefinitionsByTableName.put(tableName, new ArrayList<String>()); } this.referenceColumnDefinitionsByTableName.get(tableName).add(res); } private String getForeignKeyIdentifier(PropertyInfo pi) { return getForeignKeyIdentifier(pi.inClass().name(), pi.name()); } /** * @param tableName * @param fieldName * @return the normalized identifier for the foreign key */ private String getForeignKeyIdentifier(String tableName, String fieldName) { /* * The following is most often too long, thus we simply use the table * and field name */ // String res = "fk_" + className + "_" + fieldName + "_to_" // + targetClassName; String res = "fk_" + tableName + "_" + fieldName; return normalizeName(res); } private void generateTableCreationAndAlterStatements(ClassInfo ci) { /* * The order of the properties is defined by their sequence numbers * (which is automatically provided by a TreeMap). */ SortedMap<StructuredNumber, PropertyInfo> pis = ci.properties(); List<PropertyInfo> propertyInfosForColumns = new ArrayList<PropertyInfo>(); for (PropertyInfo pi : pis.values()) { /* * If the value type of the property is part of the schema but not * enabled for conversion, issue a warning and continue. */ // try getting the type class by ID first, then by name ClassInfo typeCi = model.classById(pi.typeInfo().id); if (typeCi == null) { typeCi = model.classByName(pi.typeInfo().name); } if (typeCi != null && typeCi.inSchema(schema) && ((typeCi.category() == Options.OBJECT && !typeCi.matches(RULE_TGT_SQL_CLS_OBJECT_TYPES)) || (typeCi.category() == Options.FEATURE && !typeCi.matches(RULE_TGT_SQL_CLS_FEATURE_TYPES)) || (typeCi.category() == Options.DATATYPE && !typeCi.matches(RULE_TGT_SQL_CLS_DATATYPES)))) { result.addWarning(this, 16, pi.name(), pi.inClass().name(), pi.typeInfo().name); continue; } if (pi.isDerived() && pi.matches(RULE_TGT_SQL_PROP_EXCLUDE_DERIVED)) { result.addInfo(this, 14, pi.name(), ci.name()); continue; } if (pi.isAttribute()) { if (pi.cardinality().maxOccurs == 1) { propertyInfosForColumns.add(pi); } else if (createAssociativeTables) { createAssociativeTableForAttribute(pi); } else { /* * Warn that attribute with max multiplicity > 1 is not * supported when creation of associative tables is not * enabled. */ result.addWarning(this, 11, pi.name(), pi.inClass().name()); } } else { // the property is an association role AssociationInfo ai = pi.association(); /* * if an associative table has already been created for this * association, continue */ if (associationsWithAssociativeTable.contains(ai)) { continue; } PropertyInfo revPi = pi.reverseProperty(); int maxOccursPi = pi.cardinality().maxOccurs; int maxOccursRevPi = revPi.cardinality().maxOccurs; /* * note: pi is navigable, otherwise it wouldn't occur as * property of ci */ if (maxOccursPi == 1) { propertyInfosForColumns.add(pi); } else { if (revPi.isNavigable() && maxOccursRevPi == 1) { /* * MaxOccurs = 1 on both ends -> the relationship will * be represented by the foreign key field that * represents the reverse property in its inClass. */ } else { /* * The reverse property is not navigable or both * association roles have a maximum multiplicity greater * than 1 - both situations represent an n:m * relationship */ if (createAssociativeTables) { if (associationsWithAssociativeTable.contains(ai)) { /* * An associative table has already been created * to represent this relationship. */ } else { createAssociativeTable(ai); associationsWithAssociativeTable.add(ai); } } else { PropertyInfo pi1, pi2; if (pi.inClass().name().compareTo(pi.reverseProperty().inClass().name()) <= 0) { pi1 = pi; pi2 = pi.reverseProperty(); } else { pi1 = pi.reverseProperty(); pi2 = pi; } result.addWarning(this, 8, pi1.inClass().name(), pi1.name(), pi2.inClass().name(), pi2.name()); } } } } } // if (propertyInfosForColumns.size() > 0) { generateTableCreationStatement(ci, propertyInfosForColumns); // } } /** * @param ai */ private void createAssociativeTable(AssociationInfo ai) { StringBuffer sb = new StringBuffer(); // identify table name - using tagged value or default name String tableName = ai.taggedValuesAll().getFirstValue(TV_ASSOCIATIVETABLE); String tableNameEnd1InClass = determineTableNameForType(ai.end1().inClass()); String tableNameEnd2InClass = determineTableNameForType(ai.end2().inClass()); if (tableName == null || tableName.trim().length() == 0) { if (ai.end1().isNavigable() && ai.end2().isNavigable()) { // choose name based on alphabetical order if (tableNameEnd1InClass.compareTo(tableNameEnd2InClass) <= 0) { tableName = tableNameEnd1InClass + "_" + ai.end1().name(); } else { tableName = tableNameEnd2InClass + "_" + ai.end2().name(); } } else if (ai.end1().isNavigable()) { tableName = tableNameEnd1InClass + "_" + ai.end1().name(); } else { // ai.end2 is navigable tableName = tableNameEnd2InClass + "_" + ai.end2().name(); } result.addInfo(this, 13, ai.end1().inClass().name() + " (context property '" + ai.end1().name() + "')", ai.end2().inClass().name() + " (context property '" + ai.end2().name() + "')", tableName); } // add table creation statement addCRLF(sb, "CREATE TABLE " + normalizeName(tableName) + " ("); newLine(sb); /* * ensure that reference fields are created in lexicographical order of * their inClass names */ PropertyInfo pi1, pi2; if (tableNameEnd1InClass.compareTo(tableNameEnd2InClass) <= 0) { pi1 = ai.end1(); pi2 = ai.end2(); } else { pi1 = ai.end2(); pi2 = ai.end1(); } // add field for first reference String name_1 = determineTableNameForType(pi1.inClass()) + idColumnName; String name_1_norm = normalizeName(name_1); indent(sb, name_1_norm + " " + foreignKeyColumnDataType + " NOT NULL,"); // add field for second reference String name_2 = determineTableNameForType(pi2.inClass()) + idColumnName; String name_2_norm = normalizeName(name_2); indent(sb, name_2_norm + " " + foreignKeyColumnDataType + " NOT NULL,"); // add primary key definition indent(sb, "PRIMARY KEY (" + name_1_norm + "," + name_2_norm + ")"); addCRLF(sb, ");"); newLine(sb); // now store the table creation statement for later use this.tablesByAssociativeTableName.put(tableName, sb.toString()); // add foreign key constraint(s) if (createReferences) { createForeignKeyDefinition(tableName, name_1, determineTableNameForType(pi1.inClass())); createForeignKeyDefinition(tableName, name_2, determineTableNameForType(pi2.inClass())); } } /** * @param ci * @return If a map entry with param = {@value #ME_PARAM_TABLE} is defined * for the given class, the targetType defined by the map entry is * returned. Otherwise the name of the class is returned. */ private String determineTableNameForType(ClassInfo ci) { ProcessMapEntry pme = this.mapEntryByType.get(ci.name()); if (pme != null && mepp.hasParameter(ci.name(), ME_PARAM_TABLE)) { return pme.getTargetType(); } else { return ci.name(); } } /** * NOTE: only works for attributes, NOT association roles * * @param pi */ private void createAssociativeTableForAttribute(PropertyInfo pi) { if (!pi.isAttribute()) { return; } StringBuffer sb = new StringBuffer(); // identify table name - using tagged value or default name String tableName = pi.taggedValuesAll().getFirstValue(TV_ASSOCIATIVETABLE); if (tableName == null || tableName.trim().length() == 0) { tableName = pi.inClass().name() + "_" + pi.name(); result.addInfo(this, 12, pi.name(), pi.inClass().name(), tableName); } // add table creation statement addCRLF(sb, "CREATE TABLE " + normalizeName(tableName) + " ("); newLine(sb); // add field to reference pi.inClass String classReferenceFieldName = pi.inClass().name() + idColumnName; String normalizedClassReferenceFieldName = normalizeName(classReferenceFieldName); indent(sb, normalizedClassReferenceFieldName + " " + foreignKeyColumnDataType + " NOT NULL,"); String normalizedPiFieldName; boolean createForeignKeyConstraintForPiField = false; if (refersToTypeRepresentedByTable(pi)) { normalizedPiFieldName = normalizeName(determineTableNameForValueType(pi) + idColumnName); createForeignKeyConstraintForPiField = true; indent(sb, normalizedPiFieldName + " " + foreignKeyColumnDataType + " NOT NULL,"); } else { normalizedPiFieldName = normalizeName(pi.name()); indent(sb, generateColumnDefinition(pi, true, true)); if (pi.categoryOfValue() == Options.ENUMERATION && pi.matches(RULE_TGT_SQL_PROP_CHECK_CONSTRAINTS_FOR_ENUMERATIONS)) { generateCheckConstraintForEnumerationValueType(tableName, pi); } if (isGeometryTypedProperty(pi)) { notePropertyForGeometryIndexCreation(pi, tableName); } } // add primary key definition indent(sb, "PRIMARY KEY (" + normalizedClassReferenceFieldName + "," + normalizedPiFieldName + ")"); addCRLF(sb, ");"); newLine(sb); // now store the table creation statement for later use this.tablesByAssociativeTableName.put(tableName, sb.toString()); // add foreign key constraint(s) if (createReferences) { createForeignKeyDefinition(tableName, classReferenceFieldName, pi.inClass().name()); if (createForeignKeyConstraintForPiField) { createForeignKeyDefinition(tableName, normalizedPiFieldName, determineTableNameForValueType(pi)); } } } private void notePropertyForGeometryIndexCreation(PropertyInfo pi, String tableName) { if (!geometryPropsByTableName.containsKey(tableName)) { // initialise the list for storage of geometry columns // for the table geometryPropsByTableName.put(tableName, new ArrayList<PropertyInfo>()); } // keep track of geometry column for index creation later on geometryPropsByTableName.get(tableName).add(pi); } /** * @param tableName * - does not need to be normalized * @param pi */ private void generateCheckConstraintForEnumerationValueType(String tableName, PropertyInfo pi) { /* * ignore the constraint if the enumeration which is the value type of * pi is mapped to a simple type (example: usage of the 'Boolean' type * from ISO 19103). */ ProcessMapEntry pme = this.mapEntryByType.get(pi.typeInfo().name); if (pme != null && !pme.hasParam()) { return; } // look up the enumeration type ClassInfo enumCi = model.classById(pi.typeInfo().id); if (enumCi == null || enumCi.properties().size() == 0) { result.addError(this, 18, pi.typeInfo().name, pi.fullNameInSchema()); } else { StringBuffer sb2 = new StringBuffer(); sb2.append("ALTER TABLE " + normalizeName(tableName) + " ADD CONSTRAINT " + createNameCheckConstraint(tableName, pi.name()) + " CHECK (" + normalizeName(pi.name()) + " IN ("); for (PropertyInfo enumPi : enumCi.properties().values()) { sb2.append("'" + StringUtils.replace(enumPi.name(), "'", "''") + "', "); // escape single quotes in the enumeration value } sb2.delete(sb2.length() - 2, sb2.length()); sb2.append("));" + CRLF); if (!this.checkConstraintsForPropsWithEnumValueTypeByTableName.containsKey(tableName)) { this.checkConstraintsForPropsWithEnumValueTypeByTableName.put(tableName, new ArrayList<String>()); } this.checkConstraintsForPropsWithEnumValueTypeByTableName.get(tableName).add(sb2.toString()); } } /** * @param pi * @return If a map entry with param = {@value #ME_PARAM_TABLE} is defined * for the value type of the property, the targetType defined by the * map entry is returned. Otherwise the name of the value type is * returned. */ private String determineTableNameForValueType(PropertyInfo pi) { String valueTypeName = pi.typeInfo().name; ProcessMapEntry pme = this.mapEntryByType.get(valueTypeName); if (pme != null && mepp.hasParameter(valueTypeName, ME_PARAM_TABLE)) { return pme.getTargetType(); } else { return pi.typeInfo().name; } } public void generateTableCreationStatement(ClassInfo ci, List<PropertyInfo> propertyInfosForColumns) { StringBuffer sb = new StringBuffer(); addCRLF(sb, "CREATE TABLE " + normalizeName(ci.name()) + " ("); newLine(sb); // create the column to store the object identifier String oid_field_stmt = idColumnName + " " + databaseStrategy.primaryKeyDataType() + " NOT NULL PRIMARY KEY"; if (!propertyInfosForColumns.isEmpty()) { indent(sb, oid_field_stmt + ","); } else { indent(sb, oid_field_stmt); } for (int i = 0; i < propertyInfosForColumns.size(); i++) { PropertyInfo pi = propertyInfosForColumns.get(i); /* * generate column definitions with comma until we've reached the * last column */ if (i < propertyInfosForColumns.size() - 1) { indent(sb, generateColumnDefinition(pi, true)); } else { indent(sb, generateColumnDefinition(pi, false)); } if (pi.categoryOfValue() == Options.ENUMERATION && pi.matches(RULE_TGT_SQL_PROP_CHECK_CONSTRAINTS_FOR_ENUMERATIONS)) { generateCheckConstraintForEnumerationValueType(ci.name(), pi); } if (isGeometryTypedProperty(pi)) { notePropertyForGeometryIndexCreation(pi, ci.name()); } } addCRLF(sb, ");"); newLine(sb); // now store the table creation statement for later use this.tablesByClassName.put(ci.name(), sb.toString()); } public void createAlterTableStatementForReverseProperty(PropertyInfo pi) { String reversePropertyName = pi.reverseProperty().name(); if (reversePropertyName.matches("role_(S|T)\\d*")) { result.addWarning(this, 7, reversePropertyName, pi.reverseProperty().inClass().name(), pi.inClass().name()); } StringBuffer sb2 = new StringBuffer(); sb2.append("ALTER TABLE " + normalizeName(determineTableNameForType(pi.reverseProperty().inClass()))); sb2.append(" ADD " + generateColumnDefinition(pi.reverseProperty(), false) + ";" + CRLF); alterTableStatementsByClassName.put(pi.reverseProperty().inClass().name(), sb2.toString()); } /** * Creates the column definition based upon the property name, its type, and * a possibly defined initial value. Also adds "NOT NULL" for all properties * that can be nil/null (set via tagged value or stereotype) or which are * optional. * * @param pi * @param addComma * true if a ',' shall be added at the end of the statement * (before a potential comment), else false (specifically used * for the last column in a table creation statement). * @return The SQL statement with the definition of the column to represent * the property */ private String generateColumnDefinition(PropertyInfo pi, boolean addComma) { return generateColumnDefinition(pi, addComma, false); } /** * Creates the column definition based upon the property name, its type, and * a possibly defined initial value. Also adds "NOT NULL" if indicated via * parameter or for all properties that can be nil/null (set via tagged * value or stereotype) or which are optional. * * @param pi * @param addComma * true if a ',' shall be added at the end of the statement * (before a potential comment), else false (specifically used * for the last column in a table creation statement). * @param alwaysNotNull * <code>true</code> if the column definition shall be created * with NOT NULL, otherwise <code>false</code> (then default * behavior applies) * @return The SQL statement with the definition of the column to represent * the property */ private String generateColumnDefinition(PropertyInfo pi, boolean addComma, boolean alwaysNotNull) { String columnName; if (refersToTypeRepresentedByTable(pi)) { columnName = normalizeName(pi.name() + identifyForeignKeyColumnSuffix(pi)); } else { columnName = normalizeName(pi.name()); } String columnType = identifyType(pi); String columnDefault = pi.initialValue(); String result = columnName + " " + columnType; if (columnDefault != null && columnDefault.trim().length() > 0) { /* * strings in UML: enclosed by " strings in SQL: enclosed by ' */ result = result + " DEFAULT " + columnDefault.trim().replace("\"", "'"); } // ----- add constraints if (alwaysNotNull) { result = result + " NOT NULL"; } else { // set NOT NULL if required if (pi.implementedByNilReason() || pi.nilReasonAllowed() || pi.voidable() || pi.cardinality().minOccurs < 1) { /* * in these cases the default behavior (that the field can be * NULL) is ok */ } else { result = result + " NOT NULL"; } } // ----- finalize column definition if (addComma) { result = result + ","; } if (createDocumentation) { String s = pi.derivedDocumentation(documentationTemplate, documentationNoValue); if (s != null) { result = result + " -- " + s.replaceAll("\\s+", " "); } } return result; } /** * @param pi * @return If a map entry is defined for the type, then the return value is * <code>true</code> if the entry specifies (via the parameter) a * mapping to a table, else <code>false</code> is returned. * Otherwise, if the value type of the property is a feature, * object, or data type that: 1) can be found in the model, 2) table * creation for the type is allowed (defined by the conversion * rules), and 3) is in the currently processed schema OR * {@value #RULE_TGT_SQL_CLS_REFERENCES_TO_EXTERNAL_TYPES} is * enabled, then the return value is <code>true</code> - else * <code>false</code>. */ public boolean refersToTypeRepresentedByTable(PropertyInfo pi) { String valueTypeName = pi.typeInfo().name; ProcessMapEntry pme = this.mapEntryByType.get(valueTypeName); if (pme != null) { if (mepp.hasParameter(valueTypeName, ME_PARAM_TABLE)) { return true; } else { return false; } } else if (pi.categoryOfValue() == Options.FEATURE || pi.categoryOfValue() == Options.OBJECT || pi.categoryOfValue() == Options.DATATYPE) { ClassInfo typeCi = this.model.classById(pi.typeInfo().id); if (typeCi != null) { if ((pi.categoryOfValue() == Options.OBJECT && !typeCi.matches(RULE_TGT_SQL_CLS_OBJECT_TYPES)) || (pi.categoryOfValue() == Options.FEATURE && !typeCi.matches(RULE_TGT_SQL_CLS_FEATURE_TYPES)) || (pi.categoryOfValue() == Options.DATATYPE && !typeCi.matches(RULE_TGT_SQL_CLS_DATATYPES))) { return false; } else { if (typeCi.inSchema(schema) || typeCi.matches(RULE_TGT_SQL_CLS_REFERENCES_TO_EXTERNAL_TYPES)) { return true; /* * NOTE: if the schema uses external types, map entries * should be defined. This helps avoiding confusion of * types that are not processed but used in the model * (e.g. from ISO packages, or application schemas that * were not selected for processing). The rule to allow * references to external types is a convenience * mechanism. */ } else { return false; } } } else { return false; } } else { return false; } } /** * @param pi * @return <code>true</code> if the value type of the given property is a * geometry type - which requires a map entry for the value type * whose param contains the {@value #ME_PARAM_GEOMETRY} parameter; * otherwise <code>false</code> is returned. */ private boolean isGeometryTypedProperty(PropertyInfo pi) { String piTypeName = pi.typeInfo().name; ProcessMapEntry me = this.mapEntryByType.get(piTypeName); if (me != null && mepp.hasParameter(piTypeName, ME_PARAM_GEOMETRY)) { return true; } else { return false; } } /** * Identifies the type to use in the SQL definition of the property. * * At first, standard mappings (defined via the configuration) are applied. * If there is no direct standard mapping, then conditional mappings based * upon the category/stereotype of the type is performed: enumeration, * codelist and object types are mapped to 'text' or 'character varying'. If * the type is a feature, 'bigserial' is returned (actual foreign key * references can be added via a separate processing step, see * {@link #generateForeignKeyDefinitions(ClassInfo)}. If all else fails, * 'unknown' is returned as type. * * @param pi * @return the type to use in the SQL definition of the property */ private String identifyType(PropertyInfo pi) { // first apply well-known mappings String piTypeName = pi.typeInfo().name; // try to get type from map entries ProcessMapEntry me = this.mapEntryByType.get(piTypeName); if (me != null) { if (mepp.hasParameter(piTypeName, ME_PARAM_GEOMETRY)) { return databaseStrategy.geometryDataType(me, srid); } else if (mepp.hasParameter(piTypeName, ME_PARAM_TABLE)) { return foreignKeyColumnDataType; } else { if (me.getTargetType().startsWith(MAP_TARGETTYPE_COND_PART)) { String conditionalCriterium = me.getTargetType().substring(MAP_TARGETTYPE_COND_PART.length()); if (conditionalCriterium.equalsIgnoreCase(MAP_TARGETTYPE_COND_TEXTORCHARACTERVARYING)) { return determineCharacterVaryingOrText(pi); } } else { return me.getTargetType(); } } } // try to identify a type mapping based upon the category of the // property value int catOfValue = pi.categoryOfValue(); if (catOfValue == Options.ENUMERATION || catOfValue == Options.CODELIST) { return determineCharacterVaryingOrText(pi); } else if (catOfValue == Options.OBJECT || catOfValue == Options.FEATURE || catOfValue == Options.DATATYPE) { ClassInfo typeCi = this.model.classById(pi.typeInfo().id); if (typeCi != null) { if ((catOfValue == Options.OBJECT && !typeCi.matches(RULE_TGT_SQL_CLS_OBJECT_TYPES)) || (catOfValue == Options.FEATURE && !typeCi.matches(RULE_TGT_SQL_CLS_FEATURE_TYPES)) || (catOfValue == Options.DATATYPE && !typeCi.matches(RULE_TGT_SQL_CLS_DATATYPES))) { /* * table creation for this category is not enabled -> assign * textual type */ return determineCharacterVaryingOrText(pi); } else { if (typeCi.inSchema(schema) || typeCi.matches(RULE_TGT_SQL_CLS_REFERENCES_TO_EXTERNAL_TYPES)) { return foreignKeyColumnDataType; } else { result.addWarning(this, 9, typeCi.name(), pi.name(), pi.inClass().name()); return determineCharacterVaryingOrText(pi); } } } else { result.addWarning(this, 10, pi.typeInfo().name, pi.name(), pi.inClass().name()); return determineCharacterVaryingOrText(pi); } } result.addWarning(this, 21, piTypeName); return "unknown"; } /** * Determines if the property should have a type that allows unlimited or * limited text size. This depends upon the setting of {@value #PARAM_SIZE}, * locally via a tagged value or globally via a configuration parameter or * the default value defined by this class ({@value #DEFAULT_SIZE}): if * {@value #PARAM_SIZE} is 0 or negative, the type is for unlimited text * size; otherwise it is with limited size (as determined by the size tagged * value, parameter, or default). * * @param pi * @return the data type for unlimited or limited text size, depending upon * the (local and global) settings of 'size' for the property */ private String determineCharacterVaryingOrText(PropertyInfo pi) { int size = getSizeForProperty(pi); if (size < 1) { return databaseStrategy.unlimitedLengthCharacterDataType(); } else { return databaseStrategy.limitedLengthCharacterDataType(size); } } /** * Determines the applicable 'size' for the given property. If the tagged * value {@value #PARAM_SIZE} is set for the property, its value is * returned. Otherwise the default value (given via the configuration * parameter {@value #PARAM_SIZE} or as defined by this class [ * {@value #DEFAULT_SIZE}]) applies. * * @param pi * @return */ private int getSizeForProperty(PropertyInfo pi) { String tvSize = pi.taggedValuesAll().getFirstValue(PARAM_SIZE); int size = defaultSize; if (tvSize != null) { try { size = Integer.parseInt(tvSize); } catch (NumberFormatException e) { MessageContext mc = result.addWarning(this, 5, PARAM_SIZE, e.getMessage(), "" + defaultSize); mc.addDetail(this, 0); mc.addDetail(this, 100, pi.name(), pi.inClass().name()); size = defaultSize; } } return size; } /** * Adds the carriage return and line feed defined for this class ( * {@value #CRLF}) to the given buffer. * * @param sb */ private void newLine(StringBuffer sb) { sb.append(CRLF); } /** * Adds the given string to the given buffer, adding the carriage return and * line feed defined for this class ({@value #CRLF}) at the end of the * string, without preceding indentation. * * @param string */ private void addCRLF(StringBuffer sb, String string) { sb.append(string + CRLF); } /** * Adds the given string to the given buffer, preceding it with the * indentation value defined for this class ({@value #IDT}), and adding the * carriage return and line feed defined for this class ({@value #CRLF}) at * the end of the string. * * @param string */ private void indent(StringBuffer sb, String string) { sb.append(IDT + string + CRLF); } /** * @see de.interactive_instruments.ShapeChange.Target.Target#write() */ public void write() { if (printed) { return; } if (diagnosticsOnly) { return; } generateGeometryMetadataUpdateStatements(); // write all indexes generateGeometryIndexes(); // TBD: add further indexes as required String fileName = schema.name().replace("/", "_").replace(" ", "_") + ".sql"; // String fileName = outputFilename + ".sql"; // Now aggregate the output StringBuffer sb = new StringBuffer(); // add table creation statements for (String tableCreationStatement : this.tablesByClassName.values()) { sb.append(tableCreationStatement); } newLine(sb); if (createAssociativeTables) { // add statements to create associative tables for (String tableCreationStatement : this.tablesByAssociativeTableName.values()) { sb.append(tableCreationStatement); } newLine(sb); } for (String alterTableStatement : alterTableStatementsByClassName.values()) { sb.append(alterTableStatement); } newLine(sb); for (List<String> checkConstrStmts : checkConstraintsForPropsWithEnumValueTypeByTableName.values()) { for (String stmt : checkConstrStmts) { sb.append(stmt); } } newLine(sb); // if required, add foreign key statements for (List<String> foreignKeys : referenceColumnDefinitionsByTableName.values()) { for (String foreignKeyStatement : foreignKeys) { sb.append(foreignKeyStatement); } } newLine(sb); for (String geometryMetadataUpdateStatement : geometryMetadataUpdateStatements) { sb.append(geometryMetadataUpdateStatement); } newLine(sb); // add indexes for (String geometryIndexCreationStatement : this.geometryIndexCreationStatements) { sb.append(geometryIndexCreationStatement); } newLine(sb); BufferedWriter writer = null; try { File file = new File(outputDirectory, fileName); writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8")); writer.write(sb.toString()); writer.close(); result.addResult(getTargetID(), outputDirectory, fileName, null); printed = true; } catch (Exception e) { String m = e.getMessage(); if (m != null) { result.addError(m); } e.printStackTrace(System.err); } finally { IOUtils.closeQuietly(writer); } } private void generateGeometryMetadataUpdateStatements() { List<String> result = new ArrayList<String>(); for (String classname : geometryPropsByTableName.keySet()) { for (PropertyInfo pi : geometryPropsByTableName.get(classname)) { String s = databaseStrategy.geometryMetadataUpdateStatement(normalizeName(classname), normalizeName(pi.name()), srid); if (!s.isEmpty()) { result.add(s + ";" + CRLF); } } } this.geometryMetadataUpdateStatements = result; } /** * Generates index creation statements for all geometry properties/columns * contained in {@link #geometryPropsByTableName}. The statements are stored * in an internal list ({@link #geometryIndexCreationStatements}). */ private void generateGeometryIndexes() { List<String> result = new ArrayList<String>(); for (String classname : geometryPropsByTableName.keySet()) { for (PropertyInfo pi : geometryPropsByTableName.get(classname)) { String columnName = normalizeName(pi.name()); Map<String, String> geometryCharacteristics = null; if (mepp.hasParameter(pi.typeInfo().name, ME_PARAM_GEOMETRY)) { geometryCharacteristics = mepp.getCharacteristics(pi.typeInfo().name, ME_PARAM_GEOMETRY); } String s = "CREATE INDEX " + normalizeName("idx_" + classname + "_" + columnName) + " ON " + normalizeName(classname) + databaseStrategy.geometryIndexColumnPart(columnName, geometryCharacteristics) + ";" + CRLF; result.add(s); } } this.geometryIndexCreationStatements = result; } public int getTargetID() { return TargetIdentification.SQLDDL.getId(); } /** * @param name * @return String with any occurrence of '.' or '-' replaced by '_'. */ private String normalizeName(String name) { if (name == null) { return null; } else { return databaseStrategy.normalizeName(name.replace(".", "_").replace("-", "_")); } } private String createNameCheckConstraint(String tableName, String propertyName) { if (tableName == null || propertyName == null) { return null; } return databaseStrategy.createNameCheckConstraint(tableName.replace(".", "_").replace("-", "_"), propertyName.replace(".", "_").replace("-", "_")); } /** * @see de.interactive_instruments.ShapeChange.MessageSource#message(int) */ public String message(int mnr) { switch (mnr) { case 0: return "Context: class SqlDdl"; case 1: return "Generating SQL DDL for application schema '$1$'."; case 2: return "Processing class '$1$'."; case 3: return "Directory named '$1$' does not exist or is not accessible."; case 4: return "Number format exception while converting the value of configuration parameter '$1$' to an integer. Exception message: $2$. Using $3$ as default value for '$1$'."; case 5: return "Number format exception while converting the tagged value '$1$' to an integer. Exception message: $2$. Using $3$ as default value."; case 6: return "Unknown database system '$1$'"; case 7: return "Role name '$1$', on a relation from '$2$' to '$3$', seems to be an autogenerated name."; case 8: return "??Many-to-many relationship represented by association between types with identity and maximum multiplicity > 1 on all navigable ends (in this case for classes: '$1$' [context is property '$2$'] <-> '$3$' [context is property '$4$']) is only supported if creation for associative tables is enabled (via inclusion of rule " + RULE_TGT_SQL_ALL_ASSOCIATIVETABLES + "). Because the rule is not included, the relationship will be ignored."; case 9: return "Type '$1$' of property '$2$' in class '$3$' is not part of the schema that is being processed, no map entry is defined for it, and " + RULE_TGT_SQL_CLS_REFERENCES_TO_EXTERNAL_TYPES + " is not enabled. Please ensure that map entries are defined for external types used in the schema - or allow referencing of external types in general by enabling " + RULE_TGT_SQL_CLS_REFERENCES_TO_EXTERNAL_TYPES + ". Assigning textual type to the property."; case 10: return "Type '$1$' of property '$2$' in class '$3$' could not be found in the model. Assigning textual type to the property."; case 11: return "Attribute '$1$' in class '$2$' has maximum multiplicity greater than one. Creation of associative tables is not enabled. The property will thus be ignored."; case 12: return "Creating associative table to represent attribute '$1$' in class '$2$'. Tagged value '" + TV_ASSOCIATIVETABLE + "' not set on this attribute, thus using default naming pattern, which leads to table name: '$3$'."; case 13: return "Creating associative table to represent association between $1$ and $2$. Tagged value '" + TV_ASSOCIATIVETABLE + "' not set on this association, thus using default naming pattern, which leads to table name: '$3$'."; case 14: return "Derived property '$1$' in class '$2$' has been ignored."; case 15: return "No map entries provided via the configuration."; case 16: return "The type of property '$1$' in class '$2$' is '$3$'. It is contained in the schema that is being processed. However, it is of a category not enabled for conversion, meaning that no table will be created to represent the type '$3$'. The property '$1$' in class '$2$' will therefore be ignored."; case 17: return "Type '$1$' is of a category not enabled for conversion, meaning that no table will be created to represent it."; case 18: return "Could not find enumeration '$1$' in the model - or no enum values defined for it. Check constraint for '$2$' will not be created."; case 19: return "Invalid map entry for type '$1$': no value is provided for the characteristic '$2$' of parameter '$3$'."; case 20: return "Invalid map entry for type '$1$': value provided for characteristic '$2$' of parameter '$3$' is invalid. Check that the value matches the regular expression: $4$."; case 21: return "?? The type '$1$' was not found in the schema(s) selected for processing or in map entries. It will be mapped to 'unknown'."; case 100: return "Context: property '$1$' in class '$2$'."; default: return "(Unknown message)"; } } }