package org.springframework.roo.addon.jpa.addon.identifier; import static org.springframework.roo.classpath.customdata.CustomDataKeys.IDENTIFIER_TYPE; import static org.springframework.roo.model.JavaType.LONG_OBJECT; import static org.springframework.roo.model.JdkJavaType.BIG_DECIMAL; import static org.springframework.roo.model.JdkJavaType.DATE; import static org.springframework.roo.model.JpaJavaType.COLUMN; import static org.springframework.roo.model.JpaJavaType.EMBEDDABLE; import static org.springframework.roo.model.JpaJavaType.TEMPORAL; import static org.springframework.roo.model.JpaJavaType.TEMPORAL_TYPE; import static org.springframework.roo.model.JpaJavaType.TRANSIENT; import static org.springframework.roo.model.SpringJavaType.DATE_TIME_FORMAT; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.springframework.roo.addon.jpa.annotations.identifier.RooIdentifier; 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.ConstructorMetadata; import org.springframework.roo.classpath.details.ConstructorMetadataBuilder; import org.springframework.roo.classpath.details.FieldMetadata; import org.springframework.roo.classpath.details.FieldMetadataBuilder; 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.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.EnumDetails; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.model.JavaType; import org.springframework.roo.project.LogicalPath; /** * Metadata for {@link RooIdentifier}. * * @author Alan Stewart * @since 1.1 */ public class IdentifierMetadata extends AbstractItdTypeDetailsProvidingMetadataItem { private static final String PROVIDES_TYPE_STRING = IdentifierMetadata.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 getMetadataIdentifierType() { 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); } // See {@link IdentifierService} for further information (populated via // {@link IdentifierMetadataProviderImpl}); may be null private List<Identifier> identifierServiceResult; private boolean publicNoArgConstructor; public IdentifierMetadata(final String identifier, final JavaType aspectName, final PhysicalTypeMetadata governorPhysicalTypeMetadata, final IdentifierAnnotationValues annotationValues, final List<Identifier> identifierServiceResult) { 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"); if (!isValid()) { return; } this.identifierServiceResult = identifierServiceResult; // Add @Embeddable annotation builder.addAnnotation(getEmbeddableAnnotation()); // Add declared fields and accessors and mutators final List<FieldMetadataBuilder> fields = getFieldBuilders(); for (final FieldMetadataBuilder field : fields) { builder.addField(field); } // Obtain a parameterised constructor builder.addConstructor(getParameterizedConstructor(fields)); // Obtain a no-arg constructor, if one is appropriate to provide if (annotationValues.isNoArgConstructor()) { builder.addConstructor(getNoArgConstructor()); } if (annotationValues.isGettersByDefault()) { for (final MethodMetadataBuilder accessor : getAccessors(fields)) { builder.addMethod(accessor); } } if (annotationValues.isSettersByDefault()) { for (final MethodMetadataBuilder mutator : getMutators(fields)) { builder.addMethod(mutator); } } // Add custom data tag for Roo Identifier type builder.putCustomData(IDENTIFIER_TYPE, null); // Create a representation of the desired output ITD buildItd(); } /** * Locates the accessor methods. * <p> * If {@link #getFieldBuilders()} returns fields created by this ITD, public * accessors will automatically be produced in the declaring class. * * @param fields * @return the accessors (never returns null) */ private List<MethodMetadataBuilder> getAccessors(final List<FieldMetadataBuilder> fields) { final List<MethodMetadataBuilder> accessors = new ArrayList<MethodMetadataBuilder>(); // Compute the names of the accessors that will be produced for (final FieldMetadataBuilder field : fields) { final JavaSymbolName requiredAccessorName = BeanInfoUtils.getAccessorMethodName(field.getFieldName(), field.getFieldType()); final MethodMetadata accessor = getGovernorMethod(requiredAccessorName); if (accessor == null) { accessors.add(getAccessorMethod(field.getFieldName(), field.getFieldType())); } else { Validate.isTrue(Modifier.isPublic(accessor.getModifier()), "User provided field but failed to provide a public '%s()' method in '%s'", requiredAccessorName.getSymbolName(), destination.getFullyQualifiedTypeName()); accessors.add(new MethodMetadataBuilder(accessor)); } } return accessors; } private AnnotationMetadataBuilder getColumnBuilder(final Identifier identifier) { final AnnotationMetadataBuilder columnBuilder = new AnnotationMetadataBuilder(COLUMN); columnBuilder.addStringAttribute("name", identifier.getColumnName()); if (StringUtils.isNotBlank(identifier.getColumnDefinition())) { columnBuilder.addStringAttribute("columnDefinition", identifier.getColumnDefinition()); } columnBuilder.addBooleanAttribute("nullable", false); // Add length attribute for Strings if (identifier.getColumnSize() < 4000 && identifier.getFieldType().equals(JavaType.STRING)) { columnBuilder.addIntegerAttribute("length", identifier.getColumnSize()); } // Add precision and scale attributes for numeric fields if (identifier.getScale() > 0 && (identifier.getFieldType().equals(JavaType.DOUBLE_OBJECT) || identifier.getFieldType().equals(JavaType.DOUBLE_PRIMITIVE) || identifier .getFieldType().equals(BIG_DECIMAL))) { columnBuilder.addIntegerAttribute("precision", identifier.getColumnSize()); columnBuilder.addIntegerAttribute("scale", identifier.getScale()); } return columnBuilder; } private AnnotationMetadata getEmbeddableAnnotation() { if (governorTypeDetails.getAnnotation(EMBEDDABLE) != null) { return null; } final AnnotationMetadataBuilder annotationBuilder = new AnnotationMetadataBuilder(EMBEDDABLE); return annotationBuilder.build(); } /** * Locates declared fields. * <p> * If no parent is defined, one will be located or created. All declared * fields will be returned. * * @return fields (never returns null) */ private List<FieldMetadataBuilder> getFieldBuilders() { // Locate all declared fields final List<? extends FieldMetadata> declaredFields = governorTypeDetails.getDeclaredFields(); // Add fields to ITD from annotation final List<FieldMetadata> fields = new ArrayList<FieldMetadata>(); if (identifierServiceResult != null) { for (final Identifier identifier : identifierServiceResult) { final List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>(); annotations.add(getColumnBuilder(identifier)); if (identifier.getFieldType().equals(DATE)) { setDateAnnotations(identifier.getColumnDefinition(), annotations); } final FieldMetadata idField = new FieldMetadataBuilder(getId(), Modifier.PRIVATE, annotations, identifier.getFieldName(), identifier.getFieldType()).build(); // Only add field to ITD if not declared on governor if (!hasField(declaredFields, idField)) { fields.add(idField); } } } fields.addAll(declaredFields); // Remove fields with static and transient modifiers for (final Iterator<FieldMetadata> iter = fields.iterator(); iter.hasNext();) { final FieldMetadata field = iter.next(); if (Modifier.isStatic(field.getModifier()) || Modifier.isTransient(field.getModifier())) { iter.remove(); } } // Remove fields with the @Transient annotation final List<FieldMetadata> transientAnnotatedFields = governorTypeDetails.getFieldsWithAnnotation(TRANSIENT); if (fields.containsAll(transientAnnotatedFields)) { fields.removeAll(transientAnnotatedFields); } final List<FieldMetadataBuilder> fieldBuilders = new ArrayList<FieldMetadataBuilder>(); if (!fields.isEmpty()) { for (final FieldMetadata field : fields) { fieldBuilders.add(new FieldMetadataBuilder(field)); } return fieldBuilders; } // We need to create a default identifier field final List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>(); // Compute the column name, as required final AnnotationMetadataBuilder columnBuilder = new AnnotationMetadataBuilder(COLUMN); columnBuilder.addStringAttribute("name", "id"); columnBuilder.addBooleanAttribute("nullable", false); annotations.add(columnBuilder); fieldBuilders.add(new FieldMetadataBuilder(getId(), Modifier.PRIVATE, annotations, new JavaSymbolName("id"), LONG_OBJECT)); return fieldBuilders; } /** * Locates the mutator methods. * <p> * If {@link #getFieldBuilders()} returns fields created by this ITD, public * mutators will automatically be produced in the declaring class. * * @param fields * @return the mutators (never returns null) */ private List<MethodMetadataBuilder> getMutators(final List<FieldMetadataBuilder> fields) { final List<MethodMetadataBuilder> mutators = new ArrayList<MethodMetadataBuilder>(); // Compute the names of the mutators that will be produced for (final FieldMetadataBuilder field : fields) { final JavaSymbolName requiredMutatorName = BeanInfoUtils.getMutatorMethodName(field.getFieldName()); final JavaType parameterType = field.getFieldType(); final MethodMetadata mutator = getGovernorMethod(requiredMutatorName, parameterType); if (mutator == null) { mutators.add(getMutatorMethod(field.getFieldName(), field.getFieldType())); } else { Validate.isTrue(Modifier.isPublic(mutator.getModifier()), "User provided field but failed to provide a public '%s(%s)' method in '%s'", requiredMutatorName.getSymbolName(), field.getFieldName().getSymbolName(), destination.getFullyQualifiedTypeName()); mutators.add(new MethodMetadataBuilder(mutator)); } } return mutators; } /** * Locates the no-arg constructor for this class, if available. * <p> * If a class defines a no-arg constructor, it is returned (irrespective of * access modifiers). * <p> * If a class does not define a no-arg constructor, one might be created. It * will only be created if the {@link RooIdentifier#noArgConstructor} is * true AND there is at least one other constructor declared in the source * file. If a constructor is created, it will have a private access * modifier. * * @return the constructor (may return null if no constructor is to be * produced) */ private ConstructorMetadataBuilder getNoArgConstructor() { // Search for an existing constructor final List<JavaType> parameterTypes = new ArrayList<JavaType>(); final ConstructorMetadata result = governorTypeDetails.getDeclaredConstructor(parameterTypes); if (result != null) { // Found an existing no-arg constructor on this class return null; } // Create the constructor final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); bodyBuilder.appendFormalLine("super();"); final ConstructorMetadataBuilder constructorBuilder = new ConstructorMetadataBuilder(getId()); constructorBuilder.setModifier(publicNoArgConstructor ? Modifier.PUBLIC : Modifier.PRIVATE); constructorBuilder.setParameterTypes(AnnotatedJavaType.convertFromJavaTypes(parameterTypes)); constructorBuilder.setBodyBuilder(bodyBuilder); return constructorBuilder; } /** * Locates the parameterised constructor consisting of the id fields for * this class. * * @param fields * @return the constructor, never null. */ private ConstructorMetadataBuilder getParameterizedConstructor( final List<FieldMetadataBuilder> fields) { // Search for an existing constructor final List<JavaType> parameterTypes = new ArrayList<JavaType>(); for (final FieldMetadataBuilder field : fields) { parameterTypes.add(field.getFieldType()); } final ConstructorMetadata result = governorTypeDetails.getDeclaredConstructor(parameterTypes); if (result != null) { // Found an existing parameterised constructor on this class publicNoArgConstructor = true; return null; } final List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>(); final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); bodyBuilder.appendFormalLine("super();"); for (final FieldMetadataBuilder field : fields) { final String fieldName = field.getFieldName().getSymbolName(); bodyBuilder.appendFormalLine("this." + fieldName + " = " + fieldName + ";"); parameterNames.add(field.getFieldName()); } // Create the constructor final ConstructorMetadataBuilder constructorBuilder = new ConstructorMetadataBuilder(getId()); constructorBuilder.setModifier(Modifier.PUBLIC); constructorBuilder.setParameterTypes(AnnotatedJavaType.convertFromJavaTypes(parameterTypes)); constructorBuilder.setParameterNames(parameterNames); constructorBuilder.setBodyBuilder(bodyBuilder); return constructorBuilder; } private boolean hasField(final List<? extends FieldMetadata> declaredFields, final FieldMetadata idField) { for (final FieldMetadata declaredField : declaredFields) { if (declaredField.getFieldName().equals(idField.getFieldName())) { return true; } } return false; } private void setDateAnnotations(final String columnDefinition, final List<AnnotationMetadataBuilder> annotations) { // Add JSR 220 @Temporal annotation to date fields String temporalType = StringUtils.defaultIfEmpty(StringUtils.upperCase(columnDefinition), "DATE"); if ("DATETIME".equals(temporalType)) { temporalType = "TIMESTAMP"; // ROO-2606 } final AnnotationMetadataBuilder temporalBuilder = new AnnotationMetadataBuilder(TEMPORAL); temporalBuilder.addEnumAttribute("value", new EnumDetails(TEMPORAL_TYPE, new JavaSymbolName( temporalType))); annotations.add(temporalBuilder); final AnnotationMetadataBuilder dateTimeFormatBuilder = new AnnotationMetadataBuilder(DATE_TIME_FORMAT); dateTimeFormatBuilder.addStringAttribute("style", "M-"); annotations.add(dateTimeFormatBuilder); } }