package org.springframework.roo.addon.web.mvc.controller.finder; import static org.springframework.roo.model.JdkJavaType.CALENDAR; import static org.springframework.roo.model.JdkJavaType.DATE; import static org.springframework.roo.model.SpringJavaType.DATE_TIME_FORMAT; import static org.springframework.roo.model.SpringJavaType.MODEL; import static org.springframework.roo.model.SpringJavaType.REQUEST_MAPPING; import static org.springframework.roo.model.SpringJavaType.REQUEST_METHOD; import static org.springframework.roo.model.SpringJavaType.REQUEST_PARAM; import java.beans.Introspector; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.ToStringBuilder; import org.springframework.roo.addon.web.mvc.controller.details.FinderMetadataDetails; import org.springframework.roo.addon.web.mvc.controller.details.JavaTypeMetadataDetails; import org.springframework.roo.addon.web.mvc.controller.details.JavaTypePersistenceMetadataDetails; import org.springframework.roo.addon.web.mvc.controller.scaffold.RooWebScaffold; import org.springframework.roo.addon.web.mvc.controller.scaffold.WebScaffoldAnnotationValues; import org.springframework.roo.classpath.PhysicalTypeIdentifierNamingUtils; import org.springframework.roo.classpath.PhysicalTypeMetadata; import org.springframework.roo.classpath.details.FieldMetadata; import org.springframework.roo.classpath.details.MemberFindingUtils; import org.springframework.roo.classpath.details.MethodMetadataBuilder; import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType; import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue; import org.springframework.roo.classpath.details.annotations.AnnotationMetadata; import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder; import org.springframework.roo.classpath.details.annotations.ArrayAttributeValue; import org.springframework.roo.classpath.details.annotations.BooleanAttributeValue; import org.springframework.roo.classpath.details.annotations.EnumAttributeValue; import org.springframework.roo.classpath.details.annotations.StringAttributeValue; import org.springframework.roo.classpath.itd.AbstractItdTypeDetailsProvidingMetadataItem; import org.springframework.roo.classpath.itd.InvocableMemberBodyBuilder; import org.springframework.roo.metadata.MetadataIdentificationUtils; import org.springframework.roo.model.EnumDetails; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.model.JavaType; import org.springframework.roo.project.LogicalPath; /** * Metadata for finder functionality provided via {@link RooWebScaffold}. * * @author Stefan Schmidt * @since 1.1.3 */ public class WebFinderMetadata extends AbstractItdTypeDetailsProvidingMetadataItem { private static final String PROVIDES_TYPE_STRING = WebFinderMetadata.class .getName(); private static final String PROVIDES_TYPE = MetadataIdentificationUtils .create(PROVIDES_TYPE_STRING); public static String createIdentifier(final JavaType javaType, final LogicalPath path) { return PhysicalTypeIdentifierNamingUtils.createIdentifier( PROVIDES_TYPE_STRING, javaType, path); } public static JavaType getJavaType(final String metadataIdentificationString) { return PhysicalTypeIdentifierNamingUtils.getJavaType( PROVIDES_TYPE_STRING, metadataIdentificationString); } public static String getMetadataIdentiferType() { return PROVIDES_TYPE; } public static LogicalPath getPath(final String metadataIdentificationString) { return PhysicalTypeIdentifierNamingUtils.getPath(PROVIDES_TYPE_STRING, metadataIdentificationString); } public static boolean isValid(final String metadataIdentificationString) { return PhysicalTypeIdentifierNamingUtils.isValid(PROVIDES_TYPE_STRING, metadataIdentificationString); } private WebScaffoldAnnotationValues annotationValues; private String controllerPath; private JavaType formBackingType; private JavaTypeMetadataDetails javaTypeMetadataHolder; private Map<JavaType, JavaTypeMetadataDetails> specialDomainTypes; /** * Constructor * * @param identifier * @param aspectName * @param governorPhysicalTypeMetadata * @param annotationValues * @param specialDomainTypes * @param dynamicFinderMethods */ public WebFinderMetadata( final String identifier, final JavaType aspectName, final PhysicalTypeMetadata governorPhysicalTypeMetadata, final WebScaffoldAnnotationValues annotationValues, final SortedMap<JavaType, JavaTypeMetadataDetails> specialDomainTypes, final Set<FinderMetadataDetails> dynamicFinderMethods) { super(identifier, aspectName, governorPhysicalTypeMetadata); Validate.isTrue(isValid(identifier), "Metadata identification string '" + identifier + "' does not appear to be a valid"); Validate.notNull(annotationValues, "Annotation values required"); Validate.notNull(specialDomainTypes, "Special domain type map required"); Validate.notNull(dynamicFinderMethods, "Dynamoic finder methods required"); if (!isValid()) { return; } this.annotationValues = annotationValues; controllerPath = annotationValues.getPath(); formBackingType = annotationValues.getFormBackingObject(); this.specialDomainTypes = specialDomainTypes; if (dynamicFinderMethods.isEmpty()) { valid = false; return; } javaTypeMetadataHolder = specialDomainTypes.get(formBackingType); Validate.notNull(javaTypeMetadataHolder, "Metadata holder required for form backing type: " + formBackingType); for (final FinderMetadataDetails finder : dynamicFinderMethods) { builder.addMethod(getFinderFormMethod(finder)); builder.addMethod(getFinderMethod(finder)); } itdTypeDetails = builder.build(); } public WebScaffoldAnnotationValues getAnnotationValues() { return annotationValues; } private MethodMetadataBuilder getFinderFormMethod( final FinderMetadataDetails finder) { Validate.notNull(finder, "Method metadata required for finder"); final JavaSymbolName finderFormMethodName = new JavaSymbolName(finder .getFinderMethodMetadata().getMethodName().getSymbolName() + "Form"); final List<JavaType> methodParameterTypes = new ArrayList<JavaType>(); final List<JavaSymbolName> methodParameterNames = new ArrayList<JavaSymbolName>(); final List<JavaType> finderParameterTypes = AnnotatedJavaType .convertFromAnnotatedJavaTypes(finder.getFinderMethodMetadata() .getParameterTypes()); final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); boolean needModel = false; for (final JavaType finderParameterType : finderParameterTypes) { JavaTypeMetadataDetails typeMd = specialDomainTypes .get(finderParameterType); JavaTypePersistenceMetadataDetails javaTypePersistenceMetadataHolder = null; if (finderParameterType.isCommonCollectionType()) { typeMd = specialDomainTypes.get(finderParameterType .getParameters().get(0)); if (typeMd != null && typeMd.isApplicationType()) { javaTypePersistenceMetadataHolder = typeMd .getPersistenceDetails(); } } else if (typeMd != null && typeMd.isEnumType()) { bodyBuilder.appendFormalLine("uiModel.addAttribute(\"" + typeMd.getPlural().toLowerCase() + "\", java.util.Arrays.asList(" + getShortName(finderParameterType) + ".class.getEnumConstants()));"); } else if (typeMd != null && typeMd.isApplicationType()) { javaTypePersistenceMetadataHolder = typeMd .getPersistenceDetails(); } if (typeMd != null && javaTypePersistenceMetadataHolder != null && javaTypePersistenceMetadataHolder.getFindAllMethod() != null) { bodyBuilder.appendFormalLine("uiModel.addAttribute(\"" + typeMd.getPlural().toLowerCase() + "\", " + javaTypePersistenceMetadataHolder.getFindAllMethod() .getMethodCall() + ");"); } needModel = true; } if (finderParameterTypes.contains(DATE) || finderParameterTypes.contains(CALENDAR)) { bodyBuilder.appendFormalLine("addDateTimeFormatPatterns(uiModel);"); } bodyBuilder.appendFormalLine("return \"" + controllerPath + "/" + finder.getFinderMethodMetadata().getMethodName() .getSymbolName() + "\";"); if (needModel) { methodParameterTypes.add(MODEL); methodParameterNames.add(new JavaSymbolName("uiModel")); } if (governorHasMethod(finderFormMethodName, methodParameterTypes)) { return null; } final List<AnnotationAttributeValue<?>> requestMappingAttributes = new ArrayList<AnnotationAttributeValue<?>>(); final List<StringAttributeValue> arrayValues = new ArrayList<StringAttributeValue>(); arrayValues.add(new StringAttributeValue(new JavaSymbolName("value"), "find=" + finder.getFinderMethodMetadata() .getMethodName() .getSymbolName() .replaceFirst( "find" + javaTypeMetadataHolder .getPlural(), ""))); arrayValues.add(new StringAttributeValue(new JavaSymbolName("value"), "form")); requestMappingAttributes .add(new ArrayAttributeValue<StringAttributeValue>( new JavaSymbolName("params"), arrayValues)); requestMappingAttributes.add(new EnumAttributeValue(new JavaSymbolName( "method"), new EnumDetails(REQUEST_METHOD, new JavaSymbolName( "GET")))); final AnnotationMetadataBuilder requestMapping = new AnnotationMetadataBuilder( REQUEST_MAPPING, requestMappingAttributes); final List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>(); annotations.add(requestMapping); final MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder( getId(), Modifier.PUBLIC, finderFormMethodName, JavaType.STRING, AnnotatedJavaType.convertFromJavaTypes(methodParameterTypes), methodParameterNames, bodyBuilder); methodBuilder.setAnnotations(annotations); return methodBuilder; } private MethodMetadataBuilder getFinderMethod( final FinderMetadataDetails finderMetadataDetails) { Validate.notNull(finderMetadataDetails, "Method metadata required for finder"); final JavaSymbolName finderMethodName = new JavaSymbolName( finderMetadataDetails.getFinderMethodMetadata().getMethodName() .getSymbolName()); final List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>(); final List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>(); final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); final StringBuilder methodParams = new StringBuilder(); boolean dateFieldPresent = false; for (final FieldMetadata field : finderMetadataDetails .getFinderMethodParamFields()) { final JavaSymbolName fieldName = field.getFieldName(); final List<AnnotationMetadata> annotations = new ArrayList<AnnotationMetadata>(); final List<AnnotationAttributeValue<?>> attributes = new ArrayList<AnnotationAttributeValue<?>>(); attributes.add(new StringAttributeValue( new JavaSymbolName("value"), uncapitalize(fieldName .getSymbolName()))); if (field.getFieldType().equals(JavaType.BOOLEAN_PRIMITIVE) || field.getFieldType().equals(JavaType.BOOLEAN_OBJECT)) { attributes.add(new BooleanAttributeValue(new JavaSymbolName( "required"), false)); } final AnnotationMetadataBuilder requestParamAnnotation = new AnnotationMetadataBuilder( REQUEST_PARAM, attributes); annotations.add(requestParamAnnotation.build()); if (field.getFieldType().equals(DATE) || field.getFieldType().equals(CALENDAR)) { dateFieldPresent = true; final AnnotationMetadata annotation = MemberFindingUtils .getAnnotationOfType(field.getAnnotations(), DATE_TIME_FORMAT); if (annotation != null) { getShortName(DATE_TIME_FORMAT); annotations.add(annotation); } } parameterNames.add(fieldName); parameterTypes.add(new AnnotatedJavaType(field.getFieldType(), annotations)); if (field.getFieldType().equals(JavaType.BOOLEAN_OBJECT)) { methodParams.append(fieldName + " == null ? Boolean.FALSE : " + fieldName + ", "); } else { methodParams.append(fieldName + ", "); } } if (methodParams.length() > 0) { methodParams.delete(methodParams.length() - 2, methodParams.length()); } parameterTypes.add(new AnnotatedJavaType(MODEL)); if (getGovernorMethod(finderMethodName, AnnotatedJavaType.convertFromAnnotatedJavaTypes(parameterTypes)) != null) { return null; } final List<JavaSymbolName> newParamNames = new ArrayList<JavaSymbolName>(); newParamNames.addAll(parameterNames); newParamNames.add(new JavaSymbolName("uiModel")); final List<AnnotationAttributeValue<?>> requestMappingAttributes = new ArrayList<AnnotationAttributeValue<?>>(); requestMappingAttributes.add(new StringAttributeValue( new JavaSymbolName("params"), "find=" + finderMetadataDetails .getFinderMethodMetadata() .getMethodName() .getSymbolName() .replaceFirst( "find" + javaTypeMetadataHolder .getPlural(), ""))); requestMappingAttributes.add(new EnumAttributeValue(new JavaSymbolName( "method"), new EnumDetails(REQUEST_METHOD, new JavaSymbolName( "GET")))); final AnnotationMetadataBuilder requestMapping = new AnnotationMetadataBuilder( REQUEST_MAPPING, requestMappingAttributes); final List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>(); annotations.add(requestMapping); bodyBuilder.appendFormalLine("uiModel.addAttribute(\"" + javaTypeMetadataHolder.getPlural().toLowerCase() + "\", " + getShortName(formBackingType) + "." + finderMetadataDetails.getFinderMethodMetadata() .getMethodName().getSymbolName() + "(" + methodParams.toString() + ").getResultList());"); if (dateFieldPresent) { bodyBuilder.appendFormalLine("addDateTimeFormatPatterns(uiModel);"); } bodyBuilder.appendFormalLine("return \"" + controllerPath + "/list\";"); final MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder( getId(), Modifier.PUBLIC, finderMethodName, JavaType.STRING, parameterTypes, newParamNames, bodyBuilder); methodBuilder.setAnnotations(annotations); return methodBuilder; } private String getShortName(final JavaType type) { return type.getNameIncludingTypeParameters(false, builder.getImportRegistrationResolver()); } @Override public String toString() { final ToStringBuilder builder = new ToStringBuilder(this); builder.append("identifier", getId()); builder.append("valid", valid); builder.append("aspectName", aspectName); builder.append("destinationType", destination); builder.append("governor", governorPhysicalTypeMetadata.getId()); builder.append("itdTypeDetails", itdTypeDetails); return builder.toString(); } private String uncapitalize(final String term) { // [ROO-1790] this is needed to adhere to the JavaBean naming // conventions (see JavaBean spec section 8.8) return Introspector.decapitalize(StringUtils.capitalize(term)); } }