package org.springframework.roo.addon.dbre; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.springframework.roo.addon.dbre.model.Database; import org.springframework.roo.addon.dbre.model.DbreModelService; import org.springframework.roo.addon.dbre.model.Table; import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails; import org.springframework.roo.classpath.details.MemberHoldingTypeDetails; import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue; import org.springframework.roo.classpath.details.annotations.AnnotationMetadata; import org.springframework.roo.model.JavaPackage; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.model.JavaType; import static org.springframework.roo.model.JpaJavaType.TABLE; import org.springframework.roo.model.ReservedWords; import static org.springframework.roo.model.RooJavaType.ROO_JPA_ACTIVE_RECORD; import static org.springframework.roo.model.RooJavaType.ROO_JPA_ENTITY; /** * Provides methods to find types based on table names and to suggest type and * field names from table and column names respectively. * * @author Alan Stewart * @since 1.1 */ public abstract class DbreTypeUtils { private static final JavaSymbolName NAME_ATTRIBUTE = new JavaSymbolName( "name"); private static final JavaSymbolName SCHEMA_ATTRIBUTE = new JavaSymbolName( "schema"); // The annotation attributes from which to read the db schema name // Linked to preserve the iteration order below private static final Map<JavaType, JavaSymbolName> SCHEMA_ATTRIBUTES = new LinkedHashMap<JavaType, JavaSymbolName>(); private static final JavaSymbolName TABLE_ATTRIBUTE = new JavaSymbolName( "table"); // The annotation attributes from which to read the db table name // Linked to preserve the iteration order below private static final Map<JavaType, JavaSymbolName> TABLE_ATTRIBUTES = new LinkedHashMap<JavaType, JavaSymbolName>(); private static final HashSet<String>missingFromAliasSet = new HashSet<String>(); static { TABLE_ATTRIBUTES.put(TABLE, NAME_ATTRIBUTE); TABLE_ATTRIBUTES.put(ROO_JPA_ENTITY, TABLE_ATTRIBUTE); TABLE_ATTRIBUTES.put(ROO_JPA_ACTIVE_RECORD, TABLE_ATTRIBUTE); } static { SCHEMA_ATTRIBUTES.put(TABLE, SCHEMA_ATTRIBUTE); SCHEMA_ATTRIBUTES.put(ROO_JPA_ENTITY, SCHEMA_ATTRIBUTE); SCHEMA_ATTRIBUTES.put(ROO_JPA_ACTIVE_RECORD, SCHEMA_ATTRIBUTE); } private static Properties tableMappings = null; public static Properties getTableMappings() { return tableMappings; } /** * This probably needs to move to a different class and cache values... * mcm * @param file * @return */ public static Properties openProperties(File file) { Properties props = new Properties(); try { InputStream in = new BufferedInputStream(new FileInputStream(file)); if (file.getName().endsWith(".xml")) { props.loadFromXML(in); } else { props.load(in); } } catch (Exception e) { throw new RuntimeException("unable to process property file", e); } return props; } public static void initAliasMappings(String aliasPropertiesFilename, Database database) { boolean initialized = false; if (aliasPropertiesFilename != null && aliasPropertiesFilename.length() > 1) { File tableNameMapper = new File(aliasPropertiesFilename); if (tableNameMapper != null && tableNameMapper.isFile() && tableNameMapper.canRead() && tableNameMapper.getName() != null) { database.setAliasPropertiesFilename(tableNameMapper.getPath()); tableMappings = openProperties(tableNameMapper); initialized = true; } if (!initialized) { tableMappings = null; database.setAliasPropertiesFilename(null); } } } /** * Locates the type associated with the presented table. * * @param managedEntities a set of database-managed entities to search * (required) * @param table the table to locate (required) * @return the type (if known) or null (if not found) */ public static JavaType findTypeForTable( final Iterable<ClassOrInterfaceTypeDetails> managedEntities, final Table table) { Validate.notNull(managedEntities, "Set of managed entities required"); Validate.notNull(table, "Table required"); return findTypeForTableName(managedEntities, table.getName(), table .getSchema().getName()); } /** * Locates the type associated with the presented table name. * * @param managedEntities a set of database-managed entities to search * (required) * @param tableName the table to locate (required) * @param schemaName the table's schema name * @return the type (if known) or null (if not found) */ public static JavaType findTypeForTableName( final Iterable<ClassOrInterfaceTypeDetails> managedEntities, final String tableName, final String schemaName) { Validate.notNull(managedEntities, "Set of managed entities required"); Validate.notBlank(tableName, "Table name required"); for (final ClassOrInterfaceTypeDetails managedEntity : managedEntities) { final String managedSchemaName = getSchemaName(managedEntity); if (tableName.equals(getTableName(managedEntity)) && (!DbreModelService.NO_SCHEMA_REQUIRED .equals(managedSchemaName) || schemaName .equals(managedSchemaName))) { return managedEntity.getName(); } } return null; } /** * Returns the value of the given attribute of the given annotation on the * given type * * @param <T> the expected annotation value type * @param type the type whose annotations to read (required) * @param annotationType the annotation to read (required) * @param attributeName the annotation attribute to read (required) * @return */ @SuppressWarnings("unchecked") private static <T> T getAnnotationAttribute( final MemberHoldingTypeDetails type, final JavaType annotationType, final JavaSymbolName attributeName) { final AnnotationMetadata typeAnnotation = type .getTypeAnnotation(annotationType); if (typeAnnotation == null) { return null; } final AnnotationAttributeValue<?> attributeValue = typeAnnotation .getAttribute(attributeName); if (attributeValue == null) { return null; } return (T) attributeValue.getValue(); } /** * Reads the given attributes of the given annotations on the given type, * returning the first non-blank one found. * * @param annotatedType the type for which to read the annotations * (required) * @param annotationAttributes the annotation/attribute pairs to read for * that type * @return <code>null</code> if none of those annotations provide a * non-blank schema name */ private static String getFirstNonBlankAttributeValue( final MemberHoldingTypeDetails annotatedType, final Map<JavaType, JavaSymbolName> annotationAttributes) { for (final Entry<JavaType, JavaSymbolName> entry : annotationAttributes .entrySet()) { final String attributeValue = getAnnotationAttribute(annotatedType, entry.getKey(), entry.getValue()); if (StringUtils.isNotBlank(attributeValue)) { return attributeValue; } } return null; } private static String getName(String str, final boolean isField, String tableName, boolean isPrimaryKey) { String dbElementName = str; if (DbreTypeUtils.tableMappings != null) { dbElementName = DbreTypeUtils.tableMappings.getProperty(str); if (dbElementName != null) { if (isField && !isPrimaryKey && (tableName.length() > 0)) { String tableLogicalName = DbreTypeUtils.tableMappings.getProperty(tableName); if (tableLogicalName != null && dbElementName.length() > (tableLogicalName.length() + 1) && dbElementName.startsWith(tableLogicalName)) { String tempFieldName = dbElementName.substring(tableLogicalName.length() + 1); // Don't shorten field to "ID". Hibernate has issues with this and generates SQL that throws an // Oracle ORA-00904 error. // if (!tempFieldName.equals("ID") && !(tableName.endsWith("_TYPE") && tempFieldName.equals("TYPE_ID"))) { if (!tempFieldName.equals("ID") && !tempFieldName.endsWith("TYPE ID")) { // remove the table name portion of the field. (Note: The +1 steps over the space after the table name dbElementName = tempFieldName; } } } } else { if (isField) { String columnAndTable = str + ", " + tableName; if (!missingFromAliasSet.contains(columnAndTable)) { System.out.println("Missing from alias: " + columnAndTable); missingFromAliasSet.add(columnAndTable); } } else { System.out.println(" Database table: " + str + " not found in alias file."); } dbElementName = str; } } final StringBuilder result = new StringBuilder(); boolean isDelimChar = false; for (int i = 0; i < dbElementName.length(); i++) { final char c = dbElementName.charAt(i); if (i == 0) { if (c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || c == '6' || c == '7' || c == '8' || c == '9') { result.append(isField ? "f" : "T"); result.append(c); } else { result.append(isField ? Character.toLowerCase(c) : Character.toUpperCase(c)); } continue; } else if (i > 0 && (c == '_' || c == '-' || c == '\\' || c == '/') || c == '.' || c == ' ') { isDelimChar = true; continue; } if (isDelimChar) { result.append(Character.toUpperCase(c)); isDelimChar = false; } else { if (i > 1 && Character.isLowerCase(dbElementName.charAt(i - 1)) && Character.isUpperCase(c)) { result.append(c); } else { result.append(Character.toLowerCase(c)); } } } if (ReservedWords.RESERVED_JAVA_KEYWORDS.contains(result.toString())) { result.append("1"); } return result.toString(); } /** * Returns the database schema for the given entity. * * @param entityDetails the type to search (required) * @return the schema name (if known) or null (if not found) */ public static String getSchemaName( final MemberHoldingTypeDetails entityDetails) { Validate.notNull(entityDetails, "MemberHoldingTypeDetails type required"); return getFirstNonBlankAttributeValue(entityDetails, SCHEMA_ATTRIBUTES); } /** * Returns the database table for the given entity. * * @param entityDetails the type to search (required) * @return the table (if known) or null (if not found) */ public static String getTableName( final MemberHoldingTypeDetails entityDetails) { Validate.notNull(entityDetails, "MemberHoldingTypeDetails type required"); return getFirstNonBlankAttributeValue(entityDetails, TABLE_ATTRIBUTES); } /** * Returns a field name for a given database table or column name; * * @param name the name of the table or column (required) * @return a String representing the table or column */ public static String suggestFieldName(final String name, final String tableName, final boolean isPreventRename) { Validate.notBlank(name, "Table or column name required"); return getName(name, true, tableName, isPreventRename); } /** * Returns a field name for a given database table or column name; * * @param name the name of the table or column (required) * @return a String representing the table or column */ public static String suggestFieldName(final String name, final String tableName) { Validate.notBlank(name, "Table or column name required"); return suggestFieldName(name, tableName, false); } /** * Returns a field name for a given database table; * * @param table the the table (required) * @return a String representing the table or column. */ public static String suggestFieldName(final Table table) { Validate.notNull(table, "Table required"); return getName(table.getName(), true, "", false); } public static String suggestPackageName(final String str) { final StringBuilder result = new StringBuilder(); final char[] value = str.toCharArray(); for (int i = 0; i < value.length; i++) { final char c = value[i]; if (i == 0 && ('1' == c || '2' == c || '3' == c || '4' == c || '5' == c || '6' == c || '7' == c || '8' == c || '9' == c || '0' == c)) { result.append("p"); result.append(c); } else if ('.' == c || '/' == c || ' ' == c || '*' == c || '>' == c || '<' == c || '!' == c || '@' == c || '%' == c || '^' == c || '?' == c || '(' == c || ')' == c || '~' == c || '`' == c || '{' == c || '}' == c || '[' == c || ']' == c || '|' == c || '\\' == c || '\'' == c || '+' == c || '-' == c) { result.append(""); } else { result.append(Character.toLowerCase(c)); } } return result.toString(); } /** * Returns a JavaType given a table identity. * * @param tableName the table name to convert (required) * @param javaPackage the Java package to use for the type * @return a new JavaType */ public static JavaType suggestTypeNameForNewTable(final String tableName, final JavaPackage javaPackage) { Validate.notBlank(tableName, "Table name required"); final StringBuilder result = new StringBuilder(); if (javaPackage != null && StringUtils.isNotBlank(javaPackage .getFullyQualifiedPackageName())) { result.append(javaPackage.getFullyQualifiedPackageName()); result.append("."); } result.append(getName(tableName, false, "", false)); return new JavaType(result.toString()); } }