/* * gvNIX is an open source tool for rapid application development (RAD). * Copyright (C) 2010 Generalitat Valenciana * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ package org.gvnix.addon.web.mvc.addon.batch; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Logger; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.Validate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Service; import org.gvnix.addon.jpa.addon.JpaOperations; import org.gvnix.addon.jpa.addon.batch.JpaBatchAnnotationValues; import org.gvnix.addon.jpa.annotations.batch.GvNIXJpaBatch; import org.gvnix.addon.web.mvc.addon.MvcOperations; import org.gvnix.addon.web.mvc.annotations.batch.GvNIXWebJpaBatch; import org.gvnix.support.WebProjectUtils; import org.gvnix.support.dependenciesmanager.DependenciesVersionManager; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.service.component.ComponentContext; import org.springframework.roo.addon.web.mvc.controller.addon.scaffold.WebScaffoldAnnotationValues; import org.springframework.roo.addon.web.mvc.controller.annotations.scaffold.RooWebScaffold; 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.AnnotationMetadataBuilder; import org.springframework.roo.classpath.operations.AbstractOperations; import org.springframework.roo.metadata.MetadataService; import org.springframework.roo.model.JavaType; import org.springframework.roo.model.RooJavaType; import org.springframework.roo.model.SpringJavaType; import org.springframework.roo.process.manager.MutableFile; import org.springframework.roo.project.Dependency; import org.springframework.roo.project.FeatureNames; import org.springframework.roo.project.LogicalPath; import org.springframework.roo.project.ProjectOperations; import org.springframework.roo.project.Property; import org.springframework.roo.project.maven.Pom; import org.springframework.roo.support.logging.HandlerUtils; import org.springframework.roo.support.util.XmlUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * Implementation of {@link WebJpaBatchOperations} * * @since 1.1 */ @Component @Service public class WebJpaBatchOperationsImpl extends AbstractOperations implements WebJpaBatchOperations { protected final static Logger LOGGER = HandlerUtils .getLogger(WebJpaBatchOperationsImpl.class); private static final String JACKSON2_RM_HANDLER_ADAPTER = "org.gvnix.web.json.Jackson2RequestMappingHandlerAdapter"; private static final String OBJECT_MAPPER = "org.gvnix.web.json.ConversionServiceObjectMapper"; private static final JavaType JPA_BATCH_ANNOTATION = new JavaType( GvNIXJpaBatch.class); private static final JavaType WEB_JPA_BATCH_ANNOTATION = new JavaType( GvNIXWebJpaBatch.class); private static final List<JavaType> JPA_BATCH_SERVICE_ANNOTATIONS = Arrays .asList(JPA_BATCH_ANNOTATION, SpringJavaType.SERVICE); private static final String WEBMCV_DATABINDER_BEAN_ID = "dataBinderRequestMappingHandlerAdapter"; private ProjectOperations projectOperations; private MvcOperations mvcOperations; private TypeLocationService typeLocationService; private TypeManagementService typeManagementService; private MetadataService metadataService; private WebProjectUtils webProjectUtils; protected void activate(ComponentContext cContext) { context = cContext.getBundleContext(); } /** * {@inheritDoc} * <p/> * Commands are available if Spring MVC, gvNIX JPA dependencies and gvNIX * MVC dependencies are installed */ public boolean isCommandAvailable() { return getProjectOperations().isFeatureInstalledInFocusedModule( FeatureNames.MVC) && getProjectOperations().isFeatureInstalledInFocusedModule( JpaOperations.FEATURE_NAME_GVNIX_JPA) && getProjectOperations().isFeatureInstalledInFocusedModule( FEATURE_NAME_GVNIX_MVC_BATCH); } /** * {@inheritDoc} * <p/> * Setup is available if Spring MVC and gvNIX JPA dependencies are installed * and gvNIX MVC dependencies have not been installed yet. * <p/> * Note if gvNIX MVC dependencies are installed there is no need to run * setup and it will be not available in */ public boolean isSetupAvailable() { return getProjectOperations().isFeatureInstalledInFocusedModule( FeatureNames.MVC) && getProjectOperations().isFeatureInstalledInFocusedModule( JpaOperations.FEATURE_NAME_GVNIX_JPA) && !getProjectOperations().isFeatureInstalledInFocusedModule( FEATURE_NAME_GVNIX_MVC_BATCH); } /** {@inheritDoc} */ public void setup() { // If gvNIX MVC dependencies are not installed, install them if (!getProjectOperations().isFeatureInstalledInFocusedModule( MvcOperations.FEATURE_NAME_GVNIX_MVC)) { getMvcOperations().setup(); } installDependencies(); updateWebMvcConfig(); } /** * Install jackson 2 dependencies on project pom */ private void installDependencies() { // Get add-on configuration file Element configuration = XmlUtils.getConfiguration(getClass()); // Install properties List<Element> properties = XmlUtils.findElements( "/configuration/gvnix/properties/*", configuration); for (Element property : properties) { getProjectOperations().addProperty( getProjectOperations().getFocusedModuleName(), new Property(property)); } // Install dependencies List<Element> depens = XmlUtils.findElements( "/configuration/gvnix/dependencies/dependency", configuration); DependenciesVersionManager.manageDependencyVersion( getMetadataService(), getProjectOperations(), depens); } /** * Update the webmvc-config.xml file in order to register * Jackson2RequestMappingHandlerAdapter * * @param targetPackage */ private void updateWebMvcConfig() { LogicalPath webappPath = getWebProjectUtils().getWebappPath( getProjectOperations()); String webMvcXmlPath = getProjectOperations().getPathResolver() .getIdentifier(webappPath, "WEB-INF/spring/webmvc-config.xml"); Validate.isTrue(fileManager.exists(webMvcXmlPath), "webmvc-config.xml not found"); MutableFile webMvcXmlMutableFile = null; Document webMvcXml; try { webMvcXmlMutableFile = fileManager.updateFile(webMvcXmlPath); webMvcXml = XmlUtils.getDocumentBuilder().parse( webMvcXmlMutableFile.getInputStream()); } catch (Exception e) { throw new IllegalStateException(e); } Element root = webMvcXml.getDocumentElement(); Element dataBinder = XmlUtils.findFirstElement("bean[@id='" + WEBMCV_DATABINDER_BEAN_ID + "']", root); if (dataBinder != null) { root.removeChild(dataBinder); } // add bean tag to argument-resolvers Element bean = webMvcXml.createElement("bean"); bean.setAttribute("id", WEBMCV_DATABINDER_BEAN_ID); bean.setAttribute("p:order", "1"); bean.setAttribute("class", JACKSON2_RM_HANDLER_ADAPTER); Element property = webMvcXml.createElement("property"); property.setAttribute("name", "objectMapper"); Element objectMapperBean = webMvcXml.createElement("bean"); objectMapperBean.setAttribute("class", OBJECT_MAPPER); property.appendChild(objectMapperBean); bean.appendChild(property); root.appendChild(bean); XmlUtils.writeXml(webMvcXmlMutableFile.getOutputStream(), webMvcXml); } /** {@inheritDoc} */ public void addAll() { Set<JavaType> jpaBatchServices = new HashSet<JavaType>( getJpaBatchServices()); for (JavaType controller : getTypeLocationService() .findTypesWithAnnotation(RooJavaType.ROO_WEB_SCAFFOLD)) { ClassOrInterfaceTypeDetails controllerDetails = getTypeLocationService() .getTypeDetails(controller); // check for if there is jpa batch service JavaType entity = getFormBackingObject(controllerDetails); if (entity == null) { continue; } // look for jpaBatchSevice for entity JavaType service = findJpaServiceForEntity(entity, jpaBatchServices); if (service == null) { continue; } // remove service from list (this service is not needed any more) jpaBatchServices.remove(service); // Add annotations add(controller, service); } } private Set<JavaType> getJpaBatchServices() { return getTypeLocationService().findTypesWithAnnotation( JPA_BATCH_SERVICE_ANNOTATIONS); } /** * Find the Spring service which entity match with required * * @param entity * @param jpaBatchServices list of class annotated with * {@link #JPA_BATCH_SERVICE_ANNOTATIONS} * @return */ private JavaType findJpaServiceForEntity(JavaType entity, Set<JavaType> jpaBatchServices) { JavaType serviceEntity; for (JavaType service : jpaBatchServices) { serviceEntity = getJpaBatchEntity(service); if (ObjectUtils.equals(serviceEntity, entity)) { return service; } } return null; } /** * Gets the entity value of {@link GvNIXJpaBatch} * * @param service * @return */ private JavaType getJpaBatchEntity(JavaType service) { ClassOrInterfaceTypeDetails serviceDatils = getTypeLocationService() .getTypeDetails(service); JpaBatchAnnotationValues jpaBatchValues = new JpaBatchAnnotationValues( serviceDatils); return jpaBatchValues.getEntity(); } /** * Gets the formBackingObject value of a {@link RooWebScaffold} annotation. * * @param controllerDetails * @return */ private JavaType getFormBackingObject( ClassOrInterfaceTypeDetails controllerDetails) { WebScaffoldAnnotationValues annotationValues = new WebScaffoldAnnotationValues( controllerDetails); return annotationValues.getFormBackingObject(); } /** * Gets the formBackingObject value of a {@link RooWebScaffold} annotation. * * @param controllerDetails * @return */ private JavaType getFormBackingObject(JavaType controller) { ClassOrInterfaceTypeDetails controllerDetails = getTypeLocationService() .getTypeDetails(controller); return getFormBackingObject(controllerDetails); } /** {@inheritDoc} */ @Override public void add(JavaType controller, JavaType service) { Validate.notNull(controller, "Controller required"); if (service != null) { annotateController(controller, service); return; } // check for if there is jpa batch service suitable JavaType entity = getFormBackingObject(controller); service = findJpaServiceForEntity(entity, getJpaBatchServices()); Validate.notNull(service, "No spring service suitable found for Controller: %s", controller); annotateController(controller, service); } private void annotateController(JavaType controller, JavaType service) { Validate.notNull(controller, "Controller required"); Validate.notNull(service, "Service required"); ClassOrInterfaceTypeDetails existing = getTypeLocationService() .getTypeDetails(controller); // Get controller annotation JavaType entity = getFormBackingObject(existing); Validate.notNull(entity, "Operation only supported for controllers"); final boolean isAlreadyAnnotated = MemberFindingUtils .getAnnotationOfType(existing.getAnnotations(), WEB_JPA_BATCH_ANNOTATION) != null; // Test if the annotation already exists on the target type if (!isAlreadyAnnotated) { ClassOrInterfaceTypeDetailsBuilder detailsBuilder = new ClassOrInterfaceTypeDetailsBuilder( existing); AnnotationMetadataBuilder annotationBuilder = new AnnotationMetadataBuilder( WEB_JPA_BATCH_ANNOTATION); annotationBuilder.addClassAttribute("service", service); // Add annotation to target type detailsBuilder.addAnnotation(annotationBuilder.build()); // Save changes to disk getTypeManagementService().createOrUpdateTypeOnDisk( detailsBuilder.build()); } } @Override public String getName() { return FEATURE_NAME_GVNIX_MVC_BATCH; } /** * Returns true if gvNIX Web MVC dependency is installed in current project. * * @param moduleName feature name to check in current project * @return true if given feature name is installed, otherwise returns false */ public boolean isInstalledInModule(final String moduleName) { final Pom pom = getProjectOperations().getPomFromModuleName(moduleName); if (pom == null) { return false; } // Look for gvnix web mvc dependency for (final Dependency dependency : pom.getDependencies()) { if ("org.gvnix.web.json.binding".equals(dependency.getArtifactId())) { return true; } } return false; } public ProjectOperations getProjectOperations() { if (projectOperations == null) { // Get all Services implement ProjectOperations interface try { ServiceReference<?>[] references = this.context .getAllServiceReferences( ProjectOperations.class.getName(), null); for (ServiceReference<?> ref : references) { return (ProjectOperations) this.context.getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load ProjectOperations on WebJpaBatchOperationsImpl."); return null; } } else { return projectOperations; } } public MvcOperations getMvcOperations() { if (mvcOperations == null) { // Get all Services implement MvcOperations interface try { ServiceReference<?>[] references = this.context .getAllServiceReferences(MvcOperations.class.getName(), null); for (ServiceReference<?> ref : references) { return (MvcOperations) this.context.getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load MvcOperations on WebJpaBatchOperationsImpl."); return null; } } else { return mvcOperations; } } public TypeLocationService getTypeLocationService() { if (typeLocationService == null) { // Get all Services implement TypeLocationService interface try { ServiceReference<?>[] references = this.context .getAllServiceReferences( TypeLocationService.class.getName(), null); for (ServiceReference<?> ref : references) { return (TypeLocationService) this.context.getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load TypeLocationService on WebJpaBatchOperationsImpl."); return null; } } else { return typeLocationService; } } public TypeManagementService getTypeManagementService() { if (typeManagementService == null) { // Get all Services implement TypeManagementService interface try { ServiceReference<?>[] references = this.context .getAllServiceReferences( TypeManagementService.class.getName(), null); for (ServiceReference<?> ref : references) { return (TypeManagementService) this.context.getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load TypeManagementService on WebJpaBatchOperationsImpl."); return null; } } else { return typeManagementService; } } public MetadataService getMetadataService() { if (metadataService == null) { // Get all Services implement MetadataService interface try { ServiceReference<?>[] references = this.context .getAllServiceReferences( MetadataService.class.getName(), null); for (ServiceReference<?> ref : references) { return (MetadataService) this.context.getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load MetadataService on WebJpaBatchOperationsImpl."); return null; } } else { return metadataService; } } public WebProjectUtils getWebProjectUtils() { if (webProjectUtils == null) { // Get all Services implement WebProjectUtils interface try { ServiceReference<?>[] references = this.context .getAllServiceReferences( WebProjectUtils.class.getName(), null); for (ServiceReference<?> ref : references) { webProjectUtils = (WebProjectUtils) this.context .getService(ref); return webProjectUtils; } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load WebProjectUtils on WebJpaBatchOperationsImpl."); return null; } } else { return webProjectUtils; } } }