package org.springframework.roo.addon.jpa.entity; import static org.springframework.roo.classpath.customdata.CustomDataKeys.COLUMN_FIELD; import static org.springframework.roo.classpath.customdata.CustomDataKeys.EMBEDDED_FIELD; import static org.springframework.roo.classpath.customdata.CustomDataKeys.EMBEDDED_ID_FIELD; import static org.springframework.roo.classpath.customdata.CustomDataKeys.ENUMERATED_FIELD; import static org.springframework.roo.classpath.customdata.CustomDataKeys.IDENTIFIER_ACCESSOR_METHOD; import static org.springframework.roo.classpath.customdata.CustomDataKeys.IDENTIFIER_FIELD; import static org.springframework.roo.classpath.customdata.CustomDataKeys.IDENTIFIER_MUTATOR_METHOD; import static org.springframework.roo.classpath.customdata.CustomDataKeys.IDENTIFIER_TYPE; import static org.springframework.roo.classpath.customdata.CustomDataKeys.LOB_FIELD; import static org.springframework.roo.classpath.customdata.CustomDataKeys.MANY_TO_MANY_FIELD; import static org.springframework.roo.classpath.customdata.CustomDataKeys.MANY_TO_ONE_FIELD; import static org.springframework.roo.classpath.customdata.CustomDataKeys.ONE_TO_MANY_FIELD; import static org.springframework.roo.classpath.customdata.CustomDataKeys.ONE_TO_ONE_FIELD; import static org.springframework.roo.classpath.customdata.CustomDataKeys.PERSISTENT_TYPE; import static org.springframework.roo.classpath.customdata.CustomDataKeys.TRANSIENT_FIELD; import static org.springframework.roo.classpath.customdata.CustomDataKeys.VERSION_ACCESSOR_METHOD; import static org.springframework.roo.classpath.customdata.CustomDataKeys.VERSION_FIELD; import static org.springframework.roo.classpath.customdata.CustomDataKeys.VERSION_MUTATOR_METHOD; import static org.springframework.roo.model.JpaJavaType.COLUMN; import static org.springframework.roo.model.JpaJavaType.EMBEDDED; import static org.springframework.roo.model.JpaJavaType.EMBEDDED_ID; import static org.springframework.roo.model.JpaJavaType.ENUMERATED; import static org.springframework.roo.model.JpaJavaType.ID; import static org.springframework.roo.model.JpaJavaType.LOB; 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 static org.springframework.roo.model.JpaJavaType.VERSION; import static org.springframework.roo.model.RooJavaType.ROO_JPA_ACTIVE_RECORD; import static org.springframework.roo.model.RooJavaType.ROO_JPA_ENTITY; import java.util.Arrays; import java.util.List; import org.apache.commons.lang3.Validate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.osgi.service.component.ComponentContext; import org.springframework.roo.addon.jpa.AbstractIdentifierServiceAwareMetadataProvider; import org.springframework.roo.addon.jpa.identifier.Identifier; import org.springframework.roo.addon.jpa.identifier.IdentifierMetadata; import org.springframework.roo.classpath.PhysicalTypeIdentifier; import org.springframework.roo.classpath.PhysicalTypeIdentifierNamingUtils; import org.springframework.roo.classpath.PhysicalTypeMetadata; import org.springframework.roo.classpath.customdata.taggers.AnnotatedTypeMatcher; import org.springframework.roo.classpath.customdata.taggers.CustomDataKeyDecorator; import org.springframework.roo.classpath.customdata.taggers.FieldMatcher; import org.springframework.roo.classpath.customdata.taggers.MethodMatcher; import org.springframework.roo.classpath.customdata.taggers.MidTypeMatcher; import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder; import org.springframework.roo.classpath.itd.ItdTypeDetailsProvidingMetadataItem; import org.springframework.roo.classpath.scanner.MemberDetails; import org.springframework.roo.metadata.MetadataIdentificationUtils; import org.springframework.roo.model.JavaType; import org.springframework.roo.model.RooJavaType; import org.springframework.roo.project.FeatureNames; import org.springframework.roo.project.LogicalPath; import org.springframework.roo.project.ProjectMetadata; import org.springframework.roo.project.ProjectOperations; import org.springframework.roo.support.util.CollectionUtils; /** * The {@link JpaEntityMetadataProvider} implementation. * * @author Andrew Swan * @since 1.2.0 */ @Component(immediate = true) @Service public class JpaEntityMetadataProviderImpl extends AbstractIdentifierServiceAwareMetadataProvider implements JpaEntityMetadataProvider { // JPA-related field matchers private static final FieldMatcher JPA_COLUMN_FIELD_MATCHER = new FieldMatcher( COLUMN_FIELD, AnnotationMetadataBuilder.getInstance(COLUMN)); private static final FieldMatcher JPA_EMBEDDED_FIELD_MATCHER = new FieldMatcher( EMBEDDED_FIELD, AnnotationMetadataBuilder.getInstance(EMBEDDED)); private static final FieldMatcher JPA_EMBEDDED_ID_FIELD_MATCHER = new FieldMatcher( EMBEDDED_ID_FIELD, AnnotationMetadataBuilder.getInstance(EMBEDDED_ID)); private static final FieldMatcher JPA_ENUMERATED_FIELD_MATCHER = new FieldMatcher( ENUMERATED_FIELD, AnnotationMetadataBuilder.getInstance(ENUMERATED)); private static final FieldMatcher JPA_ID_AND_EMBEDDED_ID_FIELD_MATCHER = new FieldMatcher( IDENTIFIER_FIELD, AnnotationMetadataBuilder.getInstance(ID), AnnotationMetadataBuilder.getInstance(EMBEDDED_ID)); private static final FieldMatcher JPA_ID_FIELD_MATCHER = new FieldMatcher( IDENTIFIER_FIELD, AnnotationMetadataBuilder.getInstance(ID)); private static final FieldMatcher JPA_LOB_FIELD_MATCHER = new FieldMatcher( LOB_FIELD, AnnotationMetadataBuilder.getInstance(LOB)); private static final FieldMatcher JPA_MANY_TO_MANY_FIELD_MATCHER = new FieldMatcher( MANY_TO_MANY_FIELD, AnnotationMetadataBuilder.getInstance(MANY_TO_MANY)); private static final FieldMatcher JPA_MANY_TO_ONE_FIELD_MATCHER = new FieldMatcher( MANY_TO_ONE_FIELD, AnnotationMetadataBuilder.getInstance(MANY_TO_ONE)); private static final FieldMatcher JPA_ONE_TO_MANY_FIELD_MATCHER = new FieldMatcher( ONE_TO_MANY_FIELD, AnnotationMetadataBuilder.getInstance(ONE_TO_MANY)); private static final FieldMatcher JPA_ONE_TO_ONE_FIELD_MATCHER = new FieldMatcher( ONE_TO_ONE_FIELD, AnnotationMetadataBuilder.getInstance(ONE_TO_ONE)); private static final FieldMatcher JPA_TRANSIENT_FIELD_MATCHER = new FieldMatcher( TRANSIENT_FIELD, AnnotationMetadataBuilder.getInstance(TRANSIENT)); private static final FieldMatcher JPA_VERSION_FIELD_MATCHER = new FieldMatcher( VERSION_FIELD, AnnotationMetadataBuilder.getInstance(VERSION)); private static final String PROVIDES_TYPE_STRING = JpaEntityMetadata.class .getName(); private static final String PROVIDES_TYPE = MetadataIdentificationUtils .create(PROVIDES_TYPE_STRING); // The order of this array is the order in which we look for annotations. We // use the values of the first one found. private static final JavaType[] TRIGGER_ANNOTATIONS = { // We trigger off RooJpaEntity in case the user doesn't want Active // Record methods ROO_JPA_ENTITY, // We trigger off RooJpaActiveRecord so that existing projects don't // need to add RooJpaEntity ROO_JPA_ACTIVE_RECORD, }; @Reference private CustomDataKeyDecorator customDataKeyDecorator; @Reference private ProjectOperations projectOperations; protected void activate(final ComponentContext context) { metadataDependencyRegistry.registerDependency( PhysicalTypeIdentifier.getMetadataIdentiferType(), PROVIDES_TYPE); addMetadataTriggers(TRIGGER_ANNOTATIONS); registerMatchers(); } @Override protected String createLocalIdentifier(final JavaType javaType, final LogicalPath path) { return PhysicalTypeIdentifierNamingUtils.createIdentifier( PROVIDES_TYPE_STRING, javaType, path); } protected void deactivate(final ComponentContext context) { metadataDependencyRegistry.deregisterDependency( PhysicalTypeIdentifier.getMetadataIdentiferType(), PROVIDES_TYPE); removeMetadataTriggers(TRIGGER_ANNOTATIONS); customDataKeyDecorator.unregisterMatchers(getClass()); } @Override protected String getGovernorPhysicalTypeIdentifier( final String metadataIdentificationString) { final JavaType javaType = getType(metadataIdentificationString); final LogicalPath path = PhysicalTypeIdentifierNamingUtils.getPath( PROVIDES_TYPE_STRING, metadataIdentificationString); return PhysicalTypeIdentifier.createIdentifier(javaType, path); } /** * Returns the {@link Identifier} for the entity identified by the given * metadata ID. * * @param metadataIdentificationString * @return <code>null</code> if there isn't one */ private Identifier getIdentifier(final String metadataIdentificationString) { final JavaType entity = getType(metadataIdentificationString); final List<Identifier> identifiers = getIdentifiersForType(entity); if (CollectionUtils.isEmpty(identifiers)) { return null; } // We have potential identifier information from an IdentifierService. // We only use this identifier information if the user did NOT provide // ANY identifier-related attributes on @RooJpaEntity.... Validate.isTrue( identifiers.size() == 1, "Identifier service indicates " + identifiers.size() + " fields illegally for the entity '" + entity.getSimpleTypeName() + "' (should only be one identifier field given this is an entity, not an Identifier class)"); return identifiers.iterator().next(); } public String getItdUniquenessFilenameSuffix() { return "Jpa_Entity"; } /** * Returns the {@link JpaEntityAnnotationValues} for the given domain type * * @param governorPhysicalType (required) * @return a non-<code>null</code> instance */ private JpaEntityAnnotationValues getJpaEntityAnnotationValues( final PhysicalTypeMetadata governorPhysicalType) { for (final JavaType triggerAnnotation : TRIGGER_ANNOTATIONS) { final JpaEntityAnnotationValues annotationValues = new JpaEntityAnnotationValues( governorPhysicalType, triggerAnnotation); if (annotationValues.isAnnotationFound()) { return annotationValues; } } throw new IllegalStateException(getClass().getSimpleName() + " was triggered but not by any of " + Arrays.toString(TRIGGER_ANNOTATIONS)); } @Override protected ItdTypeDetailsProvidingMetadataItem getMetadata( final String metadataIdentificationString, final JavaType aspectName, final PhysicalTypeMetadata governorPhysicalType, final String itdFilename) { // Find out the entity-level JPA details from the trigger annotation final JpaEntityAnnotationValues jpaEntityAnnotationValues = getJpaEntityAnnotationValues(governorPhysicalType); /* * Walk the inheritance hierarchy for any existing JpaEntityMetadata. We * don't need to monitor any such parent, as any changes to its Java * type will trickle down to the governing java type. */ final JpaEntityMetadata parent = getParentMetadata(governorPhysicalType .getMemberHoldingTypeDetails()); // Get the governor's members final MemberDetails governorMemberDetails = getMemberDetails(governorPhysicalType); // Get the governor's ID field, if any final Identifier identifier = getIdentifier(metadataIdentificationString); boolean isGaeEnabled = false; boolean isDatabaseDotComEnabled = false; final String moduleName = PhysicalTypeIdentifierNamingUtils.getPath( metadataIdentificationString).getModule(); if (projectOperations.isProjectAvailable(moduleName)) { // If the project itself changes, we want a chance to refresh this // item metadataDependencyRegistry.registerDependency( ProjectMetadata.getProjectIdentifier(moduleName), metadataIdentificationString); isGaeEnabled = projectOperations .isFeatureInstalledInModule(FeatureNames.GAE, moduleName); isDatabaseDotComEnabled = projectOperations .isFeatureInstalledInFocusedModule(FeatureNames.DATABASE_DOT_COM); } return new JpaEntityMetadata(metadataIdentificationString, aspectName, governorPhysicalType, parent, governorMemberDetails, identifier, jpaEntityAnnotationValues, isGaeEnabled, isDatabaseDotComEnabled); } public String getProvidesType() { return PROVIDES_TYPE; } private JavaType getType(final String metadataIdentificationString) { return PhysicalTypeIdentifierNamingUtils.getJavaType( PROVIDES_TYPE_STRING, metadataIdentificationString); } @SuppressWarnings("unchecked") private void registerMatchers() { customDataKeyDecorator.registerMatchers( getClass(), // Type matchers new MidTypeMatcher(IDENTIFIER_TYPE, IdentifierMetadata.class .getName()), new AnnotatedTypeMatcher(PERSISTENT_TYPE, RooJavaType.ROO_JPA_ACTIVE_RECORD, ROO_JPA_ENTITY), // Field matchers JPA_COLUMN_FIELD_MATCHER, JPA_EMBEDDED_FIELD_MATCHER, JPA_EMBEDDED_ID_FIELD_MATCHER, JPA_ENUMERATED_FIELD_MATCHER, JPA_ID_FIELD_MATCHER, JPA_LOB_FIELD_MATCHER, JPA_MANY_TO_MANY_FIELD_MATCHER, JPA_MANY_TO_ONE_FIELD_MATCHER, JPA_ONE_TO_MANY_FIELD_MATCHER, JPA_ONE_TO_ONE_FIELD_MATCHER, JPA_TRANSIENT_FIELD_MATCHER, JPA_VERSION_FIELD_MATCHER, // Method matchers new MethodMatcher(Arrays .asList(JPA_ID_AND_EMBEDDED_ID_FIELD_MATCHER), IDENTIFIER_ACCESSOR_METHOD, true), new MethodMatcher( Arrays.asList(JPA_ID_AND_EMBEDDED_ID_FIELD_MATCHER), IDENTIFIER_MUTATOR_METHOD, false), new MethodMatcher( Arrays.asList(JPA_VERSION_FIELD_MATCHER), VERSION_ACCESSOR_METHOD, true), new MethodMatcher( Arrays.asList(JPA_VERSION_FIELD_MATCHER), VERSION_MUTATOR_METHOD, false)); } }