package org.springframework.roo.addon.jsf.managedbean; import static org.springframework.roo.addon.jsf.managedbean.JsfManagedBeanMetadata.APPLICATION_TYPE_FIELDS_KEY; import static org.springframework.roo.addon.jsf.managedbean.JsfManagedBeanMetadata.APPLICATION_TYPE_KEY; import static org.springframework.roo.addon.jsf.managedbean.JsfManagedBeanMetadata.CRUD_ADDITIONS_KEY; import static org.springframework.roo.addon.jsf.managedbean.JsfManagedBeanMetadata.ENUMERATED_KEY; import static org.springframework.roo.addon.jsf.managedbean.JsfManagedBeanMetadata.LIST_VIEW_FIELD_KEY; import static org.springframework.roo.addon.jsf.managedbean.JsfManagedBeanMetadata.PARAMETER_TYPE_KEY; import static org.springframework.roo.addon.jsf.managedbean.JsfManagedBeanMetadata.PARAMETER_TYPE_MANAGED_BEAN_NAME_KEY; import static org.springframework.roo.addon.jsf.managedbean.JsfManagedBeanMetadata.PARAMETER_TYPE_PLURAL_KEY; import static org.springframework.roo.classpath.customdata.CustomDataKeys.COUNT_ALL_METHOD; import static org.springframework.roo.classpath.customdata.CustomDataKeys.EMBEDDED_FIELD; import static org.springframework.roo.classpath.customdata.CustomDataKeys.FIND_ALL_METHOD; 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.MERGE_METHOD; import static org.springframework.roo.classpath.customdata.CustomDataKeys.PERSIST_METHOD; import static org.springframework.roo.classpath.customdata.CustomDataKeys.REMOVE_METHOD; import static org.springframework.roo.model.JavaType.BOOLEAN_OBJECT; import static org.springframework.roo.model.JavaType.BOOLEAN_PRIMITIVE; import static org.springframework.roo.model.JavaType.BYTE_ARRAY_PRIMITIVE; import static org.springframework.roo.model.JavaType.INT_PRIMITIVE; import static org.springframework.roo.model.JdkJavaType.DATE; import static org.springframework.roo.model.RooJavaType.ROO_JSF_MANAGED_BEAN; import java.util.ArrayList; import java.util.Collections; 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.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.configurable.ConfigurableMetadataProvider; import org.springframework.roo.addon.plural.PluralMetadata; import org.springframework.roo.classpath.PhysicalTypeCategory; 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.BeanInfoUtils; import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails; import org.springframework.roo.classpath.details.FieldMetadata; import org.springframework.roo.classpath.details.FieldMetadataBuilder; import org.springframework.roo.classpath.details.ItdTypeDetails; import org.springframework.roo.classpath.details.MemberHoldingTypeDetails; import org.springframework.roo.classpath.details.MethodMetadata; 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.CustomDataBuilder; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.model.JavaType; import org.springframework.roo.project.LogicalPath; /** * Implementation of {@link JsfManagedBeanMetadataProvider}. * * @author Alan Stewart * @since 1.2.0 */ @Component(immediate = true) @Service public class JsfManagedBeanMetadataProviderImpl extends AbstractMemberDiscoveringItdMetadataProvider implements JsfManagedBeanMetadataProvider { private static final int LAYER_POSITION = LayerType.HIGHEST.getPosition(); // -- The maximum number of fields to form a String to show in a drop down // field. private static final int MAX_DROP_DOWN_FIELDS = 4; // -- The maximum number of entity fields to show in a list view. private static final int MAX_LIST_VIEW_FIELDS = 5; @Reference private ConfigurableMetadataProvider configurableMetadataProvider; @Reference private LayerService layerService; private final Map<JavaType, String> entityToManagedBeanMidMap = new LinkedHashMap<JavaType, String>(); private final Map<String, JavaType> managedBeanMidToEntityMap = new LinkedHashMap<String, JavaType>(); protected void activate(final ComponentContext context) { metadataDependencyRegistry.addNotificationListener(this); metadataDependencyRegistry.registerDependency( PhysicalTypeIdentifier.getMetadataIdentiferType(), getProvidesType()); addMetadataTrigger(ROO_JSF_MANAGED_BEAN); configurableMetadataProvider.addMetadataTrigger(ROO_JSF_MANAGED_BEAN); } @Override protected String createLocalIdentifier(final JavaType javaType, final LogicalPath path) { return JsfManagedBeanMetadata.createIdentifier(javaType, path); } protected void deactivate(final ComponentContext context) { metadataDependencyRegistry.removeNotificationListener(this); metadataDependencyRegistry.deregisterDependency( PhysicalTypeIdentifier.getMetadataIdentiferType(), getProvidesType()); removeMetadataTrigger(ROO_JSF_MANAGED_BEAN); configurableMetadataProvider .removeMetadataTrigger(ROO_JSF_MANAGED_BEAN); } /** * Returns the additions to make to the generated ITD in order to invoke the * various CRUD methods of the given entity * * @param entity the target entity type (required) * @param metadataIdentificationString the ID of the metadata that's being * created (required) * @return a non-<code>null</code> map (may be empty if the CRUD methods are * indeterminable) */ private Map<MethodMetadataCustomDataKey, MemberTypeAdditions> getCrudAdditions( final JavaType entity, final String metadataIdentificationString) { metadataDependencyRegistry.registerDependency( typeLocationService.getPhysicalTypeIdentifier(entity), metadataIdentificationString); final List<FieldMetadata> idFields = persistenceMemberLocator .getIdentifierFields(entity); if (idFields.isEmpty()) { return Collections.emptyMap(); } final FieldMetadata identifierField = idFields.get(0); final JavaType identifierType = persistenceMemberLocator .getIdentifierType(entity); if (identifierType == null) { return Collections.emptyMap(); } metadataDependencyRegistry.registerDependency( identifierField.getDeclaredByMetadataId(), metadataIdentificationString); final JavaSymbolName entityName = JavaSymbolName .getReservedWordSafeName(entity); final MethodParameter entityParameter = new MethodParameter(entity, entityName); final MethodParameter idParameter = new MethodParameter(identifierType, "id"); final MethodParameter firstResultParameter = new MethodParameter( INT_PRIMITIVE, "firstResult"); final MethodParameter maxResultsParameter = new MethodParameter( INT_PRIMITIVE, "sizeNo"); final Map<MethodMetadataCustomDataKey, MemberTypeAdditions> additions = new HashMap<MethodMetadataCustomDataKey, MemberTypeAdditions>(); additions.put(COUNT_ALL_METHOD, layerService.getMemberTypeAdditions( metadataIdentificationString, COUNT_ALL_METHOD.name(), entity, identifierType, LAYER_POSITION)); additions.put(FIND_ALL_METHOD, layerService.getMemberTypeAdditions( metadataIdentificationString, FIND_ALL_METHOD.name(), entity, identifierType, LAYER_POSITION)); additions.put(FIND_ENTRIES_METHOD, layerService.getMemberTypeAdditions( metadataIdentificationString, FIND_ENTRIES_METHOD.name(), entity, identifierType, LAYER_POSITION, firstResultParameter, maxResultsParameter)); additions.put(FIND_METHOD, layerService.getMemberTypeAdditions( metadataIdentificationString, FIND_METHOD.name(), entity, identifierType, LAYER_POSITION, idParameter)); additions.put(MERGE_METHOD, layerService.getMemberTypeAdditions( metadataIdentificationString, MERGE_METHOD.name(), entity, identifierType, LAYER_POSITION, entityParameter)); additions.put(PERSIST_METHOD, layerService.getMemberTypeAdditions( metadataIdentificationString, PERSIST_METHOD.name(), entity, identifierType, LAYER_POSITION, entityParameter)); additions.put(REMOVE_METHOD, layerService.getMemberTypeAdditions( metadataIdentificationString, REMOVE_METHOD.name(), entity, identifierType, LAYER_POSITION, entityParameter)); return additions; } @Override protected String getGovernorPhysicalTypeIdentifier( final String metadataIdentificationString) { final JavaType javaType = JsfManagedBeanMetadata .getJavaType(metadataIdentificationString); final LogicalPath path = JsfManagedBeanMetadata .getPath(metadataIdentificationString); return PhysicalTypeIdentifier.createIdentifier(javaType, path); } public String getItdUniquenessFilenameSuffix() { return "ManagedBean"; } @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 = entityToManagedBeanMidMap.get(governor); if (localMid != null) { return localMid; } final MemberHoldingTypeDetails memberHoldingTypeDetails = typeLocationService .getTypeDetails(governor); if (memberHoldingTypeDetails != null) { for (final JavaType type : memberHoldingTypeDetails .getLayerEntities()) { final String localMidType = entityToManagedBeanMidMap.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) { // We need to parse the annotation, which we expect to be present final JsfManagedBeanAnnotationValues annotationValues = new JsfManagedBeanAnnotationValues( governorPhysicalTypeMetadata); final JavaType entity = annotationValues.getEntity(); if (!annotationValues.isAnnotationFound() || entity == null) { return null; } final MemberDetails memberDetails = getMemberDetails(entity); if (memberDetails == null) { return null; } final MethodMetadata identifierAccessor = persistenceMemberLocator .getIdentifierAccessor(entity); final MethodMetadata versionAccessor = persistenceMemberLocator .getVersionAccessor(entity); final Set<FieldMetadata> locatedFields = locateFields(entity, memberDetails, metadataIdentificationString, identifierAccessor, versionAccessor); // Remember that this entity JavaType matches up with this metadata // identification string // Start by clearing any previous association final JavaType oldEntity = managedBeanMidToEntityMap .get(metadataIdentificationString); if (oldEntity != null) { entityToManagedBeanMidMap.remove(oldEntity); } entityToManagedBeanMidMap.put(entity, metadataIdentificationString); managedBeanMidToEntityMap.put(metadataIdentificationString, entity); final String physicalTypeIdentifier = typeLocationService .getPhysicalTypeIdentifier(entity); final LogicalPath path = PhysicalTypeIdentifier .getPath(physicalTypeIdentifier); final PluralMetadata pluralMetadata = (PluralMetadata) metadataService .get(PluralMetadata.createIdentifier(entity, path)); Validate.notNull(pluralMetadata, "Could not determine plural for '" + entity.getSimpleTypeName() + "'"); final String plural = pluralMetadata.getPlural(); final Map<MethodMetadataCustomDataKey, MemberTypeAdditions> crudAdditions = getCrudAdditions( entity, metadataIdentificationString); return new JsfManagedBeanMetadata(metadataIdentificationString, aspectName, governorPhysicalTypeMetadata, annotationValues, plural, crudAdditions, locatedFields, identifierAccessor); } public String getProvidesType() { return JsfManagedBeanMetadata.getMetadataIdentiferType(); } private boolean isEnum(final ClassOrInterfaceTypeDetails cid) { return cid != null && cid.getPhysicalTypeCategory() == PhysicalTypeCategory.ENUMERATION; } private boolean isFieldOfInterest(final FieldMetadata field) { final JavaType fieldType = field.getFieldType(); return !fieldType.isCommonCollectionType() && !fieldType.isArray() // Exclude collections and arrays && !fieldType.equals(BOOLEAN_PRIMITIVE) // Boolean values would not be meaningful in this presentation && !fieldType.equals(BOOLEAN_OBJECT) && !fieldType.equals(BYTE_ARRAY_PRIMITIVE) // Not interested in embedded types && !field.getCustomData().keySet().contains(EMBEDDED_FIELD); } /** * Returns an iterable collection of the given entity's fields excluding any * ID or version field; along the way, flags the first * {@value #MAX_LIST_VIEW_FIELDS} non ID/version fields as being displayable * in the list view for this entity type. * * @param entity the entity for which to find the fields and accessors * (required) * @param memberDetails the entity's members (required) * @param metadataIdentificationString the ID of the metadata being * generated (required) * @param versionAccessor * @param identifierAccessor * @return a non-<code>null</code> iterable collection */ private Set<FieldMetadata> locateFields(final JavaType entity, final MemberDetails memberDetails, final String metadataIdentificationString, final MethodMetadata identifierAccessor, final MethodMetadata versionAccessor) { final Set<FieldMetadata> locatedFields = new LinkedHashSet<FieldMetadata>(); final Set<ClassOrInterfaceTypeDetails> managedBeanTypes = typeLocationService .findClassesOrInterfaceDetailsWithAnnotation(ROO_JSF_MANAGED_BEAN); int listViewFields = 0; for (final MethodMetadata method : memberDetails.getMethods()) { if (!BeanInfoUtils.isAccessorMethod(method)) { continue; } if (method.hasSameName(identifierAccessor, versionAccessor)) { continue; } final FieldMetadata field = BeanInfoUtils.getFieldForPropertyName( memberDetails, BeanInfoUtils.getPropertyNameForJavaBeanMethod(method)); if (field == null) { continue; } metadataDependencyRegistry.registerDependency( field.getDeclaredByMetadataId(), metadataIdentificationString); final CustomDataBuilder customDataBuilder = new CustomDataBuilder( field.getCustomData()); final JavaType fieldType = field.getFieldType(); if (fieldType.equals(DATE) && field.getFieldName().getSymbolName().equals("created")) { continue; } final ClassOrInterfaceTypeDetails fieldTypeCid = typeLocationService .getTypeDetails(fieldType); // Check field is to be displayed in the entity's list view if (listViewFields < MAX_LIST_VIEW_FIELDS && isFieldOfInterest(field) && fieldTypeCid == null) { listViewFields++; customDataBuilder.put(LIST_VIEW_FIELD_KEY, field); } final boolean enumerated = field.getCustomData().keySet() .contains(CustomDataKeys.ENUMERATED_FIELD) || isEnum(fieldTypeCid); if (enumerated) { customDataBuilder.put(ENUMERATED_KEY, null); } else { if (fieldType.isCommonCollectionType()) { parameterTypeLoop: for (final JavaType parameter : fieldType .getParameters()) { final ClassOrInterfaceTypeDetails parameterTypeCid = typeLocationService .getTypeDetails(parameter); if (parameterTypeCid == null) { continue; } for (final ClassOrInterfaceTypeDetails managedBeanType : managedBeanTypes) { final AnnotationMetadata managedBeanAnnotation = managedBeanType .getAnnotation(ROO_JSF_MANAGED_BEAN); if (((JavaType) managedBeanAnnotation.getAttribute( "entity").getValue()).equals(parameter)) { customDataBuilder.put(PARAMETER_TYPE_KEY, parameter); customDataBuilder.put( PARAMETER_TYPE_MANAGED_BEAN_NAME_KEY, managedBeanAnnotation.getAttribute( "beanName").getValue()); final LogicalPath logicalPath = PhysicalTypeIdentifier .getPath(parameterTypeCid .getDeclaredByMetadataId()); final PluralMetadata pluralMetadata = (PluralMetadata) metadataService .get(PluralMetadata.createIdentifier( parameter, logicalPath)); if (pluralMetadata != null) { customDataBuilder.put( PARAMETER_TYPE_PLURAL_KEY, pluralMetadata.getPlural()); } // Only support one generic type parameter break parameterTypeLoop; } // Parameter type is not an entity - test for an // enum if (isEnum(parameterTypeCid)) { customDataBuilder.put(PARAMETER_TYPE_KEY, parameter); } } } } else { if (fieldTypeCid != null && !customDataBuilder.keySet().contains( CustomDataKeys.EMBEDDED_FIELD)) { customDataBuilder.put(APPLICATION_TYPE_KEY, null); final MethodMetadata applicationTypeIdentifierAccessor = persistenceMemberLocator .getIdentifierAccessor(entity); final MethodMetadata applicationTypeVersionAccessor = persistenceMemberLocator .getVersionAccessor(entity); final List<FieldMetadata> applicationTypeFields = new ArrayList<FieldMetadata>(); int dropDownFields = 0; final MemberDetails applicationTypeMemberDetails = getMemberDetails(fieldType); for (final MethodMetadata applicationTypeMethod : applicationTypeMemberDetails .getMethods()) { if (!BeanInfoUtils .isAccessorMethod(applicationTypeMethod)) { continue; } if (applicationTypeMethod.hasSameName( applicationTypeIdentifierAccessor, applicationTypeVersionAccessor)) { continue; } final FieldMetadata applicationTypeField = BeanInfoUtils .getFieldForJavaBeanMethod( applicationTypeMemberDetails, applicationTypeMethod); if (applicationTypeField == null) { continue; } if (dropDownFields < MAX_DROP_DOWN_FIELDS && isFieldOfInterest(applicationTypeField) && !typeLocationService .isInProject(applicationTypeField .getFieldType())) { dropDownFields++; applicationTypeFields.add(applicationTypeField); } } if (applicationTypeFields.isEmpty()) { applicationTypeFields.add(BeanInfoUtils .getFieldForJavaBeanMethod( applicationTypeMemberDetails, applicationTypeIdentifierAccessor)); } customDataBuilder.put(APPLICATION_TYPE_FIELDS_KEY, applicationTypeFields); customDataBuilder.put( CRUD_ADDITIONS_KEY, getCrudAdditions(fieldType, metadataIdentificationString)); } } } final FieldMetadataBuilder fieldBuilder = new FieldMetadataBuilder( field); fieldBuilder.setCustomData(customDataBuilder); locatedFields.add(fieldBuilder.build()); } return locatedFields; } }