/* * 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.monitoring; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.lang3.Validate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Service; import org.gvnix.support.WebProjectUtils; import org.osgi.framework.BundleContext; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.service.component.ComponentContext; import org.springframework.roo.addon.propfiles.PropFileOperations; import org.springframework.roo.addon.web.mvc.jsp.menu.MenuOperations; 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.MethodMetadata; import org.springframework.roo.classpath.details.MethodMetadataBuilder; import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder; import org.springframework.roo.model.JavaPackage; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.model.JavaType; import org.springframework.roo.process.manager.FileManager; import org.springframework.roo.project.Dependency; 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; import org.springframework.roo.support.util.XmlUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * Implementation of operations this add-on offers. * * @author <a href="http://www.disid.com">DISID Corporation S.L.</a> made for <a * href="http://www.dgti.gva.es">General Directorate for Information * Technologies (DGTI)</a> * @since 1.4.0 */ @Component @Service public class MonitoringOperationsImpl implements MonitoringOperations { private static final Logger LOGGER = HandlerUtils .getLogger(MonitoringOperationsImpl.class); // ------------ OSGi component attributes ---------------- private BundleContext context; protected void activate(ComponentContext cContext) { context = cContext.getBundleContext(); } private static final String MONITORING = "monitoring"; private static final String BEAN_TAG = "bean"; private static final String CLASS_TAG = "class"; private static final String NAME_TAG = "name"; private static final String JM_ANN_POINTCUT = "net.bull.javamelody.MonitoredWithAnnotationPointcut"; private static final JavaType SPRING_MONITORING_ANNOTATION = new JavaType( "net.bull.javamelody.MonitoredWithSpring"); private static final JavaType SPRING_CONTROLLER = new JavaType( "org.springframework.stereotype.Controller"); private static final JavaType SPRING_SERVICE = new JavaType( "org.springframework.stereotype.Service"); private MenuOperations menuOperations; protected FileManager fileManager; private PathResolver pathResolver; private PropFileOperations propFileOperations; private ProjectOperations projectOperations; private TypeLocationService typeLocationService; private TypeManagementService typeManagementService; private WebProjectUtils webProjectUtils; /** {@inheritDoc} */ public boolean isCommandAvailable() { // Check if a project has been created return getProjectOperations().isFocusedProjectAvailable(); } public boolean isAddAvailable() { return getProjectOperations().isFeatureInstalledInFocusedModule( FEATURE_NAME_GVNIX_MONITORING); } /** {@inheritDoc} */ public void setup(String pathString) { // Adding pom.xml dependencies addPomDependencies(); // Modifying web.xml updateWebXML(pathString); // Adding JavaMelody persistence updatePersistence(); // Modifying ApplicationContext to enable SQL monitoring updateAppContextSQL(); // Add i18n necessary messages addI18nControllerProperties(); // Adding menu entry addMenuEntry(); } /** * This method adds a new entry menu */ public void addMenuEntry() { String finalPath = MONITORING; getMenuOperations().addMenuItem( new JavaSymbolName("monitoring_menu_category"), new JavaSymbolName("monitoring_menu_entry"), "JMelody Monitoring", "global_generic", "/" + finalPath, null, getWebappPath()); } /** * This method add necessary properties to messages.properties for * Controller */ public void addI18nControllerProperties() { Map<String, String> propertyList = new HashMap<String, String>(); propertyList.put("menu_category_monitoring_menu_category_label", MONITORING); getPropFileOperations().addProperties(getWebappPath(), "WEB-INF/i18n/application.properties", propertyList, true, false); } /** * This method updates ApplicationContext.xml to enable SQL monitoring */ public void updateAppContextSQL() { String appContextPath = getPathResolver().getFocusedIdentifier( Path.SRC_MAIN_RESOURCES, "META-INF/spring/applicationContext.xml"); if (getFileManager().exists(appContextPath)) { Document docXml = getWebProjectUtils().loadXmlDocument( appContextPath, getFileManager()); // Getting root element Element docRoot = docXml.getDocumentElement(); // Checking if exist NodeList beanElements = docRoot.getElementsByTagName(BEAN_TAG); for (int i = 0; i < beanElements.getLength(); i++) { Node bean = beanElements.item(i); NamedNodeMap beanAttr = bean.getAttributes(); if (beanAttr != null) { Node idAttr = beanAttr.getNamedItem("id"); // Checking if bean exists on current beans if ("springDataSourceBeanPostProcessor".equals(idAttr .getNodeValue())) { return; } } } // Creating new element (bean) Element beanElement = docXml.createElement(BEAN_TAG); beanElement.setAttribute("id", "springDataSourceBeanPostProcessor"); beanElement.setAttribute(CLASS_TAG, "net.bull.javamelody.SpringDataSourceBeanPostProcessor"); docRoot.appendChild(beanElement); // Saving changes getFileManager().createOrUpdateTextFileIfRequired(appContextPath, XmlUtils.nodeToString(docXml), true); } } /** * This method updates persistence.xml to add persistence which is needed by * JavaMelody to work */ public void updatePersistence() { String persistencePath = getPathResolver().getFocusedIdentifier( Path.SRC_MAIN_RESOURCES, "META-INF/persistence.xml"); if (getFileManager().exists(persistencePath)) { Document docXml = getWebProjectUtils().loadXmlDocument( persistencePath, getFileManager()); // Getting root element Element docRoot = docXml.getDocumentElement(); // Getting provider NodeList allProviders = docRoot.getElementsByTagName("provider"); // Modifying or creating provider element if (allProviders.getLength() > 0) { for (int i = 0; i < allProviders.getLength(); i++) { Element provider = (Element) allProviders.item(i); provider.setTextContent("net.bull.javamelody.JpaPersistence"); } } else { Element providerElement = docXml.createElement("provider"); providerElement .setTextContent("net.bull.javamelody.JpaPersistence"); docRoot.appendChild(providerElement); } // Getting properties node NodeList allProperties = docRoot.getElementsByTagName("properties"); Element propertiesElement = null; // Modifying or creating properties element if (allProperties.getLength() > 0) { for (int i = 0; i < allProperties.getLength(); i++) { propertiesElement = (Element) allProperties.item(i); NodeList propertyElements = propertiesElement .getChildNodes(); for (int x = 0; x < propertyElements.getLength(); x++) { Node property = propertyElements.item(x); NamedNodeMap propertyAttr = property.getAttributes(); if (propertyAttr != null) { Node nameAttr = propertyAttr.getNamedItem(NAME_TAG); // Checking if property exists on current Properties if ("net.bull.javamelody.jpa.provider" .equals(nameAttr.getNodeValue())) { return; } } } } } else { propertiesElement = docXml.createElement("properties"); docRoot.appendChild(propertiesElement); } // Creating provider property Element property = docXml.createElement("property"); property.setAttribute(NAME_TAG, "net.bull.javamelody.jpa.provider"); property.setAttribute("value", "org.hibernate.ejb.HibernatePersistence"); // Adding property to properties propertiesElement.appendChild(property); // Saving result getFileManager().createOrUpdateTextFileIfRequired(persistencePath, XmlUtils.nodeToString(docXml), true); } } /** * This method updates web.xml to add filter, filter-mapping and listener * which are needed to proper functioning of JavaMelody * * @param pathString */ public void updateWebXML(String pathString) { String webPath = getPathResolver().getFocusedIdentifier( Path.SRC_MAIN_WEBAPP, "WEB-INF/web.xml"); if (getFileManager().exists(webPath)) { Document docXml = getWebProjectUtils().loadXmlDocument(webPath, getFileManager()); // Getting root element Element docRoot = docXml.getDocumentElement(); // Checking if exists NodeList allFilters = docRoot.getElementsByTagName("filter-name"); for (int i = 0; i < allFilters.getLength(); i++) { Element filter = (Element) allFilters.item(i); if (MONITORING.equals(filter.getTextContent())) { return; } } // Creating filter elements Element filterElement = docXml.createElement("filter"); // filter-name Element filterNameElement = docXml.createElement("filter-name"); filterNameElement.setTextContent(MONITORING); filterElement.appendChild(filterNameElement); // filter-class Element filterClassElement = docXml.createElement("filter-class"); filterClassElement .setTextContent("net.bull.javamelody.MonitoringFilter"); filterElement.appendChild(filterClassElement); // async-supported Element asyncSupportedElement = docXml .createElement("async-supported"); asyncSupportedElement.setTextContent("true"); filterElement.appendChild(asyncSupportedElement); // Creating filter-mapping element Element filterMappingElement = docXml .createElement("filter-mapping"); // filter-name 2 Element filterName2Element = docXml.createElement("filter-name"); filterName2Element.setTextContent(MONITORING); filterMappingElement.appendChild(filterName2Element); // url-pattern Element urlPatternElement = docXml.createElement("url-pattern"); urlPatternElement.setTextContent("/*"); filterMappingElement.appendChild(urlPatternElement); Element dispatcherElement = docXml.createElement("dispatcher"); dispatcherElement.setTextContent("REQUEST"); filterMappingElement.appendChild(dispatcherElement); Element dispatcher2Element = docXml.createElement("dispatcher"); dispatcher2Element.setTextContent("ASYNC"); filterMappingElement.appendChild(dispatcher2Element); // Creating listener Element listenerElement = docXml.createElement("listener"); // listener-class Element listenerClassElement = docXml .createElement("listener-class"); listenerClassElement .setTextContent("net.bull.javamelody.SessionListener"); listenerElement.appendChild(listenerClassElement); // Adding elements docRoot.appendChild(filterElement); docRoot.appendChild(filterMappingElement); docRoot.appendChild(listenerElement); // Add javamelody configuration file NodeList paramValueNodes = docRoot .getElementsByTagName("param-value"); for (int i = 0; i < paramValueNodes.getLength(); i++) { Node node = paramValueNodes.item(i); if (("WEB-INF/spring/webmvc-config.xml").equals(node .getTextContent())) { node.setTextContent("classpath*:net/bull/javamelody/monitoring-spring-aspectj*.xml" .concat(" ").concat(node.getTextContent())); } } getFileManager().createOrUpdateTextFileIfRequired(webPath, XmlUtils.nodeToString(docXml), true); } } /** * This method adds pom.xml dependencies */ public void addPomDependencies() { List<Dependency> dependencies = new ArrayList<Dependency>(); // Install dependencies defined in external XML file for (Element dependencyElement : XmlUtils.findElements( "/configuration/gvnix/dependencies/dependency", XmlUtils.getConfiguration(getClass()))) { dependencies.add(new Dependency(dependencyElement)); } // Add all new dependencies to pom.xml getProjectOperations().addDependencies("", dependencies); } /** * Creates an instance with the {@code src/main/webapp} path in the current * module * * @return */ public LogicalPath getWebappPath() { return getWebProjectUtils().getWebappPath(getProjectOperations()); } /** * Add all files to be monitored as a Spring service */ @Override public void all() { addPackage(getProjectOperations().getFocusedTopLevelPackage()); } /** * Add a path which all his child methods will be monitored as a Spring * service * * @param path Set the package path to be monitored */ @Override public void addPackage(JavaPackage path) { // Creating annotation AnnotationMetadataBuilder annotationBuilder = new AnnotationMetadataBuilder( SPRING_MONITORING_ANNOTATION); Set<ClassOrInterfaceTypeDetails> controllers = getTypeLocationService() .findClassesOrInterfaceDetailsWithAnnotation(SPRING_CONTROLLER); Set<ClassOrInterfaceTypeDetails> services = getTypeLocationService() .findClassesOrInterfaceDetailsWithAnnotation(SPRING_SERVICE); // Annotating all controllers if they exists if (controllers != null) { Iterator<ClassOrInterfaceTypeDetails> it = controllers.iterator(); while (it.hasNext()) { ClassOrInterfaceTypeDetails controller = it.next(); if (controller.getType().getPackage().isWithin(path)) { // Generating new annotation ClassOrInterfaceTypeDetailsBuilder builder = new ClassOrInterfaceTypeDetailsBuilder( controller); // Add annotation to target type builder.updateTypeAnnotation(annotationBuilder.build()); // Save changes to disk getTypeManagementService().createOrUpdateTypeOnDisk( builder.build()); } } } // Annotating all services if they exists if (services != null) { Iterator<ClassOrInterfaceTypeDetails> it = services.iterator(); while (it.hasNext()) { ClassOrInterfaceTypeDetails service = it.next(); if (service.getType().getPackage().isWithin(path)) { // Generating new annotation ClassOrInterfaceTypeDetailsBuilder builder = new ClassOrInterfaceTypeDetailsBuilder( service); // Add annotation to target type builder.updateTypeAnnotation(annotationBuilder.build()); // Save changes to disk getTypeManagementService().createOrUpdateTypeOnDisk( builder.build()); } } } } /** * Add a class to be monitored as a Spring service * * @param name Set the class name to be monitored */ @Override public void addClass(JavaType name) { // Get java type controller ClassOrInterfaceTypeDetails controller = getControllerDetails(name); // Generating new annotation ClassOrInterfaceTypeDetailsBuilder builder = new ClassOrInterfaceTypeDetailsBuilder( controller); AnnotationMetadataBuilder annotationBuilder = new AnnotationMetadataBuilder( SPRING_MONITORING_ANNOTATION); // Add annotation to target type builder.updateTypeAnnotation(annotationBuilder.build()); // Save changes to disk getTypeManagementService().createOrUpdateTypeOnDisk(builder.build()); } /** * Add a method to be monitored as a Spring service * * @param methodName Set the method name to be monitored * @param className Set the class name of the method to be monitored */ @Override public void addMethod(JavaSymbolName methodName, JavaType className) { // Get java type controller ClassOrInterfaceTypeDetails controller = getControllerDetails(className); ClassOrInterfaceTypeDetailsBuilder classBuilder = new ClassOrInterfaceTypeDetailsBuilder( controller); List<MethodMetadata> methodList = (List<MethodMetadata>) controller .getDeclaredMethods(); for (int i = 0; i < methodList.size(); i++) { MethodMetadata method = methodList.get(i); if (methodName.equals(method.getMethodName())) { MethodMetadataBuilder builder = new MethodMetadataBuilder( method); // Generating new annotation AnnotationMetadataBuilder annotationBuilder = new AnnotationMetadataBuilder( SPRING_MONITORING_ANNOTATION); // Add annotation to target type builder.updateTypeAnnotation(annotationBuilder.build()); // Save changes to disk getTypeManagementService().createOrUpdateTypeOnDisk( classBuilder.build()); } } LOGGER.log( Level.INFO, "[ERROR] This method doesn't exist for this class or maybe it's inside an .aj file. In this case you must to push-in that method and then execute this command again"); } /** * This method gets class details * * @param controller * @return */ private ClassOrInterfaceTypeDetails getControllerDetails(JavaType controller) { ClassOrInterfaceTypeDetails existing = getTypeLocationService() .getTypeDetails(controller); Validate.notNull(existing, "Can't get Type details"); return existing; } /*** Feature Methods ***/ /** * Gets the feature name managed by this operations class. * * @return feature name */ @Override public String getName() { return FEATURE_NAME_GVNIX_MONITORING; } /** * Returns true if the given feature 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 */ @Override public boolean isInstalledInModule(String moduleName) { // If its installed provider is net.bull.javamelody.JpaPersistence String persistencePath = getPathResolver().getFocusedIdentifier( Path.SRC_MAIN_RESOURCES, "META-INF/persistence.xml"); if (getFileManager().exists(persistencePath)) { Document docXml = getWebProjectUtils().loadXmlDocument( persistencePath, getFileManager()); // Getting root element Element docRoot = docXml.getDocumentElement(); // Getting provider NodeList allProviders = docRoot.getElementsByTagName("provider"); // Check providers if (allProviders.getLength() > 0) { for (int i = 0; i < allProviders.getLength(); i++) { Element provider = (Element) allProviders.item(i); if (provider != null && "net.bull.javamelody.JpaPersistence" .equals(provider.getTextContent())) { return true; } } } } return false; } public MenuOperations getMenuOperations() { if (menuOperations == null) { // Get all Services implement MenuOperations interface try { ServiceReference<?>[] references = this.context .getAllServiceReferences( MenuOperations.class.getName(), null); for (ServiceReference<?> ref : references) { return (MenuOperations) this.context.getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load MenuOperations on MonitoringOperationsImpl."); return null; } } else { return menuOperations; } } public FileManager getFileManager() { if (fileManager == null) { // Get all Services implement FileManager interface try { ServiceReference<?>[] references = this.context .getAllServiceReferences(FileManager.class.getName(), null); for (ServiceReference<?> ref : references) { return (FileManager) this.context.getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load FileManager on MonitoringOperationsImpl."); return null; } } else { return fileManager; } } public PathResolver getPathResolver() { if (pathResolver == null) { // Get all Services implement PathResolver interface try { ServiceReference<?>[] references = this.context .getAllServiceReferences(PathResolver.class.getName(), null); for (ServiceReference<?> ref : references) { return (PathResolver) this.context.getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load PathResolver on MonitoringOperationsImpl."); return null; } } else { return pathResolver; } } public PropFileOperations getPropFileOperations() { if (propFileOperations == null) { // Get all Services implement PropFileOperations interface try { ServiceReference<?>[] references = this.context .getAllServiceReferences( PropFileOperations.class.getName(), null); for (ServiceReference<?> ref : references) { return (PropFileOperations) this.context.getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load PropFileOperations on MonitoringOperationsImpl."); return null; } } else { return propFileOperations; } } 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 MonitoringOperationsImpl."); return null; } } else { return projectOperations; } } 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 MonitoringOperationsImpl."); 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 MonitoringOperationsImpl."); return null; } } else { return typeManagementService; } } 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 MonitoringOperationsImpl."); return null; } } else { return webProjectUtils; } } }