package org.springframework.roo.addon.dod;
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.FIND_ENTRIES_METHOD;
import static org.springframework.roo.classpath.customdata.CustomDataKeys.FIND_METHOD;
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.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_DATA_ON_DEMAND;
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 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.configurable.ConfigurableMetadataProvider;
import org.springframework.roo.classpath.PhysicalTypeIdentifier;
import org.springframework.roo.classpath.PhysicalTypeMetadata;
import org.springframework.roo.classpath.customdata.CustomDataKeys;
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.ItdTypeDetailsProvidingMetadataItem;
import org.springframework.roo.classpath.layers.LayerService;
import org.springframework.roo.classpath.layers.LayerType;
import org.springframework.roo.classpath.layers.MemberTypeAdditions;
import org.springframework.roo.classpath.layers.MethodParameter;
import org.springframework.roo.classpath.scanner.MemberDetails;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.shell.NaturalOrderComparator;
/**
* Implementation of {@link DataOnDemandMetadataProvider}.
*
* @author Ben Alex
* @author Greg Turnquist
* @author Andrew Swan
* @since 1.0
*/
@Component(immediate = true)
@Service
public class DataOnDemandMetadataProviderImpl extends
AbstractMemberDiscoveringItdMetadataProvider implements
DataOnDemandMetadataProvider {
private static final String FLUSH_METHOD = CustomDataKeys.FLUSH_METHOD
.name();
private static final String PERSIST_METHOD = CustomDataKeys.PERSIST_METHOD
.name();
@Reference private ConfigurableMetadataProvider configurableMetadataProvider;
@Reference private LayerService layerService;
private final Map<String, JavaType> dodMidToEntityMap = new LinkedHashMap<String, JavaType>();
private final Map<JavaType, String> entityToDodMidMap = new LinkedHashMap<JavaType, String>();
protected void activate(final ComponentContext context) {
metadataDependencyRegistry.addNotificationListener(this);
metadataDependencyRegistry.registerDependency(
PhysicalTypeIdentifier.getMetadataIdentiferType(),
getProvidesType());
// DOD classes are @Configurable because they may need DI of other DOD
// classes that provide M:1 relationships
configurableMetadataProvider.addMetadataTrigger(ROO_DATA_ON_DEMAND);
addMetadataTrigger(ROO_DATA_ON_DEMAND);
}
@Override
protected String createLocalIdentifier(final JavaType javaType,
final LogicalPath path) {
return DataOnDemandMetadata.createIdentifier(javaType, path);
}
protected void deactivate(final ComponentContext context) {
metadataDependencyRegistry.removeNotificationListener(this);
metadataDependencyRegistry.deregisterDependency(
PhysicalTypeIdentifier.getMetadataIdentiferType(),
getProvidesType());
configurableMetadataProvider.removeMetadataTrigger(ROO_DATA_ON_DEMAND);
removeMetadataTrigger(ROO_DATA_ON_DEMAND);
}
private String getDataOnDemandMetadataId(final JavaType javaType,
final Iterable<ClassOrInterfaceTypeDetails> dataOnDemandTypes) {
for (final ClassOrInterfaceTypeDetails cid : dataOnDemandTypes) {
final AnnotationMetadata dodAnnotation = cid
.getAnnotation(ROO_DATA_ON_DEMAND);
final AnnotationAttributeValue<JavaType> entityAttribute = dodAnnotation
.getAttribute("entity");
if (entityAttribute != null
&& entityAttribute.getValue().equals(javaType)) {
// Found the DoD type for the given field's type
return DataOnDemandMetadata.createIdentifier(cid.getName(),
PhysicalTypeIdentifier.getPath(cid
.getDeclaredByMetadataId()));
}
}
return null;
}
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()))) {
metadataDependencyRegistry.registerDependency(
field.getDeclaredByMetadataId(),
metadataIdentificationString);
fields.add(field);
}
}
embeddedHolders.add(new EmbeddedHolder(embeddedField, fields));
}
return embeddedHolders;
}
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()))) {
metadataDependencyRegistry.registerDependency(
field.getDeclaredByMetadataId(),
metadataIdentificationString);
idFields.add(field);
}
}
return new EmbeddedIdHolder(embeddedIdField, idFields);
}
@Override
protected String getGovernorPhysicalTypeIdentifier(
final String metadataIdentificationString) {
final JavaType javaType = DataOnDemandMetadata
.getJavaType(metadataIdentificationString);
final LogicalPath path = DataOnDemandMetadata
.getPath(metadataIdentificationString);
return PhysicalTypeIdentifier.createIdentifier(javaType, path);
}
public String getItdUniquenessFilenameSuffix() {
return "DataOnDemand";
}
@Override
protected String getLocalMidToRequest(final ItdTypeDetails itdTypeDetails) {
// Determine the governor for this ITD, and whether any DOD metadata is
// even hoping to hear about changes to that JavaType and its ITDs
final JavaType governor = itdTypeDetails.getName();
for (final JavaType type : itdTypeDetails.getGovernor()
.getLayerEntities()) {
final String localMidType = entityToDodMidMap.get(type);
if (localMidType != null) {
return localMidType;
}
}
final String localMid = entityToDodMidMap.get(governor);
if (localMid == null) {
// No DOD is interested in this JavaType, so let's move on
return null;
}
// We have some DOD metadata, so let's check if we care if any methods
// match our requirements
for (final MethodMetadata method : itdTypeDetails.getDeclaredMethods()) {
if (BeanInfoUtils.isMutatorMethod(method)) {
// A DOD cares about the JavaType, and an ITD offers a method
// likely of interest, so let's formally trigger it to run.
// Note that it will re-scan and discover this ITD, and register
// a direct dependency on it for the future.
return localMid;
}
}
return null;
}
private Map<FieldMetadata, DataOnDemandMetadata> getLocatedFields(
final MemberDetails memberDetails, final String dodMetadataId) {
final Map<FieldMetadata, DataOnDemandMetadata> locatedFields = new LinkedHashMap<FieldMetadata, DataOnDemandMetadata>();
final Iterable<ClassOrInterfaceTypeDetails> dataOnDemandTypes = typeLocationService
.findClassesOrInterfaceDetailsWithAnnotation(ROO_DATA_ON_DEMAND);
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)
metadataDependencyRegistry.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 DataOnDemandMetadata collaboratingMetadata = locateCollaboratingMetadata(
dodMetadataId, field, dataOnDemandTypes);
locatedFields.put(field, collaboratingMetadata);
}
return locatedFields;
}
@Override
protected ItdTypeDetailsProvidingMetadataItem getMetadata(
final String dodMetadataId, final JavaType aspectName,
final PhysicalTypeMetadata governorPhysicalTypeMetadata,
final String itdFilename) {
// We need to parse the annotation, which we expect to be present
final DataOnDemandAnnotationValues annotationValues = new DataOnDemandAnnotationValues(
governorPhysicalTypeMetadata);
final JavaType entity = annotationValues.getEntity();
if (!annotationValues.isAnnotationFound() || entity == null) {
return null;
}
// Remember that this entity JavaType matches up with this DOD's
// metadata identification string
// Start by clearing the previous association
final JavaType oldEntity = dodMidToEntityMap.get(dodMetadataId);
if (oldEntity != null) {
entityToDodMidMap.remove(oldEntity);
}
entityToDodMidMap.put(annotationValues.getEntity(), dodMetadataId);
dodMidToEntityMap.put(dodMetadataId, annotationValues.getEntity());
final JavaType identifierType = persistenceMemberLocator
.getIdentifierType(entity);
if (identifierType == 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
metadataDependencyRegistry.registerDependency(
persistenceMemberHoldingTypeDetails.getDeclaredByMetadataId(),
dodMetadataId);
// Get the additions to make for each required method
final MethodParameter fromParameter = new MethodParameter(
JavaType.INT_PRIMITIVE, "from");
final MethodParameter toParameter = new MethodParameter(
JavaType.INT_PRIMITIVE, "to");
final MemberTypeAdditions findEntriesMethod = layerService
.getMemberTypeAdditions(dodMetadataId,
FIND_ENTRIES_METHOD.name(), entity, identifierType,
LayerType.HIGHEST.getPosition(), fromParameter,
toParameter);
final MemberTypeAdditions findMethodAdditions = layerService
.getMemberTypeAdditions(dodMetadataId, FIND_METHOD.name(),
entity, identifierType,
LayerType.HIGHEST.getPosition(), new MethodParameter(
identifierType, "id"));
final MethodParameter entityParameter = new MethodParameter(entity,
"obj");
final MemberTypeAdditions flushMethod = layerService
.getMemberTypeAdditions(dodMetadataId, FLUSH_METHOD, entity,
identifierType, LayerType.HIGHEST.getPosition(),
entityParameter);
final MethodMetadata identifierAccessor = memberDetails
.getMostConcreteMethodWithTag(IDENTIFIER_ACCESSOR_METHOD);
final MemberTypeAdditions persistMethodAdditions = layerService
.getMemberTypeAdditions(dodMetadataId, PERSIST_METHOD, entity,
identifierType, LayerType.HIGHEST.getPosition(),
entityParameter);
if (findEntriesMethod == null || findMethodAdditions == null
|| identifierAccessor == null || persistMethodAdditions == null) {
return null;
}
// Identify all the fields we care about on the entity
final Map<FieldMetadata, DataOnDemandMetadata> locatedFields = getLocatedFields(
memberDetails, dodMetadataId);
// Get the embedded identifier metadata holder - may be null if no
// embedded identifier exists
final EmbeddedIdHolder embeddedIdHolder = getEmbeddedIdHolder(
memberDetails, dodMetadataId);
// Get the list of embedded metadata holders - may be an empty list if
// no embedded identifier exists
final List<EmbeddedHolder> embeddedHolders = getEmbeddedHolders(
memberDetails, dodMetadataId);
return new DataOnDemandMetadata(dodMetadataId, aspectName,
governorPhysicalTypeMetadata, annotationValues,
identifierAccessor, findMethodAdditions, findEntriesMethod,
persistMethodAdditions, flushMethod, locatedFields,
identifierType, embeddedIdHolder, embeddedHolders);
}
public String getProvidesType() {
return DataOnDemandMetadata.getMetadataIdentiferType();
}
/**
* Returns the {@link DataOnDemandMetadata} for the entity that's the target
* of the given reference field.
*
* @param dodMetadataId
* @param field
* @param dataOnDemandTypes
* @return <code>null</code> if it's not an n:1 or 1:1 field, or the DoD
* metadata is simply not available
*/
private DataOnDemandMetadata locateCollaboratingMetadata(
final String dodMetadataId, final FieldMetadata field,
final Iterable<ClassOrInterfaceTypeDetails> dataOnDemandTypes) {
if (!(field.getCustomData().keySet().contains(MANY_TO_ONE_FIELD) || field
.getCustomData().keySet().contains(ONE_TO_ONE_FIELD))) {
return null;
}
final String otherDodMetadataId = getDataOnDemandMetadataId(
field.getFieldType(), dataOnDemandTypes);
if (otherDodMetadataId == null
|| otherDodMetadataId.equals(dodMetadataId)) {
// 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(dodMetadataId, field.getFieldType());
return (DataOnDemandMetadata) metadataService.get(otherDodMetadataId);
}
private void registerDependencyUponType(final String dodMetadataId,
final JavaType type) {
final String fieldPhysicalTypeId = typeLocationService
.getPhysicalTypeIdentifier(type);
metadataDependencyRegistry.registerDependency(fieldPhysicalTypeId,
dodMetadataId);
}
}