package org.springframework.roo.addon.jms; 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.Service; import org.osgi.framework.BundleContext; import org.osgi.service.component.ComponentContext; import org.springframework.roo.application.config.ApplicationConfigService; import org.springframework.roo.classpath.ModuleFeatureName; 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.FieldMetadataBuilder; 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.InvocableMemberBodyBuilder; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.model.JavaType; import org.springframework.roo.model.SpringJavaType; import org.springframework.roo.model.SpringletsJavaType; import org.springframework.roo.process.manager.FileManager; import org.springframework.roo.project.Dependency; import org.springframework.roo.project.Path; import org.springframework.roo.project.PathResolver; 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.osgi.ServiceInstaceManager; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import static java.lang.reflect.Modifier.PRIVATE; import static org.springframework.roo.model.SpringJavaType.AUTOWIRED; /** * Provides JMS configuration operations. * * @author Stefan Schmidt * @author Alan Stewart * @author Manuel Iborra * @since 1.0 */ @Component @Service public class JmsOperationsImpl implements JmsOperations { private static final Logger LOGGER = HandlerUtils.getLogger(JmsOperations.class); private static final String JMS_PROPERTY_DESTINATION_NAME_PREFIX = "jms.destination."; //private static final String SPRINGLETS_JMS_PROPERTY_DESTINATION_NAME_PREFIX = "springlets.jms.destination."; private static final String JMS_PROPERTY_DESTINATION_NAME_SUFIX = ".jndi-name"; private static final String JMS_VAR_DESTINATION_NAME_PREFIX = "destination"; private static final String JMS_VAR_DESTINATION_NAME_SUFIX = "JndiName"; private static final String JMS_PROPERTY_JNDI_NAME = "spring.jms.jndi-name"; private static final String JNDI_PREFIX = "java:comp/env/"; // Dependencies private static final Dependency DEPENDENCY_JMS = new Dependency("org.springframework", "spring-jms", null); private static final Dependency DEPENDENCY_SPRINGLETS_STARTER_JMS = new Dependency( "io.springlets", "springlets-boot-starter-jms", "${springlets.version}"); private static final Dependency DEPENDENCY_SPRINGLETS_JMS = new Dependency("io.springlets", "springlets-jms", "${springlets.version}"); // Properties private static final Property PROPERTY_SPRINGLETS_VERSION = new Property("springlets.version", "1.0.0.RELEASE"); private BundleContext context; private ServiceInstaceManager serviceInstaceManager = new ServiceInstaceManager(); protected void activate(final ComponentContext cContext) { this.context = cContext.getBundleContext(); this.serviceInstaceManager.activate(this.context); } public boolean isJmsInstallationPossible() { return getProjectOperations().isFocusedProjectAvailable(); } @Override public void addJmsReceiver(String destinationName, JavaType endpointService, String jndiConnectionFactory, String profile, boolean force) { boolean isApplicationModule = false; // Check that the module of the service is type application Collection<Pom> modules = getTypeLocationService().getModules(ModuleFeatureName.APPLICATION); for (Pom pom : modules) { if (pom.getModuleName().equals(endpointService.getModule())) { isApplicationModule = true; } } if (!isApplicationModule) { LOGGER .log( Level.SEVERE, String .format("The module selected where JMS service will be configured must be of type application")); return; } // Check if Service already exists and --force is included final String serviceFilePathIdentifier = getPathResolver().getCanonicalPath(endpointService.getModule(), Path.SRC_MAIN_JAVA, endpointService); if (getFileManager().exists(serviceFilePathIdentifier) && force) { getFileManager().delete(serviceFilePathIdentifier); } else if (getFileManager().exists(serviceFilePathIdentifier)) { throw new IllegalArgumentException(String.format( "Endpoint '%s' already exists and cannot be created. Try to use a " + "different name on --endpoint parameter or use this command with '--force' " + "to overwrite the current service.", endpointService)); } // Set destionation property name StringBuffer destinationNamePropertyName = new StringBuffer(JMS_PROPERTY_DESTINATION_NAME_PREFIX); destinationNamePropertyName.append(destinationName.replaceAll("/", ".")); destinationNamePropertyName.append(JMS_PROPERTY_DESTINATION_NAME_SUFIX); // Set properties setProperties(destinationName, destinationNamePropertyName.toString(), jndiConnectionFactory, endpointService.getModule(), profile, force); // Create service createReceiverJmsService(endpointService, destinationNamePropertyName.toString()); // Add jms dependecy in module getProjectOperations().addDependency(endpointService.getModule(), DEPENDENCY_JMS); // Add annotation @EnableJms to application class of the module Set<ClassOrInterfaceTypeDetails> applicationClasses = getTypeLocationService().findClassesOrInterfaceDetailsWithAnnotation( SpringJavaType.SPRING_BOOT_APPLICATION); for (ClassOrInterfaceTypeDetails applicationClass : applicationClasses) { if (applicationClass.getType().getModule().equals(endpointService.getModule())) { // Check if annotation exists boolean annotationNotExists = true; for (AnnotationMetadata annotation : applicationClass.getAnnotations()) { if (annotation.getAnnotationType().equals(SpringJavaType.ENABLE_JMS)) { annotationNotExists = false; break; } } if (annotationNotExists) { ClassOrInterfaceTypeDetailsBuilder builder = new ClassOrInterfaceTypeDetailsBuilder(applicationClass); builder.addAnnotation(new AnnotationMetadataBuilder(SpringJavaType.ENABLE_JMS)); getTypeManagementService().createOrUpdateTypeOnDisk(builder.build()); } break; } } } private void createReceiverJmsService(JavaType service, String destinationProperty) { // Create new service class final String serviceClassIdentifier = getPathResolver().getCanonicalPath(service.getModule(), Path.SRC_MAIN_JAVA, service); final String mid = PhysicalTypeIdentifier.createIdentifier(service, getPathResolver().getPath(serviceClassIdentifier)); ClassOrInterfaceTypeDetailsBuilder cidBuilder = new ClassOrInterfaceTypeDetailsBuilder(mid, Modifier.PUBLIC, service, PhysicalTypeCategory.CLASS); // Create new @Service annotation AnnotationMetadataBuilder serviceAnnotation = new AnnotationMetadataBuilder(SpringJavaType.SERVICE); cidBuilder.addAnnotation(serviceAnnotation); // Add method receiveJmsMessage // @JmsListener(destination = // "${application.jms.queue.plaintext.jndi-name}") // public void receiveJmsMessage(String msg) { // // } // Define methodName final JavaSymbolName methodName = new JavaSymbolName("receiveJmsMessage"); // Define parameters List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>(); parameterTypes.add(new AnnotatedJavaType(JavaType.STRING)); final List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>(); parameterNames.add(new JavaSymbolName("msg")); // Adding annotations final List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>(); // Adding @JmsListener annotation AnnotationMetadataBuilder jmsListenerAnnotation = new AnnotationMetadataBuilder(SpringJavaType.JMS_LISTENER); jmsListenerAnnotation.addStringAttribute("destination", "${".concat(destinationProperty) .concat("}")); annotations.add(jmsListenerAnnotation); // Generate body InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); bodyBuilder.newLine(); bodyBuilder.appendFormalLine(" // To be implemented"); MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(mid, Modifier.PUBLIC, methodName, JavaType.VOID_PRIMITIVE, parameterTypes, parameterNames, bodyBuilder); methodBuilder.setAnnotations(annotations); cidBuilder.addMethod(methodBuilder); getTypeManagementService().createOrUpdateTypeOnDisk(cidBuilder.build()); } @Override public void addJmsSender(String destinationName, JavaType classSelected, String jndiConnectionFactory, String profile, boolean force) { // Check that module included in destionationName is an application module String module = ""; String destination = ""; if (getProjectOperations().isMultimoduleProject()) { Collection<String> moduleNames = getTypeLocationService().getModuleNames(ModuleFeatureName.APPLICATION); // if only have one module, select this, else check the parameter if (moduleNames.size() > 1) { // user select a module if (destinationName.contains(":")) { int charSeparation = destinationName.indexOf(":"); if (charSeparation > 0 && destinationName.length() > charSeparation) { module = destinationName.substring(0, charSeparation); destination = destinationName.substring(charSeparation + 1, destinationName.length()); if (!moduleNames.contains(module)) { // error, is necessary select an application module throw new IllegalArgumentException( String .format( "Module '%s' must be of application type. Select one in --destinationName parameter", module)); } } else { // error, is necessary select an application module and destination throw new IllegalArgumentException( String .format("--destinationName parameter must be composed by [application type module]:[destination] or focus module must be application type module and only write the destination name")); } } else { // module not selected, check if focus module is application type Pom focusedModule = getProjectOperations().getFocusedModule(); if (getTypeLocationService().hasModuleFeature(focusedModule, ModuleFeatureName.APPLICATION)) { module = focusedModule.getModuleName(); destination = destinationName; } else { throw new IllegalArgumentException( String .format("--destinationName parameter must be composed by [application type module]:[destination] or focus module must be application type module and only write the destination name")); } } } else { if (moduleNames.isEmpty()) { // error, is necessary select an application module throw new IllegalArgumentException( String.format("Is necessary to have at least an application type module.")); } else { module = moduleNames.iterator().next(); destination = destinationName; } } } else { destination = destinationName; } // Add jms springlets dependecies getProjectOperations().addDependency(classSelected.getModule(), DEPENDENCY_SPRINGLETS_JMS); // Include springlets-boot-starter-mail in module getProjectOperations().addDependency(module, DEPENDENCY_SPRINGLETS_STARTER_JMS); // Include property version getProjectOperations().addProperty("", PROPERTY_SPRINGLETS_VERSION); // Add instance of springlets service to service defined by parameter // Add JavaMailSender to service final ClassOrInterfaceTypeDetails serviceTypeDetails = getTypeLocationService().getTypeDetails(classSelected); Validate.isTrue(serviceTypeDetails != null, "Cannot locate source for '%s'", classSelected.getFullyQualifiedTypeName()); final String declaredByMetadataId = serviceTypeDetails.getDeclaredByMetadataId(); final ClassOrInterfaceTypeDetailsBuilder cidBuilder = new ClassOrInterfaceTypeDetailsBuilder(serviceTypeDetails); // Create service field cidBuilder.addField(new FieldMetadataBuilder(declaredByMetadataId, PRIVATE, Arrays .asList(new AnnotationMetadataBuilder(AUTOWIRED)), new JavaSymbolName("jmsSendingService"), SpringletsJavaType.SPRINGLETS_JMS_SENDING_SERVICE)); // Set destionation property name StringBuffer destinationNamePropertyName = new StringBuffer(JMS_PROPERTY_DESTINATION_NAME_PREFIX); destinationNamePropertyName.append(destination.replaceAll("/", ".")); destinationNamePropertyName.append(JMS_PROPERTY_DESTINATION_NAME_SUFIX); StringBuffer destionationNameVar = new StringBuffer(JMS_VAR_DESTINATION_NAME_PREFIX); if (destination.contains("/")) { // Delete char '/' and each word String[] destinationNameArray = destination.split("/"); for (String destinationFragment : destinationNameArray) { destionationNameVar.append(StringUtils.capitalize(destinationFragment)); } } else { destionationNameVar.append(StringUtils.capitalize(destination)); } destionationNameVar.append(JMS_VAR_DESTINATION_NAME_SUFIX); // Adding @Value annotation AnnotationMetadataBuilder valueAnnotation = new AnnotationMetadataBuilder(SpringJavaType.VALUE); valueAnnotation.addStringAttribute("value", "${".concat(destinationNamePropertyName.toString()) .concat("}")); // Add instance of destination name cidBuilder.addField(new FieldMetadataBuilder(declaredByMetadataId, PRIVATE, Arrays .asList(valueAnnotation), new JavaSymbolName(destionationNameVar.toString()), JavaType.STRING)); // Write both, springlets service and destination instance getTypeManagementService().createOrUpdateTypeOnDisk(cidBuilder.build()); // Set properties setProperties(destination, destinationNamePropertyName.toString(), jndiConnectionFactory, module, profile, force); } /** * Set properties in corresponding properties profile file * * @param destinationName Value of destinationName property * @param destinationNamePropertyName Name of destinationName property * @param jndiConnectionFactory Value of 'spring.jms.jndi-name' property * @param module Application module where the properties file is located * @param profile The profile where the properties will be set * @param force Indicate if the properties will be overwritten */ private void setProperties(String destinationName, String destinationNamePropertyName, String jndiConnectionFactory, String module, String profile, boolean force) { // Check and add profile properties if (jndiConnectionFactory != null) { if (getApplicationConfigService().existsSpringConfigFile(module, profile)) { String propertyJndiName = getApplicationConfigService().getProperty(module, JMS_PROPERTY_JNDI_NAME, profile); if (propertyJndiName != null && !force) { throw new IllegalArgumentException( String .format("JNDI Connection Factory for JMS already exists and cannot be created. " + "Using this command with '--force' will overwrite the current value.")); } } // add 'java:comp/env' prefix to properties if don't have it. if (!jndiConnectionFactory.startsWith(JNDI_PREFIX)) { jndiConnectionFactory = JNDI_PREFIX.concat(jndiConnectionFactory); } getApplicationConfigService().addProperty(module, JMS_PROPERTY_JNDI_NAME, jndiConnectionFactory, profile, true); if (!destinationName.startsWith(JNDI_PREFIX)) { destinationName = JNDI_PREFIX.concat(destinationName); } } // Set property destinationName in file getApplicationConfigService().addProperty(module, destinationNamePropertyName, destinationName, profile, true); } // Methods to obtain OSGi Services private ProjectOperations getProjectOperations() { return serviceInstaceManager.getServiceInstance(this, ProjectOperations.class); } private TypeLocationService getTypeLocationService() { return serviceInstaceManager.getServiceInstance(this, TypeLocationService.class); } private TypeManagementService getTypeManagementService() { return serviceInstaceManager.getServiceInstance(this, TypeManagementService.class); } private FileManager getFileManager() { return serviceInstaceManager.getServiceInstance(this, FileManager.class); } private PathResolver getPathResolver() { return serviceInstaceManager.getServiceInstance(this, PathResolver.class); } private ApplicationConfigService getApplicationConfigService() { return serviceInstaceManager.getServiceInstance(this, ApplicationConfigService.class); } }