Package org.springframework.roo.addon.jpa.entity

Source Code of org.springframework.roo.addon.jpa.entity.JpaEntityMetadata

package org.springframework.roo.addon.jpa.entity;

import static org.springframework.roo.model.GoogleJavaType.DATANUCLEUS_JPA_EXTENSION;
import static org.springframework.roo.model.GoogleJavaType.GAE_DATASTORE_KEY;
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.CALENDAR;
import static org.springframework.roo.model.JpaJavaType.COLUMN;
import static org.springframework.roo.model.JpaJavaType.DISCRIMINATOR_COLUMN;
import static org.springframework.roo.model.JpaJavaType.EMBEDDED_ID;
import static org.springframework.roo.model.JpaJavaType.ENTITY;
import static org.springframework.roo.model.JpaJavaType.GENERATED_VALUE;
import static org.springframework.roo.model.JpaJavaType.GENERATION_TYPE;
import static org.springframework.roo.model.JpaJavaType.ID;
import static org.springframework.roo.model.JpaJavaType.INHERITANCE;
import static org.springframework.roo.model.JpaJavaType.INHERITANCE_TYPE;
import static org.springframework.roo.model.JpaJavaType.MAPPED_SUPERCLASS;
import static org.springframework.roo.model.JpaJavaType.SEQUENCE_GENERATOR;
import static org.springframework.roo.model.JpaJavaType.TABLE;
import static org.springframework.roo.model.JpaJavaType.VERSION;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.springframework.roo.addon.jpa.activerecord.RooJpaActiveRecord;
import org.springframework.roo.addon.jpa.identifier.Identifier;
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.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.EnumAttributeValue;
import org.springframework.roo.classpath.itd.AbstractItdTypeDetailsProvidingMetadataItem;
import org.springframework.roo.classpath.itd.InvocableMemberBodyBuilder;
import org.springframework.roo.classpath.operations.InheritanceType;
import org.springframework.roo.classpath.scanner.MemberDetails;
import org.springframework.roo.metadata.MetadataItem;
import org.springframework.roo.model.EnumDetails;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;

/**
* The metadata for a JPA entity's *_Roo_Jpa_Entity.aj ITD.
*
* @author Andrew Swan
* @since 1.2.0
*/
public class JpaEntityMetadata extends
        AbstractItdTypeDetailsProvidingMetadataItem {

    private final JpaEntityAnnotationValues annotationValues;
    private final MemberDetails entityMemberDetails;
    private final Identifier identifier;
    private final boolean isDatabaseDotComEnabled;
    private final boolean isGaeEnabled;
    private final JpaEntityMetadata parent;
    private FieldMetadata identifierField;
    private FieldMetadata versionField;

    /**
     * Constructor
     *
     * @param metadataIdentificationString the JPA_ID of this
     *            {@link MetadataItem}
     * @param itdName the ITD's {@link JavaType} (required)
     * @param entityPhysicalType the entity's physical type (required)
     * @param parent can be <code>null</code> if none of the governor's
     *            ancestors provide {@link JpaEntityMetadata}
     * @param entityMemberDetails details of the entity's members (required)
     * @param identifier information about the entity's identifier field in the
     *            event that the annotation doesn't provide such information;
     *            can be <code>null</code>
     * @param annotationValues the effective annotation values taking into
     *            account the presence of a {@link RooJpaActiveRecord} and/or
     *            {@link RooJpaEntity} annotation (required)
     */
    public JpaEntityMetadata(final String metadataIdentificationString,
            final JavaType itdName,
            final PhysicalTypeMetadata entityPhysicalType,
            final JpaEntityMetadata parent,
            final MemberDetails entityMemberDetails,
            final Identifier identifier,
            final JpaEntityAnnotationValues annotationValues,
            final boolean isGaeEnabled, final boolean isDatabaseDotComEnabled) {
        super(metadataIdentificationString, itdName, entityPhysicalType);
        Validate.notNull(annotationValues, "Annotation values are required");
        Validate.notNull(entityMemberDetails,
                "Entity MemberDetails are required");

        /*
         * Ideally we'd pass these parameters to the methods below rather than
         * storing them in fields, but this isn't an option due to various calls
         * to the parent entity.
         */
        this.annotationValues = annotationValues;
        this.entityMemberDetails = entityMemberDetails;
        this.identifier = identifier;
        this.parent = parent;
        this.isGaeEnabled = isGaeEnabled;
        this.isDatabaseDotComEnabled = isDatabaseDotComEnabled;

        // Add @Entity or @MappedSuperclass annotation
        builder.addAnnotation(annotationValues.isMappedSuperclass() ? getTypeAnnotation(MAPPED_SUPERCLASS)
                : getEntityAnnotation());

        // Add @Table annotation if required
        builder.addAnnotation(getTableAnnotation());

        // Add @Inheritance annotation if required
        builder.addAnnotation(getInheritanceAnnotation());

        // Add @DiscriminatorColumn if required
        builder.addAnnotation(getDiscriminatorColumnAnnotation());

        // Ensure there's a no-arg constructor (explicit or default)
        builder.addConstructor(getNoArgConstructor());

        // Add identifier field and accessor
        identifierField = getIdentifierField();
        builder.addField(identifierField);
        builder.addMethod(getIdentifierAccessor());
        builder.addMethod(getIdentifierMutator());

        // Add version field and accessor
        versionField = getVersionField();
        builder.addField(versionField);
        builder.addMethod(getVersionAccessor());
        builder.addMethod(getVersionMutator());

        // Build the ITD based on what we added to the builder above
        itdTypeDetails = builder.build();
    }

    private AnnotationMetadata getDiscriminatorColumnAnnotation() {
        if (StringUtils.isNotBlank(annotationValues.getInheritanceType())
                && InheritanceType.SINGLE_TABLE.name().equals(
                        annotationValues.getInheritanceType())) {
            // Theoretically not required based on @DiscriminatorColumn
            // JavaDocs, but Hibernate appears to fail if it's missing
            return getTypeAnnotation(DISCRIMINATOR_COLUMN);
        }
        return null;
    }

    /**
     * Generates the JPA @Entity annotation to be applied to the entity
     *
     * @return
     */
    private AnnotationMetadata getEntityAnnotation() {
        AnnotationMetadata entityAnnotation = getTypeAnnotation(ENTITY);
        if (entityAnnotation == null) {
            return null;
        }

        if (StringUtils.isNotBlank(annotationValues.getEntityName())) {
            final AnnotationMetadataBuilder entityBuilder = new AnnotationMetadataBuilder(
                    entityAnnotation);
            entityBuilder.addStringAttribute("name",
                    annotationValues.getEntityName());
            entityAnnotation = entityBuilder.build();
        }

        return entityAnnotation;
    }

    /**
     * Locates the identifier accessor method.
     * <p>
     * If {@link #getIdentifierField()} returns a field created by this ITD or
     * if the field is declared within the entity itself, a public accessor will
     * automatically be produced in the declaring class.
     *
     * @return the accessor (never returns null)
     */
    private MethodMetadataBuilder getIdentifierAccessor() {
        if (parent != null) {
            return parent.getIdentifierAccessor();
        }

        // Locate the identifier field, and compute the name of the accessor
        // that will be produced
        JavaSymbolName requiredAccessorName = BeanInfoUtils
                .getAccessorMethodName(identifierField);

        // See if the user provided the field
        if (!getId().equals(identifierField.getDeclaredByMetadataId())) {
            // Locate an existing accessor
            final MethodMetadata method = entityMemberDetails.getMethod(
                    requiredAccessorName, new ArrayList<JavaType>());
            if (method != null) {
                if (Modifier.isPublic(method.getModifier())) {
                    // Method exists and is public so return it
                    return new MethodMetadataBuilder(method);
                }

                // Method is not public so make the required accessor name
                // unique
                requiredAccessorName = new JavaSymbolName(
                        requiredAccessorName.getSymbolName() + "_");
            }
        }

        // We declared the field in this ITD, so produce a public accessor for
        // it
        final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
        bodyBuilder.appendFormalLine("return this."
                + identifierField.getFieldName().getSymbolName() + ";");

        return new MethodMetadataBuilder(getId(), Modifier.PUBLIC,
                requiredAccessorName, identifierField.getFieldType(),
                bodyBuilder);
    }

    private String getIdentifierColumn() {
        if (StringUtils.isNotBlank(annotationValues.getIdentifierColumn())) {
            return annotationValues.getIdentifierColumn();
        }
        else if (identifier != null
                && StringUtils.isNotBlank(identifier.getColumnName())) {
            return identifier.getColumnName();
        }
        return "";
    }

    /**
     * Locates the identifier field.
     * <p>
     * If a parent is defined, it must provide the field.
     * <p>
     * If no parent is defined, one will be located or created. Any declared or
     * inherited field which has the {@link javax.persistence.Id @Id} or
     * {@link javax.persistence.EmbeddedId @EmbeddedId} annotation will be taken
     * as the identifier and returned. If no such field is located, a private
     * field will be created as per the details contained in the
     * {@link RooJpaActiveRecord} or {@link RooJpaEntity} annotation, as
     * applicable.
     *
     * @param parent (can be <code>null</code>)
     * @param project the user's project (required)
     * @param annotationValues
     * @param identifier can be <code>null</code>
     * @return the identifier (never returns null)
     */
    private FieldMetadata getIdentifierField() {
        if (parent != null) {
            final FieldMetadata idField = parent.getIdentifierField();
            if (idField != null) {
                if (MemberFindingUtils.getAnnotationOfType(
                        idField.getAnnotations(), ID) != null) {
                    return idField;
                }
                else if (MemberFindingUtils.getAnnotationOfType(
                        idField.getAnnotations(), EMBEDDED_ID) != null) {
                    return idField;
                }
            }
            return parent.getIdentifierField();
        }

        // Try to locate an existing field with @javax.persistence.Id
        final List<FieldMetadata> idFields = governorTypeDetails
                .getFieldsWithAnnotation(ID);
        if (!idFields.isEmpty()) {
            return getIdentifierField(idFields, ID);
        }

        // Try to locate an existing field with @javax.persistence.EmbeddedId
        final List<FieldMetadata> embeddedIdFields = governorTypeDetails
                .getFieldsWithAnnotation(EMBEDDED_ID);
        if (!embeddedIdFields.isEmpty()) {
            return getIdentifierField(embeddedIdFields, EMBEDDED_ID);
        }

        // Ensure there isn't already a field called "id"; if so, compute a
        // unique name (it's not really a fatal situation at the end of the day)
        final JavaSymbolName idField = governorTypeDetails
                .getUniqueFieldName(getIdentifierFieldName());

        // We need to create one
        final JavaType identifierType = getIdentifierType();

        final List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>();
        final boolean hasIdClass = !(identifierType.isCoreType() || identifierType
                .equals(GAE_DATASTORE_KEY));
        final JavaType annotationType = hasIdClass ? EMBEDDED_ID : ID;
        annotations.add(new AnnotationMetadataBuilder(annotationType));

        // Encode keys as strings on GAE to support entity group hierarchies
        if (isGaeEnabled && identifierType.equals(JavaType.STRING)) {
            AnnotationMetadataBuilder extensionBuilder = new AnnotationMetadataBuilder(
                    DATANUCLEUS_JPA_EXTENSION);
            extensionBuilder.addStringAttribute("vendorName", "datanucleus");
            extensionBuilder.addStringAttribute("key", "gae.encoded-pk");
            extensionBuilder.addStringAttribute("value", "true");
            annotations.add(extensionBuilder);
        }

        // Compute the column name, as required
        if (!hasIdClass) {
            if (!"".equals(annotationValues.getSequenceName())) {
                String generationType = isGaeEnabled || isDatabaseDotComEnabled ? "IDENTITY"
                        : "AUTO";

                // ROO-746: Use @GeneratedValue(strategy = GenerationType.TABLE)
                // If the root of the governor declares @Inheritance(strategy =
                // InheritanceType.TABLE_PER_CLASS)
                if ("AUTO".equals(generationType)) {
                    AnnotationMetadata inheritance = governorTypeDetails
                            .getAnnotation(INHERITANCE);
                    if (inheritance == null) {
                        inheritance = getInheritanceAnnotation();
                    }
                    if (inheritance != null) {
                        final AnnotationAttributeValue<?> value = inheritance
                                .getAttribute(new JavaSymbolName("strategy"));
                        if (value instanceof EnumAttributeValue) {
                            final EnumAttributeValue enumAttributeValue = (EnumAttributeValue) value;
                            final EnumDetails details = enumAttributeValue
                                    .getValue();
                            if (details != null
                                    && details.getType().equals(
                                            INHERITANCE_TYPE)) {
                                if ("TABLE_PER_CLASS".equals(details.getField()
                                        .getSymbolName())) {
                                    generationType = "TABLE";
                                }
                            }
                        }
                    }
                }

                final AnnotationMetadataBuilder generatedValueBuilder = new AnnotationMetadataBuilder(
                        GENERATED_VALUE);
                generatedValueBuilder.addEnumAttribute("strategy",
                        new EnumDetails(GENERATION_TYPE, new JavaSymbolName(
                                generationType)));

                if (StringUtils.isNotBlank(annotationValues.getSequenceName())
                        && !(isGaeEnabled || isDatabaseDotComEnabled)) {
                    final String sequenceKey = StringUtils
                            .uncapitalize(destination.getSimpleTypeName())
                            + "Gen";
                    generatedValueBuilder.addStringAttribute("generator",
                            sequenceKey);
                    final AnnotationMetadataBuilder sequenceGeneratorBuilder = new AnnotationMetadataBuilder(
                            SEQUENCE_GENERATOR);
                    sequenceGeneratorBuilder.addStringAttribute("name",
                            sequenceKey);
                    sequenceGeneratorBuilder.addStringAttribute("sequenceName",
                            annotationValues.getSequenceName());
                    annotations.add(sequenceGeneratorBuilder);
                }
                annotations.add(generatedValueBuilder);
            }

            final String identifierColumn = StringUtils
                    .stripToEmpty(getIdentifierColumn());
            String columnName = idField.getSymbolName();
            if (StringUtils.isNotBlank(identifierColumn)) {
                // User has specified an alternate column name
                columnName = identifierColumn;
            }

            final AnnotationMetadataBuilder columnBuilder = new AnnotationMetadataBuilder(
                    COLUMN);
            columnBuilder.addStringAttribute("name", columnName);
            if (identifier != null
                    && StringUtils.isNotBlank(identifier.getColumnDefinition())) {
                columnBuilder.addStringAttribute("columnDefinition",
                        identifier.getColumnDefinition());
            }

            // Add length attribute for String field
            if (identifier != null && identifier.getColumnSize() > 0
                    && identifier.getColumnSize() < 4000
                    && identifierType.equals(JavaType.STRING)) {
                columnBuilder.addIntegerAttribute("length",
                        identifier.getColumnSize());
            }

            // Add precision and scale attributes for numeric field
            if (identifier != null
                    && identifier.getScale() > 0
                    && (identifierType.equals(JavaType.DOUBLE_OBJECT)
                            || identifierType.equals(JavaType.DOUBLE_PRIMITIVE) || identifierType
                                .equals(BIG_DECIMAL))) {
                columnBuilder.addIntegerAttribute("precision",
                        identifier.getColumnSize());
                columnBuilder.addIntegerAttribute("scale",
                        identifier.getScale());
            }

            annotations.add(columnBuilder);
        }

        return new FieldMetadataBuilder(getId(), Modifier.PRIVATE, annotations,
                idField, identifierType).build();
    }

    private FieldMetadata getIdentifierField(
            final List<FieldMetadata> identifierFields,
            final JavaType identifierType) {
        Validate.isTrue(identifierFields.size() == 1,
                "More than one field was annotated with @%s in '%s'",
                identifierType.getSimpleTypeName(),
                destination.getFullyQualifiedTypeName());
        return new FieldMetadataBuilder(identifierFields.get(0)).build();
    }

    private String getIdentifierFieldName() {
        if (StringUtils.isNotBlank(annotationValues.getIdentifierField())) {
            return annotationValues.getIdentifierField();
        }
        else if (identifier != null && identifier.getFieldName() != null) {
            return identifier.getFieldName().getSymbolName();
        }
        // Use the default
        return RooJpaEntity.ID_FIELD_DEFAULT;
    }

    /**
     * Locates the identifier mutator method.
     * <p>
     * If {@link #getIdentifierField()} returns a field created by this ITD or
     * if the field is declared within the entity itself, a public mutator will
     * automatically be produced in the declaring class.
     *
     * @return the mutator (never returns null)
     */
    private MethodMetadataBuilder getIdentifierMutator() {
        // TODO: This is a temporary workaround to support web data binding
        // approaches; to be reviewed more thoroughly in future
        if (parent != null) {
            return parent.getIdentifierMutator();
        }

        // Locate the identifier field, and compute the name of the accessor
        // that will be produced
        JavaSymbolName requiredMutatorName = BeanInfoUtils
                .getMutatorMethodName(identifierField);

        final List<JavaType> parameterTypes = Arrays.asList(identifierField
                .getFieldType());
        final List<JavaSymbolName> parameterNames = Arrays
                .asList(new JavaSymbolName("id"));

        // See if the user provided the field
        if (!getId().equals(identifierField.getDeclaredByMetadataId())) {
            // Locate an existing mutator
            final MethodMetadata method = entityMemberDetails.getMethod(
                    requiredMutatorName, parameterTypes);
            if (method != null) {
                if (Modifier.isPublic(method.getModifier())) {
                    // Method exists and is public so return it
                    return new MethodMetadataBuilder(method);
                }

                // Method is not public so make the required mutator name unique
                requiredMutatorName = new JavaSymbolName(
                        requiredMutatorName.getSymbolName() + "_");
            }
        }

        // We declared the field in this ITD, so produce a public mutator for it
        final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
        bodyBuilder.appendFormalLine("this."
                + identifierField.getFieldName().getSymbolName() + " = id;");

        return new MethodMetadataBuilder(getId(), Modifier.PUBLIC,
                requiredMutatorName, JavaType.VOID_PRIMITIVE,
                AnnotatedJavaType.convertFromJavaTypes(parameterTypes),
                parameterNames, bodyBuilder);
    }

    /**
     * Returns the {@link JavaType} of the identifier field
     *
     * @param annotationValues the values of the {@link RooJpaEntity} annotation
     *            (required)
     * @param identifier can be <code>null</code>
     * @return a non-<code>null</code> type
     */
    private JavaType getIdentifierType() {
        if (isDatabaseDotComEnabled) {
            return JavaType.STRING;
        }
        if (annotationValues.getIdentifierType() != null) {
            return annotationValues.getIdentifierType();
        }
        else if (identifier != null && identifier.getFieldType() != null) {
            return identifier.getFieldType();
        }
        // Use the default
        return LONG_OBJECT;
    }

    /**
     * Returns the JPA @Inheritance annotation to be applied to the entity, if
     * applicable
     *
     * @param annotationValues the values of the {@link RooJpaEntity} annotation
     *            (required)
     * @return <code>null</code> if it's already present or not required
     */
    private AnnotationMetadata getInheritanceAnnotation() {
        if (governorTypeDetails.getAnnotation(INHERITANCE) != null) {
            return null;
        }
        if (StringUtils.isNotBlank(annotationValues.getInheritanceType())) {
            final AnnotationMetadataBuilder inheritanceBuilder = new AnnotationMetadataBuilder(
                    INHERITANCE);
            inheritanceBuilder.addEnumAttribute("strategy",
                    new EnumDetails(INHERITANCE_TYPE, new JavaSymbolName(
                            annotationValues.getInheritanceType())));
            return inheritanceBuilder.build();
        }
        return null;
    }

    /**
     * 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>
     * Otherwise, and if there is at least one other constructor declared in the
     * source file, this method creates one with public access.
     *
     * @return <code>null</code> if no constructor is to be produced
     */
    private ConstructorMetadataBuilder getNoArgConstructor() {
        // Search for an existing constructor
        final ConstructorMetadata existingExplicitConstructor = governorTypeDetails
                .getDeclaredConstructor(null);
        if (existingExplicitConstructor != null) {
            // Found an existing no-arg constructor on this class, so return it
            return new ConstructorMetadataBuilder(existingExplicitConstructor);
        }

        // To get this far, the user did not define a no-arg constructor
        if (governorTypeDetails.getDeclaredConstructors().isEmpty()) {
            // Java creates the default constructor => no need to add one
            return null;
        }

        // Create the constructor
        final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
        bodyBuilder.appendFormalLine("super();");

        final ConstructorMetadataBuilder constructorBuilder = new ConstructorMetadataBuilder(
                getId());
        constructorBuilder.setBodyBuilder(bodyBuilder);
        constructorBuilder.setModifier(Modifier.PUBLIC);
        return constructorBuilder;
    }

    /**
     * Generates the JPA @Table annotation to be applied to the entity
     *
     * @param annotationValues
     * @return
     */
    private AnnotationMetadata getTableAnnotation() {
        final AnnotationMetadata tableAnnotation = getTypeAnnotation(TABLE);
        if (tableAnnotation == null) {
            return null;
        }
        final String catalog = annotationValues.getCatalog();
        final String schema = annotationValues.getSchema();
        final String table = annotationValues.getTable();
        if (StringUtils.isNotBlank(table) || StringUtils.isNotBlank(schema)
                || StringUtils.isNotBlank(catalog)) {
            final AnnotationMetadataBuilder tableBuilder = new AnnotationMetadataBuilder(
                    tableAnnotation);
            if (StringUtils.isNotBlank(catalog)) {
                tableBuilder.addStringAttribute("catalog", catalog);
            }
            if (StringUtils.isNotBlank(schema)) {
                tableBuilder.addStringAttribute("schema", schema);
            }
            if (StringUtils.isNotBlank(table)) {
                tableBuilder.addStringAttribute("name", table);
            }
            return tableBuilder.build();
        }
        return null;
    }

    /**
     * Locates the version accessor method.
     * <p>
     * If {@link #getVersionField()} returns a field created by this ITD or if
     * the version field is declared within the entity itself, a public accessor
     * will automatically be produced in the declaring class.
     *
     * @param memberDetails
     * @return the version accessor (may return null if there is no version
     *         field declared in this class)
     */
    private MethodMetadataBuilder getVersionAccessor() {
        if (versionField == null) {
            // There's no version field, so there certainly won't be an accessor
            // for it
            return null;
        }

        if (parent != null) {
            final FieldMetadata result = parent.getVersionField();
            if (result != null) {
                // It's the parent's responsibility to provide the accessor, not
                // ours
                return parent.getVersionAccessor();
            }
        }

        // Compute the name of the accessor that will be produced
        JavaSymbolName requiredAccessorName = BeanInfoUtils
                .getAccessorMethodName(versionField);

        // See if the user provided the field
        if (!getId().equals(versionField.getDeclaredByMetadataId())) {
            // Locate an existing accessor
            final MethodMetadata method = entityMemberDetails.getMethod(
                    requiredAccessorName, new ArrayList<JavaType>(), getId());
            if (method != null) {
                if (Modifier.isPublic(method.getModifier())) {
                    // Method exists and is public so return it
                    return new MethodMetadataBuilder(method);
                }

                // Method is not public so make the required accessor name
                // unique
                requiredAccessorName = new JavaSymbolName(
                        requiredAccessorName.getSymbolName() + "_");
            }
        }

        // We declared the field in this ITD, so produce a public accessor for
        // it
        final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
        bodyBuilder.appendFormalLine("return this."
                + versionField.getFieldName().getSymbolName() + ";");

        return new MethodMetadataBuilder(getId(), Modifier.PUBLIC,
                requiredAccessorName, versionField.getFieldType(), bodyBuilder);
    }

    /**
     * Locates the version field.
     * <p>
     * If a parent is defined, it may provide the field.
     * <p>
     * If no parent is defined, one may be located or created. Any declared or
     * inherited field which is annotated with javax.persistence.Version will be
     * taken as the version and returned. If no such field is located, a private
     * field may be created as per the details contained in
     * {@link RooJpaActiveRecord} or {@link RooJpaEntity} annotation, as
     * applicable.
     *
     * @return the version field (may be null)
     */
    private FieldMetadata getVersionField() {
        if (parent != null) {
            final FieldMetadata result = parent.getVersionField();
            if (result != null) {
                return result;
            }
        }

        // Try to locate an existing field with @Version
        final List<FieldMetadata> versionFields = governorTypeDetails
                .getFieldsWithAnnotation(VERSION);
        if (!versionFields.isEmpty()) {
            Validate.isTrue(versionFields.size() == 1,
                    "More than 1 field was annotated with @Version in '%s'",
                    destination.getFullyQualifiedTypeName());
            return versionFields.get(0);
        }

        // Quit at this stage if the user doesn't want a version field
        final String versionField = annotationValues.getVersionField();
        if ("".equals(versionField)) {
            return null;
        }

        // Ensure there isn't already a field called "version"; if so, compute a
        // unique name (it's not really a fatal situation at the end of the day)
        final JavaSymbolName verField = governorTypeDetails
                .getUniqueFieldName(versionField);

        // We're creating one
        JavaType versionType = annotationValues.getVersionType();
        String versionColumn = StringUtils.defaultIfEmpty(
                annotationValues.getVersionColumn(), verField.getSymbolName());
        if (isDatabaseDotComEnabled) {
            versionType = CALENDAR;
            versionColumn = "lastModifiedDate";
        }

        final List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>();
        annotations.add(new AnnotationMetadataBuilder(VERSION));

        final AnnotationMetadataBuilder columnBuilder = new AnnotationMetadataBuilder(
                COLUMN);
        columnBuilder.addStringAttribute("name", versionColumn);
        annotations.add(columnBuilder);

        return new FieldMetadataBuilder(getId(), Modifier.PRIVATE, annotations,
                verField, versionType).build();
    }

    /**
     * Locates the version mutator method.
     * <p>
     * If {@link #getVersionField()} returns a field created by this ITD or if
     * the version field is declared within the entity itself, a public mutator
     * will automatically be produced in the declaring class.
     *
     * @return the mutator (may return null if there is no version field
     *         declared in this class)
     */
    private MethodMetadataBuilder getVersionMutator() {
        // TODO: This is a temporary workaround to support web data binding
        // approaches; to be reviewed more thoroughly in future
        if (parent != null) {
            return parent.getVersionMutator();
        }

        // Locate the version field, and compute the name of the mutator that
        // will be produced
        if (versionField == null) {
            // There's no version field, so there certainly won't be a mutator
            // for it
            return null;
        }

        // Compute the name of the mutator that will be produced
        JavaSymbolName requiredMutatorName = BeanInfoUtils
                .getMutatorMethodName(versionField);

        final List<JavaType> parameterTypes = Arrays.asList(versionField
                .getFieldType());
        final List<JavaSymbolName> parameterNames = Arrays
                .asList(new JavaSymbolName("version"));

        // See if the user provided the field
        if (!getId().equals(versionField.getDeclaredByMetadataId())) {
            // Locate an existing mutator
            final MethodMetadata method = entityMemberDetails.getMethod(
                    requiredMutatorName, parameterTypes, getId());
            if (method != null) {
                if (Modifier.isPublic(method.getModifier())) {
                    // Method exists and is public so return it
                    return new MethodMetadataBuilder(method);
                }

                // Method is not public so make the required mutator name unique
                requiredMutatorName = new JavaSymbolName(
                        requiredMutatorName.getSymbolName() + "_");
            }
        }

        // We declared the field in this ITD, so produce a public mutator for it
        final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
        bodyBuilder.appendFormalLine("this."
                + versionField.getFieldName().getSymbolName() + " = version;");

        return new MethodMetadataBuilder(getId(), Modifier.PUBLIC,
                requiredMutatorName, JavaType.VOID_PRIMITIVE,
                AnnotatedJavaType.convertFromJavaTypes(parameterTypes),
                parameterNames, bodyBuilder);
    }
}
TOP

Related Classes of org.springframework.roo.addon.jpa.entity.JpaEntityMetadata

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.