<% import org.apache.log4j.Logger import com.objectgen.core.* import com.objectgen.types.* // Configure MySQL specifics by setting database='mysql' def database = '' log = Logger.getLogger("com.objectgen.codegen.hibernate.hbm-template") collectionTypes = [ 'set', 'list', 'bag', 'idbag', 'map' ] // Handle subclasses with Table per class hierarchy. // Find all Persistent subclasses in the same package. subclasses = [] for(sub in c.packageData.classifiers) { if(sub.stereotype?.name == 'Persistent' && sub.superClass == c) { subclasses.add(sub) } } %> <% /* Feel free to edit this template, but not the generated xml file. */ %> <% // Generate if a tag @hibernate.version is designed. version = TagUtils.findTag(c, '@hibernate.version') if(version != null) { %> <% } // First, generate id or composite-id. for(var in c.variables) { String propertyName = propertyName(var) if(var.stereotype?.name == 'id') { if(var.attributeType) { generatorClass = tagParamValue(var, '@hibernate', 'generator.class', 'native') %> <% } else { %> <% for(compositeVar in var.type.variables) { %> <% } %> <% } } } if(!subclasses.empty) { %> <% } // Iterate through all variables in the class 2 times, // first for the elements in , then for the rest. for(boolean natural_id in [true,false]) { for(var in c.variables) { if(natural_id != (var.stereotype?.name == 'natural id')) continue if(var.isStatic() || var.isFinal()) continue // Skip <>, it is already generated. if(var.stereotype?.name == 'id') continue // Don't generate relations to non-persistent classes. if(!variableIsPersistent(var)) continue String propertyName = propertyName(var) element = null inverse = var.inverse hibernateTag = null collectionRefType = null childElements = [] childIndex = [:] attributes = [:] columns = [] naturalId = findNaturalId(var.type) Variable varId = findId(var.type) Variable compositeId = null if(varId != null && !varId.type.attributeType) { compositeId = varId } attributes['name'] = propertyName if(var.attributeType) { // The variable is an attribute. element = 'property' if(database == 'mysql' && (var.type.name == 'boolean' || var.type.name == 'Boolean') ) { attributes['type'] = javaType2Hibernate(var.type) childIndex.put('column', childElements.size()) sqlTypeParam = new TagParameter('column.sql-type', 'boolean') childElements.add( [ sqlTypeParam ] ) } else { attributes['type'] = javaType2Hibernate(var.type) attributes['column'] = mixedCase2UnderScore(var.name) } hibernateTag = TagUtils.findTag(var, '@hibernate.property') if(hibernateTag == null) hibernateTag = TagUtils.findTag(var, '@hibernate') debug 'attribute: ' + var } else if(!mapAsSet(var)) { // The variable is a single association. attributes['class'] = className(var.type) singleTypes = [ 'many-to-one', 'one-to-one', 'component' ] for(s in singleTypes ) { hibernateTag = TagUtils.findTag(var, '@hibernate.' + s) if(hibernateTag != null) { element = s break } } if(hibernateTag == null) { hibernateTag = TagUtils.findTag(var, '@hibernate') } if(element == null) { // Map 1 to 1 associations to a many-to-one and a one-to-one relation. if(inverse != null && inverse.single && (c.name >= var.type.name)) element = 'one-to-one' else element = 'many-to-one' } if(element == 'one-to-one' && inverse != null) attributes['property-ref'] = inverse.name if(element != 'one-to-one' && element != 'component' && var.multiplicity == var.ONE) { attributes['not-null'] = 'true' attributes['cascade'] = 'save-update' } if(element == 'many-to-one') { String col = (naturalId != null ? naturalId.name : 'id') attributes['foreign-key'] = 'fk_' + mixedCase2UnderScore(baseClassName(c.name) + '_' + var.name) + '_' + col if(findColumnNameTag(var) != null) { // The elements are generated by tags. Do nothing here. } else if(compositeId != null) { for(compositeVar in compositeId.type.variables) { columns.add( mixedCase2UnderScore(propertyName(compositeVar)) ) } } else { columns.add( mixedCase2UnderScore(propertyName) + '_' + col ) } if(inverse != null && inverse.single) attributes['unique'] = 'true' if(naturalId != null) { attributes['property-ref'] = naturalId.name } } /* TODO Generate cascade=all in the inverse end of 1 multiplicity? if(element == 'many-to-one' || element == 'one-to-one') attributes['cascade'] = 'all' */ debug 'single association: hibernateTag=' + hibernateTag + ', element=' + element } else { // The variable is a multiple association. // Generate a // The user can generate , etc by designing a tag like "@hibernate.list". element = 'set' for(s in collectionTypes ) { hibernateTag = TagUtils.findTag(var, '@hibernate.' + s) if(hibernateTag != null) { element = s break } } if(hibernateTag == null) { hibernateTag = TagUtils.findTag(var, '@hibernate') } debug 'multiple association: hibernateTag=' + hibernateTag + ', element=' + element // See if this is a bidirectional association. debug "inverse=" + inverse + (inverse != null ? ", inverse.multiplicity=" + inverse.multiplicity : "") if(inverse != null && inverse.single) { collectionRefType = 'one-to-many' attributes['inverse'] = 'true' debug "${var} aggregate=${var.aggregate}, ${inverse} aggregate=${inverse.aggregate}" if(var.aggregate) { if(inverse.multiplicity == Variable.ONE) attributes['cascade'] = 'all,delete-orphan' else attributes['cascade'] = 'all' } } else { collectionRefType = 'many-to-many' if(inverse != null) attributes['inverse'] = (c.name >= var.type.name) relation = null if(var.associationName != null) relation = var.associationName else if(inverse != null && c.name >= var.type.name) relation = baseClassName(var.type.name) + '_' + baseClassName(c.name) else relation = baseClassName(c.name) + '_' + baseClassName(var.type.name) attributes['table'] = tableName(relation) } } // Collect parameters to the @hibernate tag. // These may override the default attributes set earlier. if(hibernateTag != null) { debug 'hibernateTag=' + hibernateTag for(param in hibernateTag.parameters) { if(param.name.startsWith('column.')) { attributes.remove('column') } ix = param.name.indexOf(".") if(ix < 0) { attributes[param.name] = param.value } else { // Join duplicate elements in the list. elementName = param.name.substring(0, ix) if(childIndex.containsKey(elementName)) { index = childIndex.get(elementName) childElements.get(index).add(param) debug 'add to <' + elementName + '>: childElements=' + childElements } else { childIndex.put(elementName, childElements.size()) childElements.add( [ param ] ) debug 'new element <' + elementName + '>: childElements='+ childElements } } } } // Generate mapping for the variable. // Generate around 1 property if it has that stereotype. // TODO Support multiple elements in the same if(natural_id) { %> <% } %> <${element} <% for(key in attributes.keySet()) { %> ${key}='${attributes[key]}'<% } %>> <% for(paramList in childElements) { param1 = paramList.get(0) ix = param1.name.indexOf(".") elementName = param1.name.substring(0, ix) if(elementName != collectionRefType) { debug 'generate <' + elementName + '> parameters: ' + paramList %> <${elementName}<% for(param in paramList) { attrName = param.name.substring(ix+1) %> ${attrName}='${param.value}'<% } %>/> <% } } for(column in columns) { %> <% } // For map, set, list and bag, generate if(['map', 'set', 'list', 'bag'].contains(element) && !childIndex.containsKey('key')) { def fk = 'fk_' + mixedCase2UnderScore(baseClassName(c.name) + '_' + baseClassName(var.type.name)) + '_key' def idCol = idColumn(c, null) if(inverse != null) { def inverseNaturalId = findNaturalId(c) String col = (inverseNaturalId != null ? inverseNaturalId.name : 'id') idCol = mixedCase2UnderScore(inverse.name) + '_' + col fk = 'fk_' + mixedCase2UnderScore(baseClassName(var.type.name) + '_' + inverse.name) + '_key' } %> <% } // For map and list, generate if((element == 'map' || element == 'list') && !childIndex.containsKey('index')) { %> type='string'<% } %> /> <% } // For multiple associations, generate unless , // , or has already been generated. foundChild = findMapValue(childIndex, ['one-to-many', 'element', 'composite-element', 'many-to-one', 'many-to-any']) if(!var.attributeType && mapAsSet(var) && foundChild == null) { debug 'Generate collectionRefType=' + collectionRefType %> <${collectionRefType} class='${className(var.type)}'<% if(collectionRefType == 'many-to-many') { def fk = 'fk_' + mixedCase2UnderScore(baseClassName(c.name) + '_' + propertyName) %> foreign-key='${fk}'<% String column = null if(naturalId != null) { column = naturalId.name } else if(compositeId == null) { column = relationColumn(c,var,inverse) } if(column != null) { %> column='${column}'<% } if(naturalId != null) { %> property-ref='${naturalId.name}'<% } } // Generate the @hibernate tag parameters. for(paramList in childElements) { param1 = paramList.get(0) ix = param1.name.indexOf(".") elementName = param1.name.substring(0, ix) if(elementName == collectionRefType) { debug 'generate ' + elementName + ' parameters: ' + paramList for(param in paramList) { attrName = param.name.substring(ix+1) %> ${attrName}='${param.value}'<% } } } if(compositeId != null && collectionRefType != 'one-to-many') { %> ><% for(compositeVar in compositeId.type.variables) { %> <% } %> <% } else { %> /> <% } } %> <% if(natural_id) { %> <% } } // end for(var) } // end for(natural_id) // Handle subclasses with Table per class hierarchy. // The subclasses will not get their own mapping files. for(sub in subclasses) { %> <% for(var in sub.variables) { if(var.stereotype?.name == 'id') continue else if(var.isStatic() || var.isFinal()) continue else if(!variableIsPersistent(var)) continue else if(var.attributeType) { %> <% } } %> <% } %> <% void debug(s) { if(log != null) log.debug(s) } boolean variableIsPersistent(var) { if(var.type instanceof Classifier) { Classifier varType = (Classifier) var.type if(!varType.attributeType && (varType.stereotype == null || varType.stereotype.name != 'Persistent') ) return false } return true } Object findMapValue(map, keyList) { for(key in keyList) { value = map.get(key) if(value != null) return value } return null; } String tagParamValue(taggedValue, tagName, paramName, defaultValue) { param = TagUtils.findTagParameter(taggedValue, tagName, paramName) return (param != null ? param.value : defaultValue) } String tagParamValue(tag, paramName, defaultValue) { param = TagUtils.findTagParameter(tag, paramName) return (param != null ? param.value : defaultValue) } String tagParameters(tag) { buf = new StringBuffer() for(param in tag.parameters) { buf.append(" ").append(param.name).append("=").append("\'").append(param.value).append("\'") } return buf } /** For a class name "VolumePriceType", return "volum_price_type_id" etc. */ String idColumn(c, id) { if(id == null) { id = findId(c) } if(id != null) { idColumn = findColumnNameTag(id) if(idColumn != null) { return idColumn } } s = baseClassName(c.name) return mixedCase2UnderScore(s) + '_id' } String columnName(var) { column = findColumnNameTag(var) if(column != null) { return column } else { return mixedCase2UnderScore(var.name) } } String findColumnNameTag(var) { column = tagParamValue(var, '@hibernate', 'column', null) if(column == null) column = tagParamValue(var, '@hibernate', 'column.name', null) return column } String className(c) { return c.fullName } String baseClassName(String s) { // Can put special handling, for example remove prefix "H" // if(s[0] == 'H') // s = s[1..-1] return s } // Convert "MyTable" to "tb_my_table" String tableName(s) { s = baseClassName(s) buf = new StringBuffer('tb') mixedCase2UnderScore(s, buf) return buf } String indexColumn(var) { return mixedCase2UnderScore(var.name) + '_ix' } String relationColumn(c, var, inverse) { return mixedCase2UnderScore(var.name) + '_id' } String inverseRelationColumn(c, var, inverse) { if(inverse != null) return mixedCase2UnderScore(inverse.name) + '_id' else return relationColumn(c, var, inverse) } String javaType2Hibernate(type) { return HibernateTypes.instance.javaType2hibernateType(type.name) } /** Convert from "MixedCase" to "mixed_case" etc. */ String mixedCase2UnderScore(String s, StringBuffer buf) { for(char ch in s) { if(buf.size() > 0 && Character.isUpperCase(ch)) { if(buf[-1] != '_') buf.append('_') } buf.append(Character.toLowerCase(ch)) } return buf } String mixedCase2UnderScore(String s) { return mixedCase2UnderScore(s, new StringBuffer()) } // Find any variable with stereotype <> in a class. Variable findNaturalId(type) { return findVariableStereotype(type, 'natural id') } // Find any variable with stereotype <> in a class. Variable findId(type) { return findVariableStereotype(type, 'id') } Variable findVariableStereotype(cl, stereotype) { if(cl instanceof Classifier && !cl.attributeType) { for(var in cl.variables) { if(var.stereotype != null && var.stereotype.name == stereotype) { return var } } } return null } // Determine if a relation shall be mapped to a boolean mapAsSet(var) { if(!var.single) return true for(s in collectionTypes ) { hibernateTag = TagUtils.findTag(var, '@hibernate.' + s) if(hibernateTag != null) return true } return false } // Convert variable name to a correct property name String propertyName(var) { String varName = var.name if(varName.length() >= 2) { if(Character.isUpperCase( (char)varName[1] ) ) return varName[0].toUpperCase() + varName[1..-1] else return varName[0].toLowerCase() + varName[1..-1] } return varName } %>