package org.springframework.roo.addon.web.mvc.controller.converter; import static org.springframework.roo.model.RooJavaType.ROO_CONVERSION_SERVICE; import static org.springframework.roo.model.RooJavaType.ROO_WEB_SCAFFOLD; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; 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.json.CustomDataJsonTags; import org.springframework.roo.addon.web.mvc.controller.scaffold.WebScaffoldAnnotationValues; import org.springframework.roo.addon.web.mvc.controller.scaffold.WebScaffoldMetadata; import org.springframework.roo.classpath.PhysicalTypeIdentifier; import org.springframework.roo.classpath.PhysicalTypeIdentifierNamingUtils; 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.MemberFindingUtils; import org.springframework.roo.classpath.details.MethodMetadata; import org.springframework.roo.classpath.itd.AbstractItdMetadataProvider; 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.metadata.MetadataIdentificationUtils; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.model.JavaType; import org.springframework.roo.project.LogicalPath; /** * Implementation of {@link ConversionServiceMetadataProvider}. * * @author Rossen Stoyanchev * @author Stefan Schmidt * @since 1.1.1 */ @Component(immediate = true) @Service public class ConversionServiceMetadataProviderImpl extends AbstractItdMetadataProvider implements ConversionServiceMetadataProvider { // Stores the MID (as accepted by this // ConversionServiceMetadataProviderImpl) for the one (and only one) // application-wide conversion service private String applicationConversionServiceFactoryBeanMid; @Reference private LayerService layerService; protected void activate(final ComponentContext context) { metadataDependencyRegistry.registerDependency( PhysicalTypeIdentifier.getMetadataIdentiferType(), getProvidesType()); metadataDependencyRegistry.registerDependency( WebScaffoldMetadata.getMetadataIdentiferType(), getProvidesType()); addMetadataTrigger(ROO_CONVERSION_SERVICE); } @Override protected String createLocalIdentifier(final JavaType javaType, final LogicalPath path) { return PhysicalTypeIdentifierNamingUtils.createIdentifier( ConversionServiceMetadata.class.getName(), javaType, path); } protected void deactivate(final ComponentContext context) { metadataDependencyRegistry.deregisterDependency( PhysicalTypeIdentifier.getMetadataIdentiferType(), getProvidesType()); metadataDependencyRegistry.deregisterDependency( WebScaffoldMetadata.getMetadataIdentiferType(), getProvidesType()); removeMetadataTrigger(ROO_CONVERSION_SERVICE); } @Override protected String getGovernorPhysicalTypeIdentifier(final String metadataId) { final JavaType javaType = PhysicalTypeIdentifierNamingUtils .getJavaType(ConversionServiceMetadata.class.getName(), metadataId); final LogicalPath path = PhysicalTypeIdentifierNamingUtils.getPath( ConversionServiceMetadata.class.getName(), metadataId); return PhysicalTypeIdentifier.createIdentifier(javaType, path); } public String getItdUniquenessFilenameSuffix() { return "ConversionService"; } @Override protected ItdTypeDetailsProvidingMetadataItem getMetadata( final String metadataIdentificationString, final JavaType aspectName, final PhysicalTypeMetadata governorPhysicalTypeMetadata, final String itdFilename) { applicationConversionServiceFactoryBeanMid = metadataIdentificationString; // To get here we know the governor is the // ApplicationConversionServiceFactoryBean so let's go ahead and create // its ITD final Map<JavaType, Map<Object, JavaSymbolName>> compositePrimaryKeyTypes = new HashMap<JavaType, Map<Object, JavaSymbolName>>(); final Set<JavaType> relevantDomainTypes = new LinkedHashSet<JavaType>(); final Map<JavaType, MemberTypeAdditions> findMethods = new HashMap<JavaType, MemberTypeAdditions>(); final Map<JavaType, JavaType> idTypes = new HashMap<JavaType, JavaType>(); final Map<JavaType, List<MethodMetadata>> toStringMethods = new HashMap<JavaType, List<MethodMetadata>>(); for (final ClassOrInterfaceTypeDetails controllerTypeDetails : typeLocationService .findClassesOrInterfaceDetailsWithAnnotation(ROO_WEB_SCAFFOLD)) { metadataDependencyRegistry.registerDependency( controllerTypeDetails.getDeclaredByMetadataId(), metadataIdentificationString); final WebScaffoldAnnotationValues webScaffoldAnnotationValues = new WebScaffoldAnnotationValues( controllerTypeDetails); final JavaType formBackingObject = webScaffoldAnnotationValues .getFormBackingObject(); if (formBackingObject == null) { continue; } final MemberDetails memberDetails = getMemberDetails(formBackingObject); // Find composite primary key types requiring a converter final List<FieldMetadata> embeddedIdFields = MemberFindingUtils .getFieldsWithTag(memberDetails, CustomDataKeys.EMBEDDED_ID_FIELD); if (embeddedIdFields.size() > 1) { throw new IllegalStateException( "Found multiple embedded ID fields in " + formBackingObject.getFullyQualifiedTypeName() + " type. Only one is allowed."); } else if (embeddedIdFields.size() == 1) { final Map<Object, JavaSymbolName> jsonMethodNames = new LinkedHashMap<Object, JavaSymbolName>(); final MemberDetails fieldMemberDetails = getMemberDetails(embeddedIdFields .get(0).getFieldType()); final MethodMetadata fromJsonMethod = MemberFindingUtils .getMostConcreteMethodWithTag(fieldMemberDetails, CustomDataJsonTags.FROM_JSON_METHOD); if (fromJsonMethod != null) { jsonMethodNames.put(CustomDataJsonTags.FROM_JSON_METHOD, fromJsonMethod.getMethodName()); final MethodMetadata toJsonMethod = MemberFindingUtils .getMostConcreteMethodWithTag(fieldMemberDetails, CustomDataJsonTags.TO_JSON_METHOD); if (toJsonMethod != null) { jsonMethodNames.put(CustomDataJsonTags.TO_JSON_METHOD, toJsonMethod.getMethodName()); compositePrimaryKeyTypes.put(embeddedIdFields.get(0) .getFieldType(), jsonMethodNames); } } } final JavaType identifierType = persistenceMemberLocator .getIdentifierType(formBackingObject); if (identifierType == null) { // This type either has no ID field (e.g. an embedded type) or // it's ID type is unknown right now; // don't generate a converter for it; this will happen later if // and when the ID field becomes known. continue; } relevantDomainTypes.add(formBackingObject); idTypes.put(formBackingObject, identifierType); final MemberTypeAdditions findMethod = layerService .getMemberTypeAdditions(metadataIdentificationString, CustomDataKeys.FIND_METHOD.name(), formBackingObject, identifierType, LayerType.HIGHEST.getPosition(), new MethodParameter(identifierType, "id")); findMethods.put(formBackingObject, findMethod); toStringMethods.put( formBackingObject, getToStringMethods(memberDetails, metadataIdentificationString)); } return new ConversionServiceMetadata(metadataIdentificationString, aspectName, governorPhysicalTypeMetadata, findMethods, idTypes, relevantDomainTypes, compositePrimaryKeyTypes, toStringMethods); } public String getProvidesType() { return MetadataIdentificationUtils .create(ConversionServiceMetadata.class.getName()); } private List<MethodMetadata> getToStringMethods( final MemberDetails memberDetails, final String metadataIdentificationString) { final List<MethodMetadata> toStringMethods = new ArrayList<MethodMetadata>(); int counter = 0; for (final MethodMetadata method : memberDetails.getMethods()) { // Track any changes to that method (eg it goes away) metadataDependencyRegistry.registerDependency( method.getDeclaredByMetadataId(), metadataIdentificationString); if (counter < 4 && isMethodOfInterest(method, memberDetails)) { counter++; toStringMethods.add(method); } } return toStringMethods; } private boolean isMethodOfInterest(final MethodMetadata method, final MemberDetails memberDetails) { if (!BeanInfoUtils.isAccessorMethod(method)) { return false; // Only interested in accessors } if (method.getCustomData().keySet() .contains(CustomDataKeys.IDENTIFIER_ACCESSOR_METHOD) || method.getCustomData().keySet() .contains(CustomDataKeys.VERSION_ACCESSOR_METHOD)) { return false; // Only interested in methods which are not accessors // for persistence id or version fields } final FieldMetadata field = BeanInfoUtils.getFieldForJavaBeanMethod( memberDetails, method); if (field == null) { return false; } final JavaType fieldType = field.getFieldType(); if (fieldType.isCommonCollectionType() || fieldType.isArray() // Exclude collections and arrays || typeLocationService.isInProject(fieldType) // Exclude // references to // other domain // objects as they // are too verbose || fieldType.equals(JavaType.BOOLEAN_PRIMITIVE) || fieldType.equals(JavaType.BOOLEAN_OBJECT) // Exclude boolean // values as they // would not be // meaningful in // this // presentation || field.getCustomData().keySet() .contains(CustomDataKeys.EMBEDDED_FIELD) /* * Not * interested * in embedded * types */) { return false; } return true; } @Override protected String resolveDownstreamDependencyIdentifier( final String upstreamDependency) { if (MetadataIdentificationUtils.getMetadataClass(upstreamDependency) .equals(MetadataIdentificationUtils .getMetadataClass(WebScaffoldMetadata .getMetadataIdentiferType()))) { // A WebScaffoldMetadata upstream MID has changed or become // available for the first time // It's OK to return null if we don't yet know the MID because its // JavaType has never been found return applicationConversionServiceFactoryBeanMid; } // It wasn't a WebScaffoldMetadata, so we can let the superclass handle // it // (it's expected it would be a PhysicalTypeIdentifier notification, as // that's the only other thing we registered to receive) return super.resolveDownstreamDependencyIdentifier(upstreamDependency); } }