/* * Copyright 2013 The Sculptor Project Team, including the original * author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sculptor.generator.util; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.inject.Inject; import sculptormetamodel.Application; import sculptormetamodel.Attribute; import sculptormetamodel.BasicType; import sculptormetamodel.DomainObject; import sculptormetamodel.Entity; import sculptormetamodel.Enum; import sculptormetamodel.EnumConstructorParameter; import sculptormetamodel.EnumValue; import sculptormetamodel.Inheritance; import sculptormetamodel.Module; import sculptormetamodel.NamedElement; import sculptormetamodel.Reference; import sculptormetamodel.SculptormetamodelFactory; import sculptormetamodel.impl.SculptormetamodelFactoryImpl; public class DbHelperBase { private static final String ID_ATTRIBUTE_NAME = "id"; @Inject private HelperBase helperBase; @Inject private PropertiesBase propBase; @Inject private SingularPluralConverter singularPluralConverter; public List<DomainObject> getDomainObjectsInCreateOrder(Application application, Boolean ascending) { List<DomainObject> all = getAllDomainObjects(application); List<DomainObject> orderedClasses = new ArrayList<DomainObject>(); Set<DomainObject> handledClasses = new HashSet<DomainObject>(); for (DomainObject domainObject : all) { addClassRecursive(domainObject, orderedClasses, handledClasses); } if (!ascending) { Collections.reverse(orderedClasses); } return orderedClasses; } /** * @return all persistent DomainObjects */ public List<DomainObject> getAllDomainObjects(Application application) { List<DomainObject> all = new ArrayList<DomainObject>(); List<Module> modules = HelperBase.sortByName(application.getModules()); for (Module m : modules) { if (m.isExternal()) { continue; } List<DomainObject> domainObjects = HelperBase.sortByName(m.getDomainObjects()); for (DomainObject d : domainObjects) { if (helperBase.isPersistent(d) && includeInDdl(d)) { all.add(d); } } } return all; } private boolean includeInDdl(DomainObject domainObj) { return !helperBase.hasHintImpl(domainObj.getHint(), "skipddl"); } private void addClassRecursive(DomainObject domainObject, List<DomainObject> orderedClasses, Set<DomainObject> handledClasses) { if (handledClasses.contains(domainObject)) { // already added return; } if (domainObject instanceof BasicType) { // no tables for BasicType return; } if (!helperBase.isPersistent(domainObject) || !includeInDdl(domainObject)) { // no tables for non persistent ValueObject return; } if (domainObject.getModule().isExternal()) { return; } // add current class, will break recursion if referred again handledClasses.add(domainObject); // we must have the referenced super class first, due to foreign key if (domainObject.getExtends() != null) { addClassRecursive(domainObject.getExtends(), orderedClasses, handledClasses); } // we must have the referenced classes first, due to foreign keys for (Reference ref : getAllOneReferences(domainObject)) { addClassRecursive(ref.getTo(), orderedClasses, handledClasses); } if (!orderedClasses.contains(domainObject)) { orderedClasses.add(domainObject); } } public String getDiscriminatorColumnDatabaseType(Inheritance inheritance) { // create an Attribute so that we can reuse existing logic for // databaseType SculptormetamodelFactory factory = SculptormetamodelFactoryImpl.eINSTANCE; Attribute attr = factory.createAttribute(); if (inheritance.getDiscriminatorColumnName() == null) { attr.setName(propBase.getProperty("db.discriminatorColumnName")); } else { attr.setName(inheritance.getDiscriminatorColumnName()); } attr.setType("discriminatorType." + inheritance.getDiscriminatorType().getLiteral()); if (inheritance.getDiscriminatorColumnLength() != null) { attr.setLength(inheritance.getDiscriminatorColumnLength()); } return getDatabaseType(attr); } public String getDatabaseType(Attribute attribute) { String databaseTypeProperty = attribute.getDatabaseType(); String databaseType; if (databaseTypeProperty == null) { databaseType = getDefaultDbType(attribute.getType()); } else { databaseType = databaseTypeProperty; } String length = getDatabaseLength(attribute); if (length != null && databaseType.indexOf('(') == -1) { databaseType += ("(" + length + ")"); } return databaseType; } public String getEnumDatabaseType(Reference reference) { Attribute enumAttribute = getEnumAttribute((Enum) reference.getTo()); if (helperBase.hasHintImpl(reference.getHint(), "databaseLength")) enumAttribute.setLength(helperBase.getHintImpl(reference.getHint(), "databaseLength")); return getDatabaseType(enumAttribute); } public String getEnumDatabaseType(Enum _enum) { return getDatabaseType(getEnumAttribute(_enum)); } public String getEnumType(Enum _enum) { return getEnumAttribute(_enum).getType(); } public String getEnumDatabaseLength(Enum _enum) { return getEnumAttribute(_enum).getLength(); } private Attribute getEnumKeyAttribute(Enum _enum) { List<Attribute> enumAttributes = _enum.getAttributes(); for (Attribute attribute : enumAttributes) { if (attribute.isNaturalKey()) { return attribute; } } return createDefaultEnumAttribute(); } private Attribute createDefaultEnumAttribute() { SculptormetamodelFactory factory = SculptormetamodelFactoryImpl.eINSTANCE; Attribute attr = factory.createAttribute(); attr.setType("String"); return attr; } private boolean hasNaturalKeyAttribute(Enum _enum) { return (getEnumKeyAttribute(_enum).isNaturalKey()); } private Attribute getEnumAttribute(Enum _enum) { Attribute attribute = getEnumKeyAttribute(_enum); if (_enum.isOrdinal()) { attribute.setType("int"); attribute.setLength(null); } else { if (helperBase.hasHintImpl(_enum.getHint(), "databaseLength")) { attribute.setType("String"); attribute.setLength(helperBase.getHintImpl(_enum.getHint(), "databaseLength")); } else { attribute.setType(attribute.getType() != null ? attribute.getType() : "String"); attribute.setLength(calcEnumDatabaseLength(_enum)); } } return attribute; } private String calcEnumDatabaseLength(Enum _enum) { int maxLength = 0; if (hasNaturalKeyAttribute(_enum)) { int attributePosition = 0; for (Attribute attribute : (List<Attribute>) _enum.getAttributes()) { if (attribute.isNaturalKey()) { for (EnumValue enumValue : (List<EnumValue>) _enum.getValues()) { EnumConstructorParameter enumParam = (EnumConstructorParameter) enumValue.getParameters().get( attributePosition); maxLength = calcMaxLength(enumParam.getValue(), maxLength); } break; } attributePosition++; } } if (maxLength == 0) { for (EnumValue value : (List<EnumValue>) _enum.getValues()) { maxLength = calcMaxLength(value.getName(), maxLength); } } return "" + maxLength; } private int calcMaxLength(String value, int maxLength) { int length = value.length(); if (value.startsWith("\"")) { length = length - 2; } return (maxLength < length) ? length : maxLength; } public String getDatabaseTypeNullability(Attribute attribute) { if (!attribute.isNullable() || attribute.isNaturalKey()) { return " NOT NULL"; } return ""; } public String getDatabaseTypeNullability(Reference reference) { if (!reference.isNullable() || reference.isNaturalKey()) { return " NOT NULL"; } return ""; } public String getDatabaseLength(Attribute attribute) { if (attribute.getLength() == null) { return getDefaultDbLength(attribute.getType()); } else { return attribute.getLength(); } } private String getDefaultDbType(String javaType) { return propBase.getDbType(javaType); } private String getDefaultDbLength(String javaType) { return propBase.getDbLength(javaType); } public String getDatabaseNameBase(NamedElement element) { String name = element.getName(); name = convertDatabaseName(name); return name; } /** * Moved to DbHelper * @deprecated */ @Deprecated private String convertDatabaseName(String name) { if (propBase.getBooleanProperty("db.useUnderscoreNaming")) { name = CamelCaseConverter.camelCaseToUnderscore(name); } name = truncateLongDatabaseName(name); name = name.toUpperCase(); return name; } public String truncateLongDatabaseName(String name) { int max = propBase.getMaxDbName(); return truncateLongDatabaseName(name, max); } public String truncateLongDatabaseName(String name, Integer max) { if (name.length() <= max) { return name; // no problem } else if (propBase.getBooleanProperty("db.errorWhenTooLongName")) { throw new RuntimeException("Generation aborted due to too long database name: " + name); } else { String hash = String.valueOf(Math.abs(name.hashCode())); hash = "0" + hash; // make sure that it is at least 2 chars hash = hash.substring(hash.length() - 2); // use 2 last characters String truncated = name.substring(0, max - hash.length()) + hash; return truncated; } } public String getDefaultForeignKeyNameBase(Reference ref) { String name; if (ref.isMany()) { name = singularPluralConverter.toSingular(ref.getName()); } else { name = ref.getName(); } DomainObject to = ref.getTo(); name += idSuffix(name, to); return convertDatabaseName(name); } public String getDefaultOppositeForeignKeyName(Reference ref) { if (ref.getOpposite() == null) { return getForeignKeyNameForUnidirectionalToManyWithJoinTable(ref); } else { return getDefaultForeignKeyNameBase(ref.getOpposite()); } } private String getForeignKeyNameForUnidirectionalToManyWithJoinTable(Reference ref) { if (ref.getDatabaseJoinColumn() != null) { return ref.getDatabaseJoinColumn(); } DomainObject from = ref.getFrom(); String name = from.getDatabaseTable(); if (name == null) { name = from.getName(); } name += idSuffix(name, from); return convertDatabaseName(name); } /** * Moved to DbHelper * @deprecated */ @Deprecated private String idSuffix(String name, DomainObject to) { if (useIdSuffixInForeignKey()) { Attribute idAttribute = getIdAttribute(to); if (idAttribute != null) { String idName = idAttribute.getDatabaseColumn().toUpperCase(); String convertedName = convertDatabaseName(name); if (idName.equals(convertedName) && idName.startsWith(to.getDatabaseTable())) { idName = idName.substring(to.getDatabaseTable().length()); } else if (idName.startsWith(convertedName)) { idName = idName.substring(convertedName.length()); } if (idName.startsWith("_")) { return idName; } else { return ("_" + idName); } } } return ""; } /** * Use Properties.useIdSuffixInForeigKey() instead * @deprecated */ @Deprecated private boolean useIdSuffixInForeignKey() { return propBase.getBooleanProperty("db.useIdSuffixInForeigKey"); } protected Attribute getIdAttribute(DomainObject domainObject) { Attribute idAttribute = getAttributeWithName(ID_ATTRIBUTE_NAME, domainObject); if ((idAttribute == null) && (domainObject.getExtends() != null)) { // look in extended DomainOject, recursive call idAttribute = getIdAttribute(domainObject.getExtends()); } return idAttribute; } private Attribute getAttributeWithName(String name, DomainObject domainObject) { for (Object obj : domainObject.getAttributes()) { Attribute attribute = (Attribute) obj; if (attribute.getName().equals(name)) { return attribute; } } // not found return null; } public String getForeignKeyType(Reference ref) { DomainObject referencedClass = ref.getTo(); return getForeignKeyType(referencedClass) + getDatabaseTypeNullability(ref); } public String getForeignKeyType(DomainObject referencedClass) { Attribute idAttribute = getIdAttribute(referencedClass); checkIdAttribute(referencedClass, idAttribute); String type = getDatabaseType(idAttribute); return type; } /** * Moved to DbHelper * @deprecated */ @Deprecated private void checkIdAttribute(DomainObject referencedClass, Attribute idAttribute) { if (idAttribute == null) { throw new IllegalArgumentException("Referenced class " + referencedClass.getName() + " doesn't contain 'id' attribute"); } } public List<DomainObject> resolveManyToManyRelations(Application application, Boolean ascending) { // first, find all many-to-many references Set<Reference> manyToManyReferences = new HashSet<Reference>(); List<DomainObject> domainObjects = getAllDomainObjects(application); for (DomainObject domainObject : domainObjects) { for (Reference ref : getAllManyReferences(domainObject)) { if (!helperBase.isPersistent(ref.getTo()) || !includeInDdl(ref.getTo())) { // skip this reference, since it refers to a non persistent // object continue; } if (ref.isTransient()) { // skip this reference, since it is transient continue; } Reference opposite = ref.getOpposite(); // undirectional many references are designed in db as // many-to-many, except when inverse is defined to true if ((opposite == null && !ref.isInverse()) || (opposite != null && opposite.isMany() && !opposite.isTransient() && !manyToManyReferences .contains(opposite))) { manyToManyReferences.add(ref); } } } // then, create an fictive DomainObject for each many-to-many reference List<DomainObject> manyToManyClasses = new ArrayList<DomainObject>(); for (Reference ref : manyToManyReferences) { DomainObject relObj = createFictiveManyToManyObject(ref); manyToManyClasses.add(relObj); } manyToManyClasses = HelperBase.sortByName(manyToManyClasses); if (!ascending) { Collections.reverse(manyToManyClasses); } return manyToManyClasses; } public DomainObject createFictiveManyToManyObject(Reference ref) { DomainObject relObj; if (ref.getFrom() instanceof Entity || ref.getTo() instanceof Entity) { relObj = SculptormetamodelFactory.eINSTANCE.createEntity(); } else { // many-to-many between two value objects is not likely (good // design) but whatever... relObj = SculptormetamodelFactory.eINSTANCE.createValueObject(); } String name = getManyToManyJoinTableName(ref); relObj.setName(name.toUpperCase()); relObj.setDatabaseTable(getDatabaseNameBase(relObj)); relObj.setAbstract(true); Reference ref1 = SculptormetamodelFactory.eINSTANCE.createReference(); ref1.setTo(ref.getTo()); ref1.setName(singularPluralConverter.toSingular(ref.getName())); ref1.setDatabaseColumn(ref.getDatabaseColumn()); ref1.setFrom(relObj); relObj.getReferences().add(ref1); Reference ref2 = SculptormetamodelFactory.eINSTANCE.createReference(); ref2.setTo(ref.getFrom()); if (ref.getOpposite() == null) { // use table name of from obj, it doesn't matter that we loose // upper/lower case ref2.setName(helperBase.toFirstLower(ref.getFrom().getName())); ref2.setDatabaseColumn(getForeignKeyNameForUnidirectionalToManyWithJoinTable(ref)); } else { ref2.setName(singularPluralConverter.toSingular(ref.getOpposite().getName())); ref2.setDatabaseColumn(ref.getOpposite().getDatabaseColumn()); } ref2.setFrom(relObj); relObj.getReferences().add(ref2); String tablespaceHint = helperBase.getHintImpl(ref.getFrom().getHint(), "tablespace"); if (tablespaceHint != null) { helperBase.addHint(relObj, "tablespace=" + tablespaceHint); } return relObj; } public String getManyToManyJoinTableName(Reference ref) { if (ref.getDatabaseJoinTable() != null) { return ref.getDatabaseJoinTable(); } if (ref.getOpposite() != null && ref.getOpposite().getDatabaseJoinTable() != null) { return ref.getOpposite().getDatabaseJoinTable(); } String name1 = ref.getDatabaseColumn(); name1 = removeIdSuffix(name1, ref.getTo()); String name2; if (ref.getOpposite() == null) { name2 = (ref.getFrom().getDatabaseTable() != null) ? ref.getFrom().getDatabaseTable() : ref.getFrom().getName() .toUpperCase(); } else { name2 = ref.getOpposite().getDatabaseColumn(); name2 = removeIdSuffix(name2, ref.getOpposite().getTo()); } return getJoinTableName(name1, name2, true); } public String getElementCollectionTableName(Attribute attribute) { String hintParam = "databaseJoinTableName"; if (helperBase.hasHintImpl(attribute.getHint(), hintParam)) { return helperBase.getHintImpl(attribute.getHint(), hintParam); } String name1 = singularPluralConverter.toSingular(attribute.getDatabaseColumn().toLowerCase()).toUpperCase(); String name2 = ((DomainObject) attribute.eContainer()).getDatabaseTable(); return getJoinTableName(name1, name2, false); } public String getElementCollectionTableName(Reference reference) { if (reference.getDatabaseJoinTable() != null) { return reference.getDatabaseJoinTable(); } String name1 = reference.getDatabaseColumn(); name1 = removeIdSuffix(name1, reference.getTo()); String name2; if (reference.getOpposite() == null) { name2 = (reference.getFrom().getDatabaseTable() != null) ? reference.getFrom().getDatabaseTable() : reference.getFrom() .getName().toUpperCase(); } else { name2 = reference.getOpposite().getDatabaseColumn(); name2 = removeIdSuffix(name2, reference.getOpposite().getTo()); } return getJoinTableName(name1, name2, false); } private String getJoinTableName(String name1, String name2, boolean ordered) { int max = propBase.getMaxDbName(); if ((name1.length() > (max - 6)) && (name2.length() > (max - 6))) { // both names are long, truncate both name1 = name1.substring(0, (max / 2)); name2 = name2.substring(0, (max / 2)); } if ((name1.length() + name2.length() + 1) > max) { // too long, truncate the longest name if (name1.length() > name2.length()) { name1 = name1.substring(0, (max - name2.length() - 1)); } else { name2 = name2.substring(0, (max - name1.length() - 1)); } } // order them in some well defined way, like alphabetic order if (ordered) { String name; if (name1.compareTo(name2) < 0) { name = name1 + "_" + name2; } else { name = name2 + "_" + name1; } return name; } return name2 + "_" + name1; } private String removeIdSuffix(String name, DomainObject to) { String idSuffix = idSuffix(name, to); if (idSuffix.equals("")) { return name; } if (name.endsWith(idSuffix)) { return name.substring(0, name.length() - idSuffix.length()); } return name; } /** * Inverse attribute for many-to-many associations. */ public boolean isInverse(Reference ref) { if (ref.isInverse()) { return true; } if (!ref.isInverse() && (ref.getOpposite() != null) && ref.getOpposite().isInverse()) { return false; } if (ref.getOpposite() == null) { return false; } // inverse not defined on any side, use this algorithm String name1 = ref.getTo().getName(); String name2 = ref.getFrom().getName(); // use a well defined algorithm, like alphabetic order // A -> B inverse=false // B -> A inverse= true return (name1.compareTo(name2) < 0); } /** * List of references with multiplicity > 1 */ private List<Reference> getAllManyReferences(DomainObject domainObject) { List<Reference> allReferences = domainObject.getReferences(); List<Reference> allManyReferences = new ArrayList<Reference>(); for (Reference ref : allReferences) { if (ref.isMany()) { allManyReferences.add(ref); } } return allManyReferences; } /** * List of references with multiplicity = 1 */ private List<Reference> getAllOneReferences(DomainObject domainObject) { List<Reference> allReferences = domainObject.getReferences(); List<Reference> allOneReferences = new ArrayList<Reference>(); for (Reference ref : allReferences) { if (!ref.isMany()) { allOneReferences.add(ref); } } return allOneReferences; } public String getDerivedCascade(Reference ref) { boolean inSameModule = ref.getFrom().getModule().equals(ref.getTo().getModule()); boolean biDirectional = ref.getOpposite() != null; boolean aggregate = helperBase.isEntityOrPersistentValueObject(ref.getTo()) && !ref.getTo().isAggregateRoot(); boolean oneToMany = biDirectional && ref.isMany() && !ref.getOpposite().isMany(); boolean manyToMany = biDirectional && ref.isMany() && ref.getOpposite().isMany(); boolean oneToOne = biDirectional && !ref.isMany() && !ref.getOpposite().isMany(); if (aggregate) { if (oneToMany) { return propBase.getDefaultCascade("aggregate.oneToMany"); } else { return propBase.getDefaultCascade("aggregate"); } } if (!inSameModule) { return null; } if (manyToMany) { return isInverse(ref) ? null : propBase.getDefaultCascade("manyToMany"); } if (oneToMany) { return propBase.getDefaultCascade("oneToMany"); } if (oneToOne) { return propBase.getDefaultCascade("oneToOne"); } if (!biDirectional && ref.isMany()) { return propBase.getDefaultCascade("toMany"); } if (!biDirectional && !ref.isMany()) { return propBase.getDefaultCascade("toOne"); } return null; } }