package org.springframework.roo.addon.javabean; import static org.springframework.roo.model.GoogleJavaType.GAE_DATASTORE_KEY; import static org.springframework.roo.model.GoogleJavaType.GAE_DATASTORE_KEY_FACTORY; import static org.springframework.roo.model.JavaType.LONG_OBJECT; import static org.springframework.roo.model.JdkJavaType.ARRAY_LIST; import static org.springframework.roo.model.JdkJavaType.HASH_SET; import static org.springframework.roo.model.JdkJavaType.LIST; import static org.springframework.roo.model.JdkJavaType.SET; import static org.springframework.roo.model.JpaJavaType.MANY_TO_MANY; import static org.springframework.roo.model.JpaJavaType.MANY_TO_ONE; import static org.springframework.roo.model.JpaJavaType.ONE_TO_MANY; import static org.springframework.roo.model.JpaJavaType.ONE_TO_ONE; import static org.springframework.roo.model.JpaJavaType.TRANSIENT; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.ToStringBuilder; import org.springframework.roo.classpath.PhysicalTypeIdentifierNamingUtils; import org.springframework.roo.classpath.PhysicalTypeMetadata; import org.springframework.roo.classpath.details.BeanInfoUtils; import org.springframework.roo.classpath.details.DeclaredFieldAnnotationDetails; import org.springframework.roo.classpath.details.FieldMetadata; import org.springframework.roo.classpath.details.FieldMetadataBuilder; import org.springframework.roo.classpath.details.MethodMetadataBuilder; import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType; import org.springframework.roo.classpath.details.annotations.AnnotationMetadata; import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder; 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.project.LogicalPath; /** * Metadata for {@link RooJavaBean}. * * @author Ben Alex * @author Alan Stewart * @since 1.0 */ public class JavaBeanMetadata extends AbstractItdTypeDetailsProvidingMetadataItem { private static final String PROVIDES_TYPE_STRING = JavaBeanMetadata.class .getName(); private static final String PROVIDES_TYPE = MetadataIdentificationUtils .create(PROVIDES_TYPE_STRING); public static String createIdentifier(final JavaType javaType, final LogicalPath path) { return PhysicalTypeIdentifierNamingUtils.createIdentifier( PROVIDES_TYPE_STRING, javaType, path); } public static JavaType getJavaType(final String metadataIdentificationString) { return PhysicalTypeIdentifierNamingUtils.getJavaType( PROVIDES_TYPE_STRING, metadataIdentificationString); } public static String getMetadataIdentiferType() { return PROVIDES_TYPE; } public static LogicalPath getPath(final String metadataIdentificationString) { return PhysicalTypeIdentifierNamingUtils.getPath(PROVIDES_TYPE_STRING, metadataIdentificationString); } public static boolean isValid(final String metadataIdentificationString) { return PhysicalTypeIdentifierNamingUtils.isValid(PROVIDES_TYPE_STRING, metadataIdentificationString); } private JavaBeanAnnotationValues annotationValues; private Map<FieldMetadata, JavaSymbolName> declaredFields; /** * Constructor * * @param identifier the ID of the metadata to create (must be a valid ID) * @param aspectName the name of the ITD to be created (required) * @param governorPhysicalTypeMetadata the governor (required) * @param annotationValues the values of the {@link RooJavaBean} annotation * (required) * @param declaredFields the fields declared in the governor (required, can * be empty) */ public JavaBeanMetadata(final String identifier, final JavaType aspectName, final PhysicalTypeMetadata governorPhysicalTypeMetadata, final JavaBeanAnnotationValues annotationValues, final Map<FieldMetadata, JavaSymbolName> declaredFields) { super(identifier, aspectName, governorPhysicalTypeMetadata); Validate.isTrue(isValid(identifier), "Metadata identification string '" + identifier + "' does not appear to be a valid"); Validate.notNull(annotationValues, "Annotation values required"); Validate.notNull(declaredFields, "Declared fields required"); if (!isValid()) { return; } if (declaredFields.isEmpty()) { return; // N.B. the MD is still valid, just empty } this.annotationValues = annotationValues; this.declaredFields = declaredFields; // Add getters and setters for (final Entry<FieldMetadata, JavaSymbolName> entry : declaredFields .entrySet()) { final FieldMetadata field = entry.getKey(); final MethodMetadataBuilder accessorMethod = getDeclaredGetter(field); final MethodMetadataBuilder mutatorMethod = getDeclaredSetter(field); // Check to see if GAE is interested if (entry.getValue() != null) { JavaSymbolName hiddenIdFieldName; if (field.getFieldType().isCommonCollectionType()) { hiddenIdFieldName = governorTypeDetails .getUniqueFieldName(field.getFieldName() .getSymbolName() + "Keys"); builder.getImportRegistrationResolver().addImport( GAE_DATASTORE_KEY_FACTORY); builder.addField(getMultipleEntityIdField(hiddenIdFieldName)); } else { hiddenIdFieldName = governorTypeDetails .getUniqueFieldName(field.getFieldName() .getSymbolName() + "Id"); builder.addField(getSingularEntityIdField(hiddenIdFieldName)); } processGaeAnnotations(field); accessorMethod.setBodyBuilder(getGaeAccessorBody(field, hiddenIdFieldName)); mutatorMethod.setBodyBuilder(getGaeMutatorBody(field, hiddenIdFieldName)); } builder.addMethod(accessorMethod); builder.addMethod(mutatorMethod); } // Create a representation of the desired output ITD itdTypeDetails = builder.build(); } /** * Obtains the specific accessor method that is either contained within the * normal Java compilation unit or will be introduced by this add-on via an * ITD. * * @param field that already exists on the type either directly or via * introduction (required; must be declared by this type to be * located) * @return the method corresponding to an accessor, or null if not found */ private MethodMetadataBuilder getDeclaredGetter(final FieldMetadata field) { Validate.notNull(field, "Field required"); // Compute the mutator method name final JavaSymbolName methodName = BeanInfoUtils .getAccessorMethodName(field); // See if the type itself declared the accessor if (governorHasMethod(methodName)) { return null; } // Decide whether we need to produce the accessor method (see ROO-619 // for reason we allow a getter for a final field) if (annotationValues.isGettersByDefault() && !Modifier.isTransient(field.getModifier()) && !Modifier.isStatic(field.getModifier())) { final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); bodyBuilder.appendFormalLine("return this." + field.getFieldName().getSymbolName() + ";"); return new MethodMetadataBuilder(getId(), Modifier.PUBLIC, methodName, field.getFieldType(), bodyBuilder); } return null; } /** * Obtains the specific mutator method that is either contained within the * normal Java compilation unit or will be introduced by this add-on via an * ITD. * * @param field that already exists on the type either directly or via * introduction (required; must be declared by this type to be * located) * @return the method corresponding to a mutator, or null if not found */ private MethodMetadataBuilder getDeclaredSetter(final FieldMetadata field) { Validate.notNull(field, "Field required"); // Compute the mutator method name final JavaSymbolName methodName = BeanInfoUtils .getMutatorMethodName(field); // Compute the mutator method parameters final JavaType parameterType = field.getFieldType(); // See if the type itself declared the mutator if (governorHasMethod(methodName, parameterType)) { return null; } // Compute the mutator method parameter names final List<JavaSymbolName> parameterNames = Arrays.asList(field .getFieldName()); // Decide whether we need to produce the mutator method (disallowed for // final fields as per ROO-36) if (annotationValues.isSettersByDefault() && !Modifier.isTransient(field.getModifier()) && !Modifier.isStatic(field.getModifier()) && !Modifier.isFinal(field.getModifier())) { final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); bodyBuilder.appendFormalLine("this." + field.getFieldName().getSymbolName() + " = " + field.getFieldName().getSymbolName() + ";"); return new MethodMetadataBuilder(getId(), Modifier.PUBLIC, methodName, JavaType.VOID_PRIMITIVE, AnnotatedJavaType.convertFromJavaTypes(parameterType), parameterNames, bodyBuilder); } return null; } private InvocableMemberBodyBuilder getEntityCollectionAccessorBody( final FieldMetadata field, final JavaSymbolName entityIdsFieldName) { final String entityCollectionName = field.getFieldName() .getSymbolName(); final String entityIdsName = entityIdsFieldName.getSymbolName(); final String localEnitiesName = "local" + StringUtils.capitalize(entityCollectionName); final JavaType collectionElementType = field.getFieldType() .getParameters().get(0); final String simpleCollectionElementTypeName = collectionElementType .getSimpleTypeName(); JavaType collectionType = field.getFieldType(); builder.getImportRegistrationResolver().addImport(collectionType); final String collectionName = field .getFieldType() .getNameIncludingTypeParameters() .replace( field.getFieldType().getPackage() .getFullyQualifiedPackageName() + ".", ""); String instantiableCollection = collectionName; // GAE only supports java.util.List and java.util.Set collections and we // need a concrete implementation of either. if (collectionType.getFullyQualifiedTypeName().equals( LIST.getFullyQualifiedTypeName())) { collectionType = new JavaType( ARRAY_LIST.getFullyQualifiedTypeName(), 0, DataType.TYPE, null, collectionType.getParameters()); instantiableCollection = collectionType .getNameIncludingTypeParameters().replace( collectionType.getPackage() .getFullyQualifiedPackageName() + ".", ""); } else if (collectionType.getFullyQualifiedTypeName().equals( SET.getFullyQualifiedTypeName())) { collectionType = new JavaType(HASH_SET.getFullyQualifiedTypeName(), 0, DataType.TYPE, null, collectionType.getParameters()); instantiableCollection = collectionType .getNameIncludingTypeParameters().replace( collectionType.getPackage() .getFullyQualifiedPackageName() + ".", ""); } builder.getImportRegistrationResolver().addImport(collectionType); final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); bodyBuilder.appendFormalLine(collectionName + " " + localEnitiesName + " = new " + instantiableCollection + "();"); bodyBuilder.appendFormalLine("for (Key key : " + entityIdsName + ") {"); bodyBuilder.indent(); bodyBuilder.appendFormalLine(simpleCollectionElementTypeName + " entity = " + simpleCollectionElementTypeName + ".find" + simpleCollectionElementTypeName + "(key.getId());"); bodyBuilder.appendFormalLine("if (entity != null) {"); bodyBuilder.indent(); bodyBuilder.appendFormalLine(localEnitiesName + ".add(entity);"); bodyBuilder.indentRemove(); bodyBuilder.appendFormalLine("}"); bodyBuilder.indentRemove(); bodyBuilder.appendFormalLine("}"); bodyBuilder.appendFormalLine("this." + entityCollectionName + " = " + localEnitiesName + ";"); bodyBuilder.appendFormalLine("return " + localEnitiesName + ";"); return bodyBuilder; } private InvocableMemberBodyBuilder getEntityCollectionMutatorBody( final FieldMetadata field, final JavaSymbolName entityIdsFieldName) { final String entityCollectionName = field.getFieldName() .getSymbolName(); final String entityIdsName = entityIdsFieldName.getSymbolName(); final JavaType collectionElementType = field.getFieldType() .getParameters().get(0); final String localEnitiesName = "local" + StringUtils.capitalize(entityCollectionName); JavaType collectionType = field.getFieldType(); builder.getImportRegistrationResolver().addImport(collectionType); final String collectionName = field .getFieldType() .getNameIncludingTypeParameters() .replace( field.getFieldType().getPackage() .getFullyQualifiedPackageName() + ".", ""); String instantiableCollection = collectionName; if (collectionType.getFullyQualifiedTypeName().equals( LIST.getFullyQualifiedTypeName())) { collectionType = new JavaType( ARRAY_LIST.getFullyQualifiedTypeName(), 0, DataType.TYPE, null, collectionType.getParameters()); instantiableCollection = collectionType .getNameIncludingTypeParameters().replace( collectionType.getPackage() .getFullyQualifiedPackageName() + ".", ""); } else if (collectionType.getFullyQualifiedTypeName().equals( SET.getFullyQualifiedTypeName())) { collectionType = new JavaType(HASH_SET.getFullyQualifiedTypeName(), 0, DataType.TYPE, null, collectionType.getParameters()); instantiableCollection = collectionType .getNameIncludingTypeParameters().replace( collectionType.getPackage() .getFullyQualifiedPackageName() + ".", ""); } builder.getImportRegistrationResolver().addImports(collectionType, LIST, ARRAY_LIST); final String identifierMethodName = getIdentifierMethodName(field) .getSymbolName(); final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); bodyBuilder.appendFormalLine(collectionName + " " + localEnitiesName + " = new " + instantiableCollection + "();"); bodyBuilder .appendFormalLine("List<Long> longIds = new ArrayList<Long>();"); bodyBuilder.appendFormalLine("for (Key key : " + entityIdsName + ") {"); bodyBuilder.indent(); bodyBuilder.appendFormalLine("if (!longIds.contains(key.getId())) {"); bodyBuilder.indent(); bodyBuilder.appendFormalLine("longIds.add(key.getId());"); bodyBuilder.indentRemove(); bodyBuilder.appendFormalLine("}"); bodyBuilder.indentRemove(); bodyBuilder.appendFormalLine("}"); bodyBuilder.appendFormalLine("for (" + collectionElementType.getSimpleTypeName() + " entity : " + entityCollectionName + ") {"); bodyBuilder.indent(); bodyBuilder.appendFormalLine("if (!longIds.contains(entity." + identifierMethodName + "())) {"); bodyBuilder.indent(); bodyBuilder.appendFormalLine("longIds.add(entity." + identifierMethodName + "());"); bodyBuilder.appendFormalLine(entityIdsName + ".add(KeyFactory.createKey(" + collectionElementType.getSimpleTypeName() + ".class.getName(), entity." + identifierMethodName + "()));"); bodyBuilder.indentRemove(); bodyBuilder.appendFormalLine("}"); bodyBuilder.appendFormalLine(localEnitiesName + ".add(entity);"); bodyBuilder.indentRemove(); bodyBuilder.appendFormalLine("}"); bodyBuilder.appendFormalLine("this." + entityCollectionName + " = " + localEnitiesName + ";"); return bodyBuilder; } private InvocableMemberBodyBuilder getGaeAccessorBody( final FieldMetadata field, final JavaSymbolName hiddenIdFieldName) { return field.getFieldType().isCommonCollectionType() ? getEntityCollectionAccessorBody( field, hiddenIdFieldName) : getSingularEntityAccessor(field, hiddenIdFieldName); } private InvocableMemberBodyBuilder getGaeMutatorBody( final FieldMetadata field, final JavaSymbolName hiddenIdFieldName) { return field.getFieldType().isCommonCollectionType() ? getEntityCollectionMutatorBody( field, hiddenIdFieldName) : getSingularEntityMutator(field, hiddenIdFieldName); } private JavaSymbolName getIdentifierMethodName(final FieldMetadata field) { final JavaSymbolName identifierAccessorMethodName = declaredFields .get(field); return identifierAccessorMethodName != null ? identifierAccessorMethodName : new JavaSymbolName("getId"); } private FieldMetadataBuilder getMultipleEntityIdField( final JavaSymbolName fieldName) { builder.getImportRegistrationResolver().addImport(HASH_SET); return new FieldMetadataBuilder(getId(), Modifier.PRIVATE, fieldName, new JavaType(SET.getFullyQualifiedTypeName(), 0, DataType.TYPE, null, Collections.singletonList(GAE_DATASTORE_KEY)), "new HashSet<Key>()"); } private InvocableMemberBodyBuilder getSingularEntityAccessor( final FieldMetadata field, final JavaSymbolName hiddenIdFieldName) { final String entityName = field.getFieldName().getSymbolName(); final String entityIdName = hiddenIdFieldName.getSymbolName(); final String simpleFieldTypeName = field.getFieldType() .getSimpleTypeName(); final String identifierMethodName = getIdentifierMethodName(field) .getSymbolName(); final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); bodyBuilder .appendFormalLine("if (this." + entityIdName + " != null) {"); bodyBuilder.indent(); bodyBuilder.appendFormalLine("if (this." + entityName + " == null || this." + entityName + "." + identifierMethodName + "() != this." + entityIdName + ") {"); bodyBuilder.indent(); bodyBuilder.appendFormalLine("this." + entityName + " = " + simpleFieldTypeName + ".find" + simpleFieldTypeName + "(this." + entityIdName + ");"); bodyBuilder.indentRemove(); bodyBuilder.appendFormalLine("}"); bodyBuilder.indentRemove(); bodyBuilder.appendFormalLine("} else {"); bodyBuilder.indent(); bodyBuilder.appendFormalLine("this." + entityName + " = null;"); bodyBuilder.indentRemove(); bodyBuilder.appendFormalLine("}"); bodyBuilder.appendFormalLine("return this." + entityName + ";"); return bodyBuilder; } private FieldMetadataBuilder getSingularEntityIdField( final JavaSymbolName fieldName) { return new FieldMetadataBuilder(getId(), Modifier.PRIVATE, fieldName, LONG_OBJECT, null); } private InvocableMemberBodyBuilder getSingularEntityMutator( final FieldMetadata field, final JavaSymbolName hiddenIdFieldName) { final String entityName = field.getFieldName().getSymbolName(); final String entityIdName = hiddenIdFieldName.getSymbolName(); final String identifierMethodName = getIdentifierMethodName(field) .getSymbolName(); final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); bodyBuilder.appendFormalLine("if (" + entityName + " != null) {"); bodyBuilder.indent(); bodyBuilder.appendFormalLine("if (" + entityName + "." + identifierMethodName + " () == null) {"); bodyBuilder.indent(); bodyBuilder.appendFormalLine(entityName + ".persist();"); bodyBuilder.indentRemove(); bodyBuilder.appendFormalLine("}"); bodyBuilder.appendFormalLine("this." + entityIdName + " = " + entityName + "." + identifierMethodName + "();"); bodyBuilder.indentRemove(); bodyBuilder.appendFormalLine("} else {"); bodyBuilder.indent(); bodyBuilder.appendFormalLine("this." + entityIdName + " = null;"); bodyBuilder.indentRemove(); bodyBuilder.appendFormalLine("}"); bodyBuilder.appendFormalLine("this." + entityName + " = " + entityName + ";"); return bodyBuilder; } private void processGaeAnnotations(final FieldMetadata field) { for (final AnnotationMetadata annotation : field.getAnnotations()) { if (annotation.getAnnotationType().equals(ONE_TO_ONE) || annotation.getAnnotationType().equals(MANY_TO_ONE) || annotation.getAnnotationType().equals(ONE_TO_MANY) || annotation.getAnnotationType().equals(MANY_TO_MANY)) { builder.addFieldAnnotation(new DeclaredFieldAnnotationDetails( field, new AnnotationMetadataBuilder(annotation .getAnnotationType()).build(), true)); builder.addFieldAnnotation(new DeclaredFieldAnnotationDetails( field, new AnnotationMetadataBuilder(TRANSIENT).build())); break; } } } @Override 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(); } }