package org.springframework.roo.addon.jpa.addon.entity.factories;
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.IDENTIFIER_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_FIELD;
import static org.springframework.roo.model.RooJavaType.ROO_JPA_ENTITY_FACTORY;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.ComponentContext;
import org.springframework.roo.addon.configurable.addon.ConfigurableMetadataProvider;
import org.springframework.roo.addon.jpa.addon.dod.JpaDataOnDemandMetadata;
import org.springframework.roo.addon.jpa.addon.dod.EmbeddedHolder;
import org.springframework.roo.addon.jpa.addon.dod.EmbeddedIdHolder;
import org.springframework.roo.classpath.PhysicalTypeIdentifier;
import org.springframework.roo.classpath.PhysicalTypeMetadata;
import org.springframework.roo.classpath.details.BeanInfoUtils;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.FieldMetadata;
import org.springframework.roo.classpath.details.ItdTypeDetails;
import org.springframework.roo.classpath.details.MemberFindingUtils;
import org.springframework.roo.classpath.details.MemberHoldingTypeDetails;
import org.springframework.roo.classpath.details.MethodMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.itd.AbstractMemberDiscoveringItdMetadataProvider;
import org.springframework.roo.classpath.itd.ItdTriggerBasedMetadataProviderTracker;
import org.springframework.roo.classpath.itd.ItdTypeDetailsProvidingMetadataItem;
import org.springframework.roo.classpath.scanner.MemberDetails;
import org.springframework.roo.metadata.MetadataDependencyRegistry;
import org.springframework.roo.metadata.internal.MetadataDependencyRegistryTracker;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.model.RooJavaType;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.shell.NaturalOrderComparator;
import org.springframework.roo.support.logging.HandlerUtils;
import org.springframework.roo.support.osgi.ServiceInstaceManager;
/**
* Implementation of {@link JpaEntityFactoryMetadataProvider}.
*
* @author Sergio Clares
* @since 2.0
*/
@Component
@Service
public class JpaEntityFactoryMetadataProviderImpl extends
AbstractMemberDiscoveringItdMetadataProvider implements JpaEntityFactoryMetadataProvider {
protected final static Logger LOGGER = HandlerUtils
.getLogger(JpaEntityFactoryMetadataProviderImpl.class);
protected MetadataDependencyRegistryTracker registryTracker = null;
private ServiceInstaceManager serviceInstaceManager = new ServiceInstaceManager();
/**
* This service is being activated so setup it:
* <ul>
* <li>Create and open the {@link MetadataDependencyRegistryTracker}</li>
* <li>Create and open the {@link ItdTriggerBasedMetadataProviderTracker}
* to track for {@link ConfigurableMetadataProvider} service.</li>
* <li>Registers {@link RooJavaType#ROO_JPA_DATA_ON_DEMAND_CONFIGURATION} as
* additional JavaType that will trigger metadata registration.</li>
* </ul>
*/
@Override
protected void activate(final ComponentContext cContext) {
super.activate(cContext);
BundleContext localContext = cContext.getBundleContext();
this.registryTracker =
new MetadataDependencyRegistryTracker(localContext, this,
PhysicalTypeIdentifier.getMetadataIdentiferType(), getProvidesType());
this.registryTracker.open();
addMetadataTrigger(ROO_JPA_ENTITY_FACTORY);
serviceInstaceManager.activate(cContext.getBundleContext());
}
/**
* This service is being deactivated so unregister upstream-downstream
* dependencies, triggers, matchers and listeners.
*
* @param context
*/
protected void deactivate(final ComponentContext context) {
MetadataDependencyRegistry registry = this.registryTracker.getService();
registry.removeNotificationListener(this);
registry.deregisterDependency(PhysicalTypeIdentifier.getMetadataIdentiferType(),
getProvidesType());
this.registryTracker.close();
removeMetadataTrigger(ROO_JPA_ENTITY_FACTORY);
}
@Override
protected String createLocalIdentifier(final JavaType javaType, final LogicalPath path) {
return JpaEntityFactoryMetadata.createIdentifier(javaType, path);
}
@Override
protected String getGovernorPhysicalTypeIdentifier(final String metadataIdentificationString) {
final JavaType javaType = JpaEntityFactoryMetadata.getJavaType(metadataIdentificationString);
final LogicalPath path = JpaEntityFactoryMetadata.getPath(metadataIdentificationString);
return PhysicalTypeIdentifier.createIdentifier(javaType, path);
}
public String getItdUniquenessFilenameSuffix() {
return "JpaEntityFactory";
}
@Override
protected String getLocalMidToRequest(final ItdTypeDetails itdTypeDetails) {
return getLocalMid(itdTypeDetails);
}
@Override
protected ItdTypeDetailsProvidingMetadataItem getMetadata(final String entityFactoryMetadata,
final JavaType aspectName, final PhysicalTypeMetadata governorPhysicalTypeMetadata,
final String itdFilename) {
// Get related entity
final JavaType entity =
(JavaType) governorPhysicalTypeMetadata.getMemberHoldingTypeDetails()
.getAnnotation(ROO_JPA_ENTITY_FACTORY).getAttribute("entity").getValue();
if (entity == null) {
return null;
}
final MemberDetails memberDetails = getMemberDetails(entity);
if (memberDetails == null) {
return null;
}
final MemberHoldingTypeDetails persistenceMemberHoldingTypeDetails =
MemberFindingUtils.getMostConcreteMemberHoldingTypeDetailsWithTag(memberDetails,
PERSISTENT_TYPE);
if (persistenceMemberHoldingTypeDetails == null) {
return null;
}
// We need to be informed if our dependent metadata changes
getMetadataDependencyRegistry().registerDependency(
persistenceMemberHoldingTypeDetails.getDeclaredByMetadataId(), entityFactoryMetadata);
final JavaType identifierType = getPersistenceMemberLocator().getIdentifierType(entity);
if (identifierType == null) {
return null;
}
// Identify all the fields we care about on the entity
final Map<FieldMetadata, JpaEntityFactoryMetadata> locatedFields =
getLocatedFields(memberDetails, entityFactoryMetadata);
// Get the list of embedded metadata holders - may be an empty list if
// no embedded identifier exists
final List<EmbeddedHolder> embeddedHolders =
getEmbeddedHolders(memberDetails, entityFactoryMetadata);
// Get the embedded identifier metadata holder - may be null if no
// embedded identifier exists
final EmbeddedIdHolder embeddedIdHolder =
getEmbeddedIdHolder(memberDetails, entityFactoryMetadata);
Set<ClassOrInterfaceTypeDetails> entityFactoryClasses =
getTypeLocationService().findClassesOrInterfaceDetailsWithAnnotation(
RooJavaType.ROO_JPA_ENTITY_FACTORY);
return new JpaEntityFactoryMetadata(entityFactoryMetadata, aspectName,
governorPhysicalTypeMetadata, entity, locatedFields, embeddedHolders, entityFactoryClasses,
embeddedIdHolder);
}
public String getProvidesType() {
return JpaEntityFactoryMetadata.getMetadataIdentiferType();
}
private Map<FieldMetadata, JpaEntityFactoryMetadata> getLocatedFields(
final MemberDetails memberDetails, final String dodMetadataId) {
final Map<FieldMetadata, JpaEntityFactoryMetadata> locatedFields =
new LinkedHashMap<FieldMetadata, JpaEntityFactoryMetadata>();
final Iterable<ClassOrInterfaceTypeDetails> entityFactoryTypes =
getTypeLocationService()
.findClassesOrInterfaceDetailsWithAnnotation(ROO_JPA_ENTITY_FACTORY);
final List<MethodMetadata> mutatorMethods = memberDetails.getMethods();
// To avoid unnecessary rewriting of the DoD ITD we sort the mutators by
// method name to provide a consistent ordering
Collections.sort(mutatorMethods, new NaturalOrderComparator<MethodMetadata>() {
@Override
protected String stringify(final MethodMetadata object) {
return object.getMethodName().getSymbolName();
}
});
for (final MethodMetadata method : mutatorMethods) {
if (!BeanInfoUtils.isMutatorMethod(method)) {
continue;
}
final FieldMetadata field = BeanInfoUtils.getFieldForJavaBeanMethod(memberDetails, method);
if (field == null) {
continue;
}
// Track any changes to the mutator method (eg it goes away)
getMetadataDependencyRegistry().registerDependency(method.getDeclaredByMetadataId(),
dodMetadataId);
final Set<Object> fieldCustomDataKeys = field.getCustomData().keySet();
// Never include id or version fields (they shouldn't normally have
// a mutator anyway, but the user might have added one), or embedded
// types
if (fieldCustomDataKeys.contains(IDENTIFIER_FIELD)
|| fieldCustomDataKeys.contains(EMBEDDED_ID_FIELD)
|| fieldCustomDataKeys.contains(EMBEDDED_FIELD)
|| fieldCustomDataKeys.contains(VERSION_FIELD)) {
continue;
}
// Never include persistence transient fields
if (fieldCustomDataKeys.contains(TRANSIENT_FIELD)) {
continue;
}
// Never include any sort of collection; user has to make such
// entities by hand
if (field.getFieldType().isCommonCollectionType()
|| fieldCustomDataKeys.contains(ONE_TO_MANY_FIELD)
|| fieldCustomDataKeys.contains(MANY_TO_MANY_FIELD)) {
continue;
}
// Look up collaborating metadata
final JpaEntityFactoryMetadata collaboratingMetadata =
locateCollaboratingMetadata(dodMetadataId, field, entityFactoryTypes);
locatedFields.put(field, collaboratingMetadata);
}
return locatedFields;
}
private EmbeddedIdHolder getEmbeddedIdHolder(final MemberDetails memberDetails,
final String metadataIdentificationString) {
final List<FieldMetadata> idFields = new ArrayList<FieldMetadata>();
final List<FieldMetadata> fields =
MemberFindingUtils.getFieldsWithTag(memberDetails, EMBEDDED_ID_FIELD);
if (fields.isEmpty()) {
return null;
}
final FieldMetadata embeddedIdField = fields.get(0);
final MemberDetails identifierMemberDetails = getMemberDetails(embeddedIdField.getFieldType());
if (identifierMemberDetails == null) {
return null;
}
for (final FieldMetadata field : identifierMemberDetails.getFields()) {
if (!(Modifier.isStatic(field.getModifier()) || Modifier.isFinal(field.getModifier()) || Modifier
.isTransient(field.getModifier()))) {
getMetadataDependencyRegistry().registerDependency(field.getDeclaredByMetadataId(),
metadataIdentificationString);
idFields.add(field);
}
}
return new EmbeddedIdHolder(embeddedIdField, idFields);
}
/**
* Returns the {@link JpaEntityFactoryMetadata} for the entity that's the target
* of the given reference field.
*
* @param entityFactoryMetadataId
* @param field
* @param entityFactoryTypes
* @return <code>null</code> if it's not an n:1 or 1:1 field, or the DoD
* metadata is simply not available
*/
private JpaEntityFactoryMetadata locateCollaboratingMetadata(
final String entityFactoryMetadataId, final FieldMetadata field,
final Iterable<ClassOrInterfaceTypeDetails> entityFactoryTypes) {
if (!(field.getCustomData().keySet().contains(MANY_TO_ONE_FIELD) || field.getCustomData()
.keySet().contains(ONE_TO_ONE_FIELD))) {
return null;
}
final String otherEntityFactoryMetadataId =
getEntityFactoryMetadataId(field.getFieldType(), entityFactoryTypes);
if (otherEntityFactoryMetadataId == null
|| otherEntityFactoryMetadataId.equals(entityFactoryMetadataId)) {
// No DoD for this field's type, or it's a self-reference
return null;
}
// Make this DoD depend on the related entity (not its Dod, otherwise
// we get a circular MD dependency)
registerDependencyUponType(entityFactoryMetadataId, field.getFieldType());
return (JpaEntityFactoryMetadata) getMetadataService().get(otherEntityFactoryMetadataId);
}
private String getEntityFactoryMetadataId(final JavaType javaType,
final Iterable<ClassOrInterfaceTypeDetails> entityFactoryTypes) {
for (final ClassOrInterfaceTypeDetails cid : entityFactoryTypes) {
final AnnotationMetadata entityFactoryAnnotation = cid.getAnnotation(ROO_JPA_ENTITY_FACTORY);
final AnnotationAttributeValue<JavaType> entityAttribute =
entityFactoryAnnotation.getAttribute("entity");
if (entityAttribute != null && entityAttribute.getValue().equals(javaType)) {
// Found the DoD type for the given field's type
return JpaDataOnDemandMetadata.createIdentifier(cid.getName(),
PhysicalTypeIdentifier.getPath(cid.getDeclaredByMetadataId()));
}
}
return null;
}
private void registerDependencyUponType(final String entityFactoryMetadata, final JavaType type) {
final String fieldPhysicalTypeId = getTypeLocationService().getPhysicalTypeIdentifier(type);
getMetadataDependencyRegistry().registerDependency(fieldPhysicalTypeId, entityFactoryMetadata);
}
private List<EmbeddedHolder> getEmbeddedHolders(final MemberDetails memberDetails,
final String metadataIdentificationString) {
final List<EmbeddedHolder> embeddedHolders = new ArrayList<EmbeddedHolder>();
final List<FieldMetadata> embeddedFields =
MemberFindingUtils.getFieldsWithTag(memberDetails, EMBEDDED_FIELD);
if (embeddedFields.isEmpty()) {
return embeddedHolders;
}
for (final FieldMetadata embeddedField : embeddedFields) {
final MemberDetails embeddedMemberDetails = getMemberDetails(embeddedField.getFieldType());
if (embeddedMemberDetails == null) {
continue;
}
final List<FieldMetadata> fields = new ArrayList<FieldMetadata>();
for (final FieldMetadata field : embeddedMemberDetails.getFields()) {
if (!(Modifier.isStatic(field.getModifier()) || Modifier.isFinal(field.getModifier()) || Modifier
.isTransient(field.getModifier()))) {
getMetadataDependencyRegistry().registerDependency(field.getDeclaredByMetadataId(),
metadataIdentificationString);
fields.add(field);
}
}
embeddedHolders.add(new EmbeddedHolder(embeddedField, fields));
}
return embeddedHolders;
}
}