package org.springframework.roo.addon.web.mvc.thymeleaf.addon.link.factory; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringBuilder; import org.springframework.roo.addon.web.mvc.controller.addon.ControllerMetadata; import org.springframework.roo.addon.web.mvc.thymeleaf.annotations.RooThymeleaf; import org.springframework.roo.classpath.PhysicalTypeIdentifier; import org.springframework.roo.classpath.PhysicalTypeIdentifierNamingUtils; import org.springframework.roo.classpath.PhysicalTypeMetadata; import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails; import org.springframework.roo.classpath.details.FieldMetadataBuilder; import org.springframework.roo.classpath.details.MethodMetadata; import org.springframework.roo.classpath.details.MethodMetadataBuilder; import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType; import org.springframework.roo.classpath.details.annotations.AnnotationMetadata; import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder; 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.JavaSymbolName; import org.springframework.roo.model.JavaType; import org.springframework.roo.model.JdkJavaType; import org.springframework.roo.model.SpringJavaType; import org.springframework.roo.model.SpringletsJavaType; import org.springframework.roo.project.LogicalPath; /** * = LinkFactoryMetadata * * Metadata for {@link RooThymeleaf}. * * @author Sergio Clares * @since 2.0 */ public class LinkFactoryMetadata extends AbstractItdTypeDetailsProvidingMetadataItem { protected static final JavaSymbolName TO_URI_METHOD_NAME = new JavaSymbolName("toUri"); protected static final JavaSymbolName GET_CONTROLLER_CLASS_METHOD_NAME = new JavaSymbolName( "getControllerClass"); private final JavaType controller; private final List<MethodMetadata> controllerMethods; private final MethodMetadata toUriMethod; private final MethodMetadata getControllerClassMethod; private Map<String, FieldMetadataBuilder> constantForMethods; private final AnnotatedJavaType stringArgument = new AnnotatedJavaType(JavaType.STRING); private final AnnotatedJavaType objectArrayArgument = new AnnotatedJavaType(JavaType.OBJECT_ARRAY); private final AnnotatedJavaType mapStringObjectArgument = new AnnotatedJavaType( JavaType.wrapperOf(JdkJavaType.MAP, JavaType.STRING, JavaType.OBJECT)); private final JavaSymbolName METHOD_NAME_ARGUMENT_NAME = new JavaSymbolName("methodName"); private final JavaSymbolName PARAMETERS_ARGUMENT_NAME = new JavaSymbolName("parameters"); private final JavaSymbolName PATH_VARIABLES_ARGUMENT_NAME = new JavaSymbolName("pathVariables"); private static final String PROVIDES_TYPE_STRING = LinkFactoryMetadata.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 String createIdentifier(ClassOrInterfaceTypeDetails details) { final LogicalPath logicalPath = PhysicalTypeIdentifier.getPath(details.getDeclaredByMetadataId()); return createIdentifier(details.getType(), logicalPath); } 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); } /** * Constructor * * @param identifier * the identifier for this item of metadata (required) * @param aspectName * the Java type of the ITD (required) * @param governorPhysicalTypeMetadata * the governor, which is expected to contain a * {@link ClassOrInterfaceTypeDetails} (required) * @param controller * @param controllerMetadata */ public LinkFactoryMetadata(final String identifier, final JavaType aspectName, final PhysicalTypeMetadata governorPhysicalTypeMetadata, final JavaType controller, final ControllerMetadata controllerMetadata, final List<MethodMetadata> controllerMethods) { super(identifier, aspectName, governorPhysicalTypeMetadata); this.controller = controller; this.controllerMethods = controllerMethods; this.constantForMethods = new HashMap<String, FieldMetadataBuilder>(); // Add @Component ensureGovernorIsAnnotated(new AnnotationMetadataBuilder(SpringJavaType.COMPONENT)); // Set implements type ensureGovernorImplements(JavaType.wrapperOf(SpringletsJavaType.SPRINGLETS_METHOD_LINK_FACTORY, this.controller)); // Including constants for every method existing in the controller class for (MethodMetadata method : controllerMethods) { String methodName = method.getMethodName().getSymbolName(); if (constantForMethods.get(methodName) == null) { ensureGovernorHasField(getConstantForMethodName(methodName)); } } // Create methods this.getControllerClassMethod = addAndGet(getControllerClassMethod()); this.toUriMethod = addAndGet(getToUriMethod()); // Build the ITD itdTypeDetails = builder.build(); } /** * Generates a `toUri` method which generates URI's for the *Collection* * controller methods which are called from views. * * @param finderName * @param serviceFinderMethod * @return */ private MethodMetadata getToUriMethod() { // Define methodName final JavaSymbolName toUriMethodName = TO_URI_METHOD_NAME; // Define method argument types List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>(); parameterTypes.add(stringArgument); parameterTypes.add(objectArrayArgument); parameterTypes.add(mapStringObjectArgument); // Return method if already exists MethodMetadata existingMethod = getGovernorMethod(toUriMethodName, AnnotatedJavaType.convertFromAnnotatedJavaTypes(parameterTypes)); if (existingMethod != null) { return existingMethod; } // Define method argument names final List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>(); parameterNames.add(METHOD_NAME_ARGUMENT_NAME); parameterNames.add(PARAMETERS_ARGUMENT_NAME); parameterNames.add(PATH_VARIABLES_ARGUMENT_NAME); // Generate body final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); // Include a conditional for every method for (MethodMetadata method : this.controllerMethods) { // Getting methodName String methodName = method.getMethodName().getSymbolName(); // Getting methodParams String methodParamsToNull = ""; for (int i = 0; i < method.getParameterTypes().size(); i++) { // Include a null declaration for every parameter methodParamsToNull += "null, "; } // Remove empty space and comma if (StringUtils.isNotEmpty(methodParamsToNull)) { methodParamsToNull = methodParamsToNull.substring(0, methodParamsToNull.length() - 2); } // if (METHOD_NAME_ARGUMENT_NAME.equals(methodNameConstant)) { bodyBuilder.appendFormalLine("if (%s.equals(%s)) {", METHOD_NAME_ARGUMENT_NAME, getConstantForMethodName(methodName).getFieldName()); bodyBuilder.indent(); // return MvcUriComponentsBuilder.fromMethodCall(MvcUriComponentsBuilder.on(getControllerClass()).list(null)).build(); bodyBuilder.appendFormalLine( "return %1$s.fromMethodCall(%1$s.on(%2$s()).%3$s(%4$s)).buildAndExpand(%5$s);", getNameOfJavaType(SpringletsJavaType.SPRINGLETS_MVC_URI_COMPONENTS_BUILDER), this.getControllerClassMethod.getMethodName(), methodName, methodParamsToNull, PATH_VARIABLES_ARGUMENT_NAME); bodyBuilder.indentRemove(); // } bodyBuilder.appendFormalLine("}"); } // throw new IllegalArgumentException("Invalid method name: " + METHOD_NAME_ARGUMENT_NAME); bodyBuilder.appendFormalLine( "throw new IllegalArgumentException(\"Invalid method name: \" + %s);", METHOD_NAME_ARGUMENT_NAME); // Build method builder MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(getId(), Modifier.PUBLIC, toUriMethodName, SpringJavaType.URI_COMPONENTS, parameterTypes, parameterNames, bodyBuilder); return methodBuilder.build(); } private MethodMetadata getControllerClassMethod() { // Define methodName final JavaSymbolName methodName = GET_CONTROLLER_CLASS_METHOD_NAME; // Define method argument types List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>(); // Return method if already exists MethodMetadata existingMethod = getGovernorMethod(methodName, AnnotatedJavaType.convertFromAnnotatedJavaTypes(parameterTypes)); if (existingMethod != null) { return existingMethod; } InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); bodyBuilder.appendFormalLine("return %s.class;", getNameOfJavaType(this.controller)); // return CONTROLLER_CLASS; MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(getId(), Modifier.PUBLIC, methodName, JavaType.wrapperOf( JavaType.CLASS, this.controller), null); // Set method body methodBuilder.setBodyBuilder(bodyBuilder); return methodBuilder.build(); } /** * Add method to governor if needed and returns the MethodMetadata. * * @param method the MethodMetadata to add and return. * @return MethodMetadata */ private MethodMetadata addAndGet(MethodMetadata method) { ensureGovernorHasMethod(new MethodMetadataBuilder(method)); return method; } /** * Builds and returns a private static final field with provided field name and initializer * * @param methodName * @return */ private FieldMetadataBuilder getConstantForMethodName(String methodName) { // If already exists, return the existing one if (constantForMethods.get(methodName) != null) { return constantForMethods.get(methodName); } // Create a new one and cache it FieldMetadataBuilder constant = new FieldMetadataBuilder(getId(), Modifier.PUBLIC + Modifier.STATIC + Modifier.FINAL, new JavaSymbolName(methodName.toUpperCase()), JavaType.STRING, "\"" + methodName + "\""); constantForMethods.put(methodName, constant); return constant; } @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(); } public List<MethodMetadata> getControllerMethods() { return controllerMethods; } }