package org.springframework.roo.addon.web.mvc.controller.addon.config;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.osgi.service.component.ComponentContext;
import org.springframework.roo.addon.jpa.addon.entity.JpaEntityMetadata;
import org.springframework.roo.classpath.PhysicalTypeIdentifier;
import org.springframework.roo.classpath.PhysicalTypeMetadata;
import org.springframework.roo.classpath.customdata.taggers.CustomDataKeyDecorator;
import org.springframework.roo.classpath.customdata.taggers.CustomDataKeyDecoratorTracker;
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.MemberHoldingTypeDetails;
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.metadata.MetadataDependencyRegistry;
import org.springframework.roo.metadata.MetadataIdentificationUtils;
import org.springframework.roo.metadata.internal.MetadataDependencyRegistryTracker;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.model.JpaJavaType;
import org.springframework.roo.model.RooJavaType;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.support.logging.HandlerUtils;
/**
* Implementation of {@link JSONMixinMetadataProvider}.
*
* @author Jose Manuel Vivó
* @since 2.0
*/
@Component
@Service
public class JSONMixinMetadataProviderImpl extends AbstractMemberDiscoveringItdMetadataProvider
implements JSONMixinMetadataProvider {
protected final static Logger LOGGER = HandlerUtils
.getLogger(JSONMixinMetadataProviderImpl.class);
private final Map<JavaType, String> domainTypeToServiceMidMap =
new LinkedHashMap<JavaType, String>();
protected MetadataDependencyRegistryTracker registryTracker = null;
protected CustomDataKeyDecoratorTracker keyDecoratorTracker = null;
/**
* This service is being activated so setup it:
* <ul>
* <li>Create and open the {@link MetadataDependencyRegistryTracker}.</li>
* <li>Create and open the {@link CustomDataKeyDecoratorTracker}.</li>
* <li>Registers {@link RooJavaType#ROO_JSON_MIXIN} as additional
* JavaType that will trigger metadata registration.</li>
* <li>Set ensure the governor type details represent a class.</li>
* </ul>
*/
@Override
protected void activate(final ComponentContext cContext) {
super.activate(cContext);
context = cContext.getBundleContext();
this.registryTracker =
new MetadataDependencyRegistryTracker(context, this,
PhysicalTypeIdentifier.getMetadataIdentiferType(), getProvidesType());
this.registryTracker.open();
addMetadataTrigger(RooJavaType.ROO_JSON_MIXIN);
}
/**
* 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(RooJavaType.ROO_JSON_MIXIN);
CustomDataKeyDecorator keyDecorator = this.keyDecoratorTracker.getService();
keyDecorator.unregisterMatchers(getClass());
this.keyDecoratorTracker.close();
}
@Override
protected String createLocalIdentifier(final JavaType javaType, final LogicalPath path) {
return JSONMixinMetadata.createIdentifier(javaType, path);
}
@Override
protected String getGovernorPhysicalTypeIdentifier(final String metadataIdentificationString) {
final JavaType javaType = JSONMixinMetadata.getJavaType(metadataIdentificationString);
final LogicalPath path = JSONMixinMetadata.getPath(metadataIdentificationString);
return PhysicalTypeIdentifier.createIdentifier(javaType, path);
}
public String getItdUniquenessFilenameSuffix() {
return "JSONMixin";
}
@Override
protected String getLocalMidToRequest(final ItdTypeDetails itdTypeDetails) {
// Determine the governor for this ITD, and whether any metadata is even
// hoping to hear about changes to that JavaType and its ITDs
final JavaType governor = itdTypeDetails.getName();
final String localMid = domainTypeToServiceMidMap.get(governor);
if (localMid != null) {
return localMid;
}
final MemberHoldingTypeDetails memberHoldingTypeDetails =
getTypeLocationService().getTypeDetails(governor);
if (memberHoldingTypeDetails != null) {
for (final JavaType type : memberHoldingTypeDetails.getLayerEntities()) {
final String localMidType = domainTypeToServiceMidMap.get(type);
if (localMidType != null) {
return localMidType;
}
}
}
return null;
}
@Override
protected ItdTypeDetailsProvidingMetadataItem getMetadata(
final String metadataIdentificationString, final JavaType aspectName,
final PhysicalTypeMetadata governorPhysicalTypeMetadata, final String itdFilename) {
final JSONMixinAnnotationValues values =
new JSONMixinAnnotationValues(governorPhysicalTypeMetadata);
final JavaType mixinType = governorPhysicalTypeMetadata.getType();
final JavaType entity = values.getEntity();
final ClassOrInterfaceTypeDetails entityDetails =
getTypeLocationService().getTypeDetails(entity);
Validate.notNull(entityDetails, "Can't get details of '%s' defined on '%s.@%s.entity'",
entity.getFullyQualifiedTypeName(), mixinType,
RooJavaType.ROO_JSON_MIXIN.getSimpleTypeName());
Validate
.notNull(
entityDetails.getAnnotation(RooJavaType.ROO_JPA_ENTITY),
"Class '%s' defined on '%s.@%s.entity' has no @%s annotation. Only JPA entities can set as mixin",
entity.getFullyQualifiedTypeName(), mixinType,
RooJavaType.ROO_JSON_MIXIN.getSimpleTypeName());
final String entityId = JpaEntityMetadata.createIdentifier(entityDetails);
final JpaEntityMetadata entityMetadata = getMetadataService().get(entityId);
if (entityMetadata == null) {
// not ready for this metadata yet
return null;
}
// register metadata dependency
registerDependency(entityId, metadataIdentificationString);
// Register dependency with DomainModelModule
Set<ClassOrInterfaceTypeDetails> domainModelModuleDetails =
getTypeLocationService().findClassesOrInterfaceDetailsWithAnnotation(
RooJavaType.ROO_DOMAIN_MODEL_MODULE);
if (!domainModelModuleDetails.isEmpty()) {
String domainModelModuleMetadataId =
DomainModelModuleMetadata.createIdentifier(domainModelModuleDetails.iterator().next());
registerDependency(metadataIdentificationString, domainModelModuleMetadataId);
}
Map<FieldMetadata, JavaType> jsonDeserializerByEntity =
new TreeMap<FieldMetadata, JavaType>(FieldMetadata.COMPARATOR_BY_NAME);
for (FieldMetadata field : entityMetadata.getRelationsAsChild().values()) {
if (isAnyToOneRelation(field)) {
JavaType parentEntity = field.getFieldType();
JavaType entityDeserializer = getEntityDeserializerFor(parentEntity);
Validate.notNull(entityDeserializer,
"Can't locate class with @%s.entity=%s required for %s entity Json Mixin (%s)",
RooJavaType.ROO_DESERIALIZER, parentEntity, entity.getFullyQualifiedTypeName(),
mixinType.getFullyQualifiedTypeName());
jsonDeserializerByEntity.put(field, entityDeserializer);
}
}
return new JSONMixinMetadata(metadataIdentificationString, aspectName,
governorPhysicalTypeMetadata, values, entityMetadata, jsonDeserializerByEntity);
}
private JavaType getEntityDeserializerFor(JavaType entity) {
Set<ClassOrInterfaceTypeDetails> deserializers =
getTypeLocationService().findClassesOrInterfaceDetailsWithAnnotation(
RooJavaType.ROO_DESERIALIZER);
for (ClassOrInterfaceTypeDetails deserializer : deserializers) {
AnnotationMetadata annotation = deserializer.getAnnotation(RooJavaType.ROO_DESERIALIZER);
AnnotationAttributeValue<JavaType> annotationValue = annotation.getAttribute("entity");
if (entity.equals(annotationValue.getValue())) {
return deserializer.getType();
}
}
return null;
}
/**
* Return true if field is annotated with @OneToOne or @ManyToOne JPA annotation
*
* @param field
* @return
*/
private boolean isAnyToOneRelation(FieldMetadata field) {
return field.getAnnotation(JpaJavaType.MANY_TO_ONE) != null
|| field.getAnnotation(JpaJavaType.ONE_TO_ONE) != null;
}
public String getProvidesType() {
return JSONMixinMetadata.getMetadataIdentiferType();
}
private void registerDependency(final String upstreamDependency, final String downStreamDependency) {
if (getMetadataDependencyRegistry() != null
&& StringUtils.isNotBlank(upstreamDependency)
&& StringUtils.isNotBlank(downStreamDependency)
&& !upstreamDependency.equals(downStreamDependency)
&& !MetadataIdentificationUtils.getMetadataClass(downStreamDependency).equals(
MetadataIdentificationUtils.getMetadataClass(upstreamDependency))) {
getMetadataDependencyRegistry().registerDependency(upstreamDependency, downStreamDependency);
}
}
}