/* * gvNIX is an open source tool for rapid application development (RAD). * Copyright (C) 2010 Generalitat Valenciana * * 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/>. */ package org.gvnix.addon.jpa.addon.query; import static org.springframework.roo.model.JdkJavaType.MAP; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.ToStringBuilder; import org.gvnix.addon.jpa.annotations.query.GvNIXJpaQuery; import org.gvnix.support.ItdBuilderHelper; import org.springframework.roo.classpath.PhysicalTypeIdentifierNamingUtils; import org.springframework.roo.classpath.PhysicalTypeMetadata; import org.springframework.roo.classpath.details.FieldMetadata; import org.springframework.roo.classpath.details.FieldMetadataBuilder; import org.springframework.roo.classpath.details.MemberFindingUtils; import org.springframework.roo.classpath.details.MethodMetadata; import org.springframework.roo.classpath.details.MethodMetadataBuilder; import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType; import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue; import org.springframework.roo.classpath.details.annotations.AnnotationMetadata; import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder; import org.springframework.roo.classpath.details.annotations.StringAttributeValue; import org.springframework.roo.classpath.itd.AbstractItdTypeDetailsProvidingMetadataItem; import org.springframework.roo.classpath.itd.InvocableMemberBodyBuilder; import org.springframework.roo.metadata.MetadataIdentificationUtils; import org.springframework.roo.model.DataType; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.model.JavaType; import org.springframework.roo.model.JdkJavaType; import org.springframework.roo.project.LogicalPath; /** * Metadata for {@link GvNIXJpaQuery} annotation.<br> * Generates ITD with the information collected from properties annotated by * {@link GvNIXJpaQuery}. * * @author <a href="http://www.disid.com">DISID Corporation S.L.</a> made for <a * href="http://www.dgti.gva.es">General Directorate for Information * Technologies (DGTI)</a> * @since 1.1.0 */ public class JpaQueryMetadata extends AbstractItdTypeDetailsProvidingMetadataItem { // Types constants private static final JavaType GVNIX_JPA_QUERY = new JavaType( GvNIXJpaQuery.class); private static final JavaType LIST = new JavaType(List.class); private static final JavaType LIST_STRING = new JavaType( LIST.getFullyQualifiedTypeName(), 0, DataType.TYPE, null, Arrays.asList(JavaType.STRING)); private static final JavaType MAP_STRING_LIST_STRING = new JavaType( MAP.getFullyQualifiedTypeName(), 0, DataType.TYPE, null, Arrays.asList(JavaType.STRING, LIST_STRING)); private static final JavaType HASHMAP = new JavaType(HashMap.class); private static final JavaType HASHMAP_STRING_LIST_STRING = new JavaType( HASHMAP.getFullyQualifiedTypeName(), 0, DataType.TYPE, null, Arrays.asList(JavaType.STRING, LIST_STRING)); private static final JavaType COLLECTIONS = new JavaType(Collections.class); private static final JavaType ARRAYS = new JavaType(Arrays.class); public static final JavaSymbolName GET_FILTERBY_DEFINITION = new JavaSymbolName( "getFilterByAssociations"); public static final JavaSymbolName GET_ORDERBY_DEFINITION = new JavaSymbolName( "getOrderByAssociations"); // Constants private static final String PROVIDES_TYPE_STRING = JpaQueryMetadata.class .getName(); private static final String PROVIDES_TYPE = MetadataIdentificationUtils .create(PROVIDES_TYPE_STRING); private static final Comparator<? super FieldMetadata> FIELD_COMPARATOR = new Comparator<FieldMetadata>() { @Override public int compare(FieldMetadata o1, FieldMetadata o2) { return o1.getFieldName().getSymbolName() .compareTo(o2.getFieldName().getSymbolName()); } }; public static final String getMetadataIdentiferType() { return PROVIDES_TYPE; } public static final String createIdentifier(JavaType javaType, LogicalPath path) { return PhysicalTypeIdentifierNamingUtils.createIdentifier( PROVIDES_TYPE_STRING, javaType, path); } public static final JavaType getJavaType(String metadataIdentificationString) { return PhysicalTypeIdentifierNamingUtils.getJavaType( PROVIDES_TYPE_STRING, metadataIdentificationString); } public static final LogicalPath getPath(String metadataIdentificationString) { return PhysicalTypeIdentifierNamingUtils.getPath(PROVIDES_TYPE_STRING, metadataIdentificationString); } public static boolean isValid(String metadataIdentificationString) { return PhysicalTypeIdentifierNamingUtils.isValid(PROVIDES_TYPE_STRING, metadataIdentificationString); } /** * Field which holds filterBy definition */ private FieldMetadata filterByField; /** * Field which holds orderBy definition */ private FieldMetadata orderByField; /** * ITD generation helper */ private final ItdBuilderHelper helper; public JpaQueryMetadata(String identifier, JavaType aspectName, PhysicalTypeMetadata governorPhysicalTypeMetadata) { super(identifier, aspectName, governorPhysicalTypeMetadata); Validate.isTrue(isValid(identifier), "Metadata identification string '" + identifier + "' does not appear to be a valid"); this.helper = new ItdBuilderHelper(this, governorPhysicalTypeMetadata, builder.getImportRegistrationResolver()); // Prepare fields Map ordered by name Map<FieldMetadata, AnnotationMetadata> fieldsToProcess = newFieldsMap(); // Locate field with @GvNIXJpaFilterProperty AnnotationMetadata annotation; for (FieldMetadata field : governorTypeDetails.getDeclaredFields()) { annotation = field.getAnnotation(GVNIX_JPA_QUERY); if (annotation != null) { fieldsToProcess.put(field, annotation); } } // No fields to process if (fieldsToProcess.isEmpty()) { return; } // Adding static field to store filterBy definition builder.addField(getFilterByField()); // Adding method to get FilterBy builder.addMethod(getFilterByMethod(fieldsToProcess)); // Adding static field to store orderBy definition builder.addField(getOrderByField()); // Adding method to get FilterBy builder.addMethod(getOrderByMethod(fieldsToProcess)); // Create a representation of the desired output ITD itdTypeDetails = builder.build(); } /** * @return a new instance of a (ordered) map to store filed */ private Map<FieldMetadata, AnnotationMetadata> newFieldsMap() { Map<FieldMetadata, AnnotationMetadata> fieldsToProcess = new TreeMap<FieldMetadata, AnnotationMetadata>( FIELD_COMPARATOR); return fieldsToProcess; } /** * Create metadata for a field definition. * * @return a FieldMetadata object */ private FieldMetadata getFilterByField() { if (filterByField == null) { JavaSymbolName curName = new JavaSymbolName("filterByAssociations"); String initializer = "null"; int modifier = Modifier.PUBLIC + Modifier.STATIC; filterByField = getOrCreateField(curName, MAP_STRING_LIST_STRING, initializer, modifier, null); } return filterByField; } /** * Create metadata for a field definition. * * @return a FieldMetadata object */ private FieldMetadata getOrderByField() { if (orderByField == null) { JavaSymbolName curName = new JavaSymbolName("orderyByAssociations"); String initializer = "null"; int modifier = Modifier.PUBLIC + Modifier.STATIC; orderByField = getOrCreateField(curName, MAP_STRING_LIST_STRING, initializer, modifier, null); } return orderByField; } /** * Gets or creates a field based on parameters.<br> * First try to get a suitable field (by name and type). If not found create * a new one (adding a counter to name if it's needed) * * @param fielName * @param fieldType * @param initializer (String representation) * @param modifier See {@link Modifier} * @param annotations optional (can be null) * @return */ private FieldMetadata getOrCreateField(JavaSymbolName fielName, JavaType fieldType, String initializer, int modifier, List<AnnotationMetadataBuilder> annotations) { JavaSymbolName curName = fielName; // Check if field exist FieldMetadata currentField = governorTypeDetails .getDeclaredField(curName); if (currentField != null) { if (!currentField.getFieldType().equals(fieldType)) { // No compatible field: look for new name currentField = null; JavaSymbolName newName = curName; int i = 1; while (governorTypeDetails.getDeclaredField(newName) != null) { newName = new JavaSymbolName(curName.getSymbolName() .concat(StringUtils.repeat('_', i))); i++; } curName = newName; } } if (currentField == null) { // create field if (annotations == null) { annotations = new ArrayList<AnnotationMetadataBuilder>(0); } // Using the FieldMetadataBuilder to create the field // definition. final FieldMetadataBuilder fieldBuilder = new FieldMetadataBuilder( getId(), modifier, annotations, curName, // Field fieldType); // Field type fieldBuilder.setFieldInitializer(initializer); currentField = fieldBuilder.build(); // Build and return a // FieldMetadata // instance } return currentField; } /** * Generate method to get the filterBy information * * @param fieldsToProcess * @return */ private MethodMetadata getFilterByMethod( Map<FieldMetadata, AnnotationMetadata> fieldsToProcess) { // public Map<String,List<String>> getJpaQueryFilterBy() { // Check if a method with the same signature already exists in the // target type final MethodMetadata method = methodExists(GET_FILTERBY_DEFINITION, new ArrayList<AnnotatedJavaType>()); if (method != null) { // If it already exists, just return the method and omit its // generation via the ITD return method; } // Define method annotations (none in this case) List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>(); // Define method throws types (none in this case) List<JavaType> throwsTypes = new ArrayList<JavaType>(); // Define method parameter types (none in this case) List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>(); // Define method parameter names (none in this case) List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>(); // Create the method body InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); buildGetFilterByDefinitionMethodBody(bodyBuilder, fieldsToProcess); // Use the MethodMetadataBuilder for easy creation of MethodMetadata MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder( getId(), Modifier.PUBLIC + Modifier.STATIC, GET_FILTERBY_DEFINITION, MAP_STRING_LIST_STRING, parameterTypes, parameterNames, bodyBuilder); methodBuilder.setAnnotations(annotations); methodBuilder.setThrowsTypes(throwsTypes); return methodBuilder.build(); // Build and return a MethodMetadata // instance } /** * Generate method to get the orderBy information * * @param fieldsToProcess * @return */ private MethodMetadata getOrderByMethod( Map<FieldMetadata, AnnotationMetadata> fieldsToProcess) { // public Map<String,List<String>> getJpaQueryOrderBy() { // Check if a method with the same signature already exists in the // target type final MethodMetadata method = methodExists(GET_ORDERBY_DEFINITION, new ArrayList<AnnotatedJavaType>()); if (method != null) { // If it already exists, just return the method and omit its // generation via the ITD return method; } // Define method annotations (none in this case) List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>(); // Define method throws types (none in this case) List<JavaType> throwsTypes = new ArrayList<JavaType>(); // Define method parameter types (none in this case) List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>(); // Define method parameter names (none in this case) List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>(); // Create the method body InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); buildGetOrderByDefinitionMethodBody(bodyBuilder, fieldsToProcess); // Use the MethodMetadataBuilder for easy creation of MethodMetadata MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder( getId(), Modifier.PUBLIC + Modifier.STATIC, GET_ORDERBY_DEFINITION, MAP_STRING_LIST_STRING, parameterTypes, parameterNames, bodyBuilder); methodBuilder.setAnnotations(annotations); methodBuilder.setThrowsTypes(throwsTypes); return methodBuilder.build(); // Build and return a MethodMetadata // instance } /** * Build body for get-filter-by information method * * @param bodyBuilder * @param fieldsToProcess */ private void buildGetFilterByDefinitionMethodBody( InvocableMemberBodyBuilder bodyBuilder, Map<FieldMetadata, AnnotationMetadata> fieldsToProcess) { buildDefinitionMethodBody(bodyBuilder, getRelationFields(fieldsToProcess), getFilterByField(), "filterBy"); } /** * Return a new map with fields from <code>fields</code> which are relation * properties.<br> * Currently just discard primitives an java.lang types. * * @param fields * @return */ private Map<FieldMetadata, AnnotationMetadata> getRelationFields( Map<FieldMetadata, AnnotationMetadata> fields) { Map<FieldMetadata, AnnotationMetadata> result = newFieldsMap(); JavaType type; for (Entry<FieldMetadata, AnnotationMetadata> entry : fields.entrySet()) { type = entry.getKey().getFieldType(); if (JdkJavaType.isPartOfJavaLang(type)) { continue; } else if (type.isPrimitive()) { continue; } // TODO filter more properties result.put(entry.getKey(), entry.getValue()); } return result; } /** * Build body for get-order-by information method * * @param bodyBuilder * @param fieldsToProcess */ private void buildGetOrderByDefinitionMethodBody( InvocableMemberBodyBuilder bodyBuilder, Map<FieldMetadata, AnnotationMetadata> fieldsToProcess) { buildDefinitionMethodBody(bodyBuilder, getRelationFields(fieldsToProcess), getOrderByField(), "orderBy"); } /** * Build method to get filter by or orderBy information * * @param bodyBuilder * @param fieldsToProcess * @param field to store result * @param annotationAttribute name to get values to include */ private void buildDefinitionMethodBody( InvocableMemberBodyBuilder bodyBuilder, Map<FieldMetadata, AnnotationMetadata> fieldsToProcess, FieldMetadata field, String annotationAttribute) { String fieldName = field.getFieldName().getSymbolName(); // if(filterByAssociations == null) { bodyBuilder.appendFormalLine(String.format("if (%s == null) {", fieldName)); bodyBuilder.indent(); // Map<String, List<String>> tmp = new HashMap<String, List<String>>(); bodyBuilder.appendFormalLine(String.format("%s tmp = new %s();", helper.getFinalTypeName(MAP_STRING_LIST_STRING), helper.getFinalTypeName(HASHMAP_STRING_LIST_STRING))); for (Entry<FieldMetadata, AnnotationMetadata> entry : fieldsToProcess .entrySet()) { List<String> properties = new ArrayList<String>(); AnnotationAttributeValue<?> curValue = entry.getValue() .getAttribute(annotationAttribute); if (curValue == null) { continue; } if (curValue instanceof StringAttributeValue) { properties.add("\"".concat((String) curValue.getValue()) .concat("\"")); } else { for (StringAttributeValue property : (List<StringAttributeValue>) curValue .getValue()) { properties.add("\"".concat(property.getValue()) .concat("\"")); } } bodyBuilder .appendFormalLine(String .format("tmp.put(\"%s\", %s.unmodifiableList(%s.asList( new String[] {%s} )));", entry.getKey().getFieldName() .getSymbolName(), helper.getFinalTypeName(COLLECTIONS), helper.getFinalTypeName(ARRAYS), StringUtils.join(properties, ","))); } // filterByAssociations = Collections.unmodifiableMap(tmp); bodyBuilder.appendFormalLine(String.format( "%s = %s.unmodifiableMap(tmp);", fieldName, helper.getFinalTypeName(COLLECTIONS))); // } bodyBuilder.indentRemove(); bodyBuilder.appendFormalLine("}"); // return Collections.unmodifiableMap(filterByAssociations); bodyBuilder.appendFormalLine(String.format("return %s;", fieldName)); } private MethodMetadata methodExists(JavaSymbolName methodName, List<AnnotatedJavaType> paramTypes) { return MemberFindingUtils.getDeclaredMethod(governorTypeDetails, methodName, AnnotatedJavaType.convertFromAnnotatedJavaTypes(paramTypes)); } public String toString() { final ToStringBuilder builder = new ToStringBuilder(this); builder.append("identifier", getId()); builder.append("valid", valid); builder.append("aspectName", aspectName); builder.append("destinationType", destination); builder.append("governor", governorPhysicalTypeMetadata.getId()); builder.append("itdTypeDetails", itdTypeDetails); return builder.toString(); } /** * Gets symbol name of method to get FilterBy definitions * * @return */ public JavaSymbolName getFilterByMethodName() { return GET_FILTERBY_DEFINITION; } /** * Gets symbol name of method to get OrderBy definitions * * @return */ public JavaSymbolName getOrderByMethodName() { return GET_ORDERBY_DEFINITION; } }