package org.springframework.roo.addon.web.mvc.controller.json;
import static org.springframework.roo.model.RooJavaType.ROO_WEB_JSON;
import static org.springframework.roo.model.RooJavaType.ROO_WEB_SCAFFOLD;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
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.Reference;
import org.apache.felix.scr.annotations.Service;
import org.osgi.service.component.ComponentContext;
import org.springframework.roo.addon.json.JsonMetadata;
import org.springframework.roo.addon.plural.PluralMetadata;
import org.springframework.roo.addon.web.mvc.controller.details.FinderMetadataDetails;
import org.springframework.roo.addon.web.mvc.controller.details.JavaTypePersistenceMetadataDetails;
import org.springframework.roo.addon.web.mvc.controller.details.WebMetadataService;
import org.springframework.roo.classpath.PhysicalTypeIdentifier;
import org.springframework.roo.classpath.PhysicalTypeMetadata;
import org.springframework.roo.classpath.customdata.CustomDataKeys;
import org.springframework.roo.classpath.customdata.tagkeys.MethodMetadataCustomDataKey;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
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.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.itd.AbstractMemberDiscoveringItdMetadataProvider;
import org.springframework.roo.classpath.itd.ItdTypeDetailsProvidingMetadataItem;
import org.springframework.roo.classpath.layers.MemberTypeAdditions;
import org.springframework.roo.classpath.scanner.MemberDetails;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.project.LogicalPath;
/**
* Implementation of {@link WebJsonMetadataProvider}.
*
* @author Stefan Schmidt
* @since 1.1.3
*/
@Component(immediate = true)
@Service
public class WebJsonMetadataProviderImpl extends
AbstractMemberDiscoveringItdMetadataProvider implements
WebJsonMetadataProvider {
@Reference private WebMetadataService webMetadataService;
// Maps entities to the IDs of their WebJsonMetadata
private final Map<JavaType, String> managedEntityTypes = new HashMap<JavaType, String>();
protected void activate(final ComponentContext context) {
metadataDependencyRegistry.addNotificationListener(this);
metadataDependencyRegistry.registerDependency(
PhysicalTypeIdentifier.getMetadataIdentiferType(),
getProvidesType());
addMetadataTrigger(ROO_WEB_JSON);
}
@Override
protected String createLocalIdentifier(final JavaType javaType,
final LogicalPath path) {
return WebJsonMetadata.createIdentifier(javaType, path);
}
protected void deactivate(final ComponentContext context) {
metadataDependencyRegistry.removeNotificationListener(this);
metadataDependencyRegistry.deregisterDependency(
PhysicalTypeIdentifier.getMetadataIdentiferType(),
getProvidesType());
removeMetadataTrigger(ROO_WEB_JSON);
}
@Override
protected String getGovernorPhysicalTypeIdentifier(
final String metadataIdentificationString) {
final JavaType javaType = WebJsonMetadata
.getJavaType(metadataIdentificationString);
final LogicalPath path = WebJsonMetadata
.getPath(metadataIdentificationString);
return PhysicalTypeIdentifier.createIdentifier(javaType, path);
}
public String getItdUniquenessFilenameSuffix() {
return "Controller_Json";
}
@Override
protected String getLocalMidToRequest(final ItdTypeDetails itdTypeDetails) {
final ClassOrInterfaceTypeDetails governorTypeDetails = typeLocationService
.getTypeDetails(itdTypeDetails.getName());
if (governorTypeDetails == null) {
return null;
}
// Check whether a relevant layer component has appeared, changed, or
// disappeared
final String localMidForLayerManagedEntity = getWebJsonMidIfLayerComponent(governorTypeDetails);
if (StringUtils.isNotBlank(localMidForLayerManagedEntity)) {
return localMidForLayerManagedEntity;
}
// Check whether the relevant MVC controller has appeared, changed, or
// disappeared
return getWebJsonMidIfMvcController(governorTypeDetails);
}
@Override
protected ItdTypeDetailsProvidingMetadataItem getMetadata(
final String metadataIdentificationString,
final JavaType aspectName,
final PhysicalTypeMetadata governorPhysicalTypeMetadata,
final String itdFilename) {
// We need to parse the annotation, which we expect to be present
final WebJsonAnnotationValues annotationValues = new WebJsonAnnotationValues(
governorPhysicalTypeMetadata);
if (!annotationValues.isAnnotationFound()
|| annotationValues.getJsonObject() == null
|| governorPhysicalTypeMetadata.getMemberHoldingTypeDetails() == null) {
return null;
}
// Lookup the form backing object's metadata
final JavaType jsonObject = annotationValues.getJsonObject();
final ClassOrInterfaceTypeDetails jsonTypeDetails = typeLocationService
.getTypeDetails(jsonObject);
if (jsonTypeDetails == null) {
return null;
}
final LogicalPath jsonObjectPath = PhysicalTypeIdentifier
.getPath(jsonTypeDetails.getDeclaredByMetadataId());
final JsonMetadata jsonMetadata = (JsonMetadata) metadataService
.get(JsonMetadata.createIdentifier(jsonObject, jsonObjectPath));
if (jsonMetadata == null) {
return null;
}
final PhysicalTypeMetadata backingObjectPhysicalTypeMetadata = (PhysicalTypeMetadata) metadataService
.get(PhysicalTypeIdentifier.createIdentifier(jsonObject,
typeLocationService.getTypePath(jsonObject)));
Validate.notNull(backingObjectPhysicalTypeMetadata,
"Unable to obtain physical type metadata for type "
+ jsonObject.getFullyQualifiedTypeName());
final MemberDetails formBackingObjectMemberDetails = getMemberDetails(backingObjectPhysicalTypeMetadata);
final MemberHoldingTypeDetails backingMemberHoldingTypeDetails = MemberFindingUtils
.getMostConcreteMemberHoldingTypeDetailsWithTag(
formBackingObjectMemberDetails,
CustomDataKeys.PERSISTENT_TYPE);
if (backingMemberHoldingTypeDetails == null) {
return null;
}
// We need to be informed if our dependent metadata changes
metadataDependencyRegistry.registerDependency(
backingMemberHoldingTypeDetails.getDeclaredByMetadataId(),
metadataIdentificationString);
final Set<FinderMetadataDetails> finderDetails = webMetadataService
.getDynamicFinderMethodsAndFields(jsonObject,
formBackingObjectMemberDetails,
metadataIdentificationString);
if (finderDetails == null) {
return null;
}
final Map<MethodMetadataCustomDataKey, MemberTypeAdditions> persistenceAdditions = webMetadataService
.getCrudAdditions(jsonObject, metadataIdentificationString);
final JavaTypePersistenceMetadataDetails javaTypePersistenceMetadataDetails = webMetadataService
.getJavaTypePersistenceMetadataDetails(jsonObject,
getMemberDetails(jsonObject),
metadataIdentificationString);
final PluralMetadata pluralMetadata = (PluralMetadata) metadataService
.get(PluralMetadata.createIdentifier(jsonObject,
typeLocationService.getTypePath(jsonObject)));
if (persistenceAdditions.isEmpty()
|| javaTypePersistenceMetadataDetails == null
|| pluralMetadata == null) {
return null;
}
// Maintain a list of entities that are being tested
managedEntityTypes.put(jsonObject, metadataIdentificationString);
return new WebJsonMetadata(metadataIdentificationString, aspectName,
governorPhysicalTypeMetadata, annotationValues,
persistenceAdditions,
javaTypePersistenceMetadataDetails.getIdentifierField(),
pluralMetadata.getPlural(), finderDetails, jsonMetadata,
introduceLayerComponents(governorPhysicalTypeMetadata));
}
public String getProvidesType() {
return WebJsonMetadata.getMetadataIdentiferType();
}
/**
* If the given type is a layer component (e.g. repository or service),
* returns the ID of the WebJsonMetadata for the first (!) domain type it
* manages, in case this is a new layer component that the JSON ITD needs to
* use.
*
* @param governorTypeDetails the type to check (required)
*/
private String getWebJsonMidIfLayerComponent(
final ClassOrInterfaceTypeDetails governorTypeDetails) {
for (final JavaType domainType : governorTypeDetails.getLayerEntities()) {
final String webJsonMetadataId = managedEntityTypes.get(domainType);
if (webJsonMetadataId != null) {
return webJsonMetadataId;
}
}
return null;
}
/**
* If the given type is a web MVC controller, returns the ID of the
* WebJsonMetadata for its form backing type, to ensure that any required
* layer components are injected. This is a workaround to AspectJ not
* handling multiple ITDs introducing the same field (in our case the layer
* component) into one Java class.
*
* @param governorTypeDetails the type to check (required)
*/
private String getWebJsonMidIfMvcController(
final ClassOrInterfaceTypeDetails governorTypeDetails) {
final AnnotationMetadata controllerAnnotation = governorTypeDetails
.getAnnotation(ROO_WEB_SCAFFOLD);
if (controllerAnnotation != null) {
final JavaType formBackingType = (JavaType) controllerAnnotation
.getAttribute("formBackingObject").getValue();
final String webJsonMetadataId = managedEntityTypes
.get(formBackingType);
if (webJsonMetadataId != null) {
/*
* We've been notified of a change to an MVC controller for
* whose backing object we produce WebJsonMetadata; refresh that
* MD to ensure our ITD does or does not introduce any required
* layer components, as appropriate.
*/
metadataService.get(webJsonMetadataId);
}
}
return null;
}
/**
* Indicates whether the web JSON ITD should introduce any required layer
* components (services, repositories, etc.). This information is necessary
* for so long as AspectJ does not allow the same field to be introduced
* into a given Java class by more than one ITD.
*
* @param governor the governor, i.e. the controller (required)
* @return see above
*/
private boolean introduceLayerComponents(final PhysicalTypeMetadata governor) {
// If no MVC ITD is going to be created, we have to introduce any
// required layer components
return MemberFindingUtils.getAnnotationOfType(governor
.getMemberHoldingTypeDetails().getAnnotations(),
ROO_WEB_SCAFFOLD) == null;
}
}