package org.springframework.roo.addon.web.mvc.controller; import static org.springframework.roo.classpath.customdata.CustomDataKeys.PERSISTENT_TYPE; import static org.springframework.roo.model.RooJavaType.ROO_WEB_SCAFFOLD; import static org.springframework.roo.model.SpringJavaType.CONTROLLER; import static org.springframework.roo.model.SpringJavaType.REQUEST_MAPPING; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Logger; 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.springframework.roo.addon.plural.PluralMetadata; import org.springframework.roo.addon.web.mvc.controller.scaffold.WebScaffoldMetadata; import org.springframework.roo.classpath.PhysicalTypeCategory; import org.springframework.roo.classpath.PhysicalTypeIdentifier; import org.springframework.roo.classpath.TypeLocationService; import org.springframework.roo.classpath.TypeManagementService; import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails; import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetailsBuilder; import org.springframework.roo.classpath.details.MemberFindingUtils; import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue; 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.ClassAttributeValue; import org.springframework.roo.classpath.details.annotations.StringAttributeValue; import org.springframework.roo.metadata.MetadataDependencyRegistry; import org.springframework.roo.metadata.MetadataService; import org.springframework.roo.model.JavaPackage; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.model.JavaType; import org.springframework.roo.project.FeatureNames; import org.springframework.roo.project.LogicalPath; import org.springframework.roo.project.Path; import org.springframework.roo.project.PathResolver; import org.springframework.roo.project.ProjectOperations; import org.springframework.roo.support.logging.HandlerUtils; /** * Implementation of {@link ControllerOperations}. * * @author Stefan Schmidt * @since 1.0 */ @Component @Service public class ControllerOperationsImpl implements ControllerOperations { private static final Logger LOGGER = HandlerUtils .getLogger(ControllerOperationsImpl.class); private static final JavaSymbolName PATH = new JavaSymbolName("path"); private static final JavaSymbolName VALUE = new JavaSymbolName("value"); @Reference private MetadataDependencyRegistry dependencyRegistry; @Reference private MetadataService metadataService; @Reference private PathResolver pathResolver; @Reference private ProjectOperations projectOperations; @Reference private TypeLocationService typeLocationService; @Reference private TypeManagementService typeManagementService; @Reference private WebMvcOperations webMvcOperations; public void createAutomaticController(final JavaType controller, final JavaType entity, final Set<String> disallowedOperations, final String path) { Validate.notNull(controller, "Controller Java Type required"); Validate.notNull(entity, "Entity Java Type required"); Validate.notNull(disallowedOperations, "Set of disallowed operations required"); Validate.notBlank(path, "Controller base path required"); // Look for an existing controller mapped to this path final ClassOrInterfaceTypeDetails existingController = getExistingController(path); webMvcOperations.installConversionService(controller.getPackage()); List<AnnotationMetadataBuilder> annotations = null; ClassOrInterfaceTypeDetailsBuilder cidBuilder = null; if (existingController == null) { final LogicalPath controllerPath = pathResolver .getFocusedPath(Path.SRC_MAIN_JAVA); final String resourceIdentifier = typeLocationService .getPhysicalTypeCanonicalPath(controller, controllerPath); final String declaredByMetadataId = PhysicalTypeIdentifier .createIdentifier(controller, pathResolver.getPath(resourceIdentifier)); // Create annotation @RequestMapping("/myobject/**") final List<AnnotationAttributeValue<?>> requestMappingAttributes = new ArrayList<AnnotationAttributeValue<?>>(); requestMappingAttributes.add(new StringAttributeValue(VALUE, "/" + path)); annotations = new ArrayList<AnnotationMetadataBuilder>(); annotations.add(new AnnotationMetadataBuilder(REQUEST_MAPPING, requestMappingAttributes)); // Create annotation @Controller final List<AnnotationAttributeValue<?>> controllerAttributes = new ArrayList<AnnotationAttributeValue<?>>(); annotations.add(new AnnotationMetadataBuilder(CONTROLLER, controllerAttributes)); // Create annotation @RooWebScaffold(path = "/test", // formBackingObject = MyObject.class) annotations.add(getRooWebScaffoldAnnotation(entity, disallowedOperations, path, PATH)); cidBuilder = new ClassOrInterfaceTypeDetailsBuilder( declaredByMetadataId, Modifier.PUBLIC, controller, PhysicalTypeCategory.CLASS); } else { cidBuilder = new ClassOrInterfaceTypeDetailsBuilder( existingController); annotations = cidBuilder.getAnnotations(); if (MemberFindingUtils.getAnnotationOfType( existingController.getAnnotations(), ROO_WEB_SCAFFOLD) == null) { annotations.add(getRooWebScaffoldAnnotation(entity, disallowedOperations, path, PATH)); } } cidBuilder.setAnnotations(annotations); typeManagementService.createOrUpdateTypeOnDisk(cidBuilder.build()); } public void generateAll(final JavaPackage javaPackage) { for (final ClassOrInterfaceTypeDetails entityDetails : typeLocationService .findClassesOrInterfaceDetailsWithTag(PERSISTENT_TYPE)) { if (Modifier.isAbstract(entityDetails.getModifier())) { continue; } final JavaType entityType = entityDetails.getType(); final LogicalPath entityPath = PhysicalTypeIdentifier .getPath(entityDetails.getDeclaredByMetadataId()); // Check to see if this persistent type has a web scaffold metadata // listening to it final String downstreamWebScaffoldMetadataId = WebScaffoldMetadata .createIdentifier(entityType, entityPath); if (dependencyRegistry.getDownstream( entityDetails.getDeclaredByMetadataId()).contains( downstreamWebScaffoldMetadataId)) { // There is already a controller for this entity continue; } // To get here, there is no listening controller, so add one final PluralMetadata pluralMetadata = (PluralMetadata) metadataService .get(PluralMetadata .createIdentifier(entityType, entityPath)); if (pluralMetadata != null) { final JavaType controller = new JavaType( javaPackage.getFullyQualifiedPackageName() + "." + entityType.getSimpleTypeName() + "Controller"); createAutomaticController(controller, entityType, new HashSet<String>(), pluralMetadata.getPlural() .toLowerCase()); } } } public boolean isControllerInstallationPossible() { return projectOperations.isFocusedProjectAvailable() && projectOperations .isFeatureInstalledInFocusedModule(FeatureNames.MVC) && !projectOperations .isFeatureInstalledInFocusedModule(FeatureNames.JSF); } public boolean isNewControllerAvailable() { return projectOperations.isFocusedProjectAvailable(); } public void setup() { webMvcOperations.installAllWebMvcArtifacts(); } /** * Looks for an existing controller mapped to the given path * * @param path (required) * @return <code>null</code> if there is no such controller */ private ClassOrInterfaceTypeDetails getExistingController(final String path) { for (final ClassOrInterfaceTypeDetails cid : typeLocationService .findClassesOrInterfaceDetailsWithAnnotation(REQUEST_MAPPING)) { final AnnotationAttributeValue<?> attribute = MemberFindingUtils .getAnnotationOfType(cid.getAnnotations(), REQUEST_MAPPING) .getAttribute(VALUE); if (attribute instanceof ArrayAttributeValue) { final ArrayAttributeValue<?> mappingAttribute = (ArrayAttributeValue<?>) attribute; if (mappingAttribute.getValue().size() > 1) { LOGGER.warning("Skipping controller '" + cid.getName().getFullyQualifiedTypeName() + "' as it contains more than one path"); continue; } else if (mappingAttribute.getValue().size() == 1) { final StringAttributeValue attr = (StringAttributeValue) mappingAttribute .getValue().get(0); final String mapping = attr.getValue(); if (StringUtils.isNotBlank(mapping) && mapping.equalsIgnoreCase("/" + path)) { return cid; } } } else if (attribute instanceof StringAttributeValue) { final StringAttributeValue mappingAttribute = (StringAttributeValue) attribute; if (mappingAttribute != null) { final String mapping = mappingAttribute.getValue(); if (StringUtils.isNotBlank(mapping) && mapping.equalsIgnoreCase("/" + path)) { return cid; } } } } return null; } private AnnotationMetadataBuilder getRooWebScaffoldAnnotation( final JavaType entity, final Set<String> disallowedOperations, final String path, final JavaSymbolName pathName) { final List<AnnotationAttributeValue<?>> rooWebScaffoldAttributes = new ArrayList<AnnotationAttributeValue<?>>(); rooWebScaffoldAttributes.add(new StringAttributeValue(pathName, path)); rooWebScaffoldAttributes.add(new ClassAttributeValue( new JavaSymbolName("formBackingObject"), entity)); for (final String operation : disallowedOperations) { rooWebScaffoldAttributes.add(new BooleanAttributeValue( new JavaSymbolName(operation), false)); } return new AnnotationMetadataBuilder(ROO_WEB_SCAFFOLD, rooWebScaffoldAttributes); } }