package org.springframework.roo.addon.jms;
import static java.lang.reflect.Modifier.PRIVATE;
import static java.lang.reflect.Modifier.PUBLIC;
import static java.lang.reflect.Modifier.TRANSIENT;
import static org.springframework.roo.model.JavaType.OBJECT;
import static org.springframework.roo.model.SpringJavaType.ASYNC;
import static org.springframework.roo.model.SpringJavaType.AUTOWIRED;
import static org.springframework.roo.model.SpringJavaType.JMS_OPERATIONS;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
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.propfiles.PropFileOperations;
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.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.itd.InvocableMemberBodyBuilder;
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.ProjectOperations;
import org.springframework.roo.support.util.DomUtils;
import org.springframework.roo.support.util.FileUtils;
import org.springframework.roo.support.util.XmlElementBuilder;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Provides JMS configuration operations.
*
* @author Stefan Schmidt
* @author Alan Stewart
* @since 1.0
*/
@Component
@Service
public class JmsOperationsImpl implements JmsOperations {
@Reference private FileManager fileManager;
@Reference private ProjectOperations projectOperations;
@Reference private PropFileOperations propFileOperations;
@Reference private TypeLocationService typeLocationService;
@Reference private TypeManagementService typeManagementService;
private void addDefaultDestination(final Document appCtx, final String name) {
// If we do already have a default destination configured then do
// nothing
final Element root = appCtx.getDocumentElement();
if (null != XmlUtils
.findFirstElement(
"/beans/bean[@class = 'org.springframework.jms.core.JmsTemplate']/property[@name = 'defaultDestination']",
root)) {
return;
}
// Otherwise add it
final Element jmsTemplate = XmlUtils
.findRequiredElement(
"/beans/bean[@class = 'org.springframework.jms.core.JmsTemplate']",
root);
final Element defaultDestination = appCtx.createElement("property");
defaultDestination.setAttribute("ref", name);
defaultDestination.setAttribute("name", "defaultDestination");
jmsTemplate.appendChild(defaultDestination);
}
public void addJmsListener(final JavaType targetType, final String name,
final JmsDestinationType destinationType) {
Validate.notNull(targetType, "Java type required");
final String declaredByMetadataId = PhysicalTypeIdentifier
.createIdentifier(targetType, projectOperations
.getPathResolver().getFocusedPath(Path.SRC_MAIN_JAVA));
final List<MethodMetadataBuilder> methods = new ArrayList<MethodMetadataBuilder>();
final List<JavaType> parameterTypes = Arrays.asList(OBJECT);
final List<JavaSymbolName> parameterNames = Arrays
.asList(new JavaSymbolName("message"));
// Create some method content to get people started
final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
bodyBuilder
.appendFormalLine("System.out.println(\"JMS message received: \" + message);");
methods.add(new MethodMetadataBuilder(declaredByMetadataId, PUBLIC,
new JavaSymbolName("onMessage"), JavaType.VOID_PRIMITIVE,
AnnotatedJavaType.convertFromJavaTypes(parameterTypes),
parameterNames, bodyBuilder));
final ClassOrInterfaceTypeDetailsBuilder cidBuilder = new ClassOrInterfaceTypeDetailsBuilder(
declaredByMetadataId, PUBLIC, targetType,
PhysicalTypeCategory.CLASS);
cidBuilder.setDeclaredMethods(methods);
// Determine the canonical filename
final String physicalLocationCanonicalPath = getPhysicalLocationCanonicalPath(declaredByMetadataId);
// Check the file doesn't already exist
Validate.isTrue(!fileManager.exists(physicalLocationCanonicalPath),
"%s already exists", projectOperations.getPathResolver()
.getFriendlyName(physicalLocationCanonicalPath));
typeManagementService.createOrUpdateTypeOnDisk(cidBuilder.build());
final String jmsContextPath = projectOperations.getPathResolver()
.getFocusedIdentifier(Path.SPRING_CONFIG_ROOT,
"applicationContext-jms.xml");
final Document document = XmlUtils.readXml(fileManager
.getInputStream(jmsContextPath));
final Element root = document.getDocumentElement();
Element listenerContainer = DomUtils.findFirstElementByName(
"jms:listener-container", root);
if (listenerContainer != null
&& destinationType.name().equalsIgnoreCase(
listenerContainer.getAttribute("destination-type"))) {
listenerContainer = document
.createElement("jms:listener-container");
listenerContainer.setAttribute("connection-factory", "jmsFactory");
listenerContainer.setAttribute("destination-type", destinationType
.name().toLowerCase());
root.appendChild(listenerContainer);
}
if (listenerContainer != null) {
final Element jmsListener = document.createElement("jms:listener");
jmsListener.setAttribute("ref",
StringUtils.uncapitalize(targetType.getSimpleTypeName()));
jmsListener.setAttribute("method", "onMessage");
jmsListener.setAttribute("destination", name);
final Element bean = document.createElement("bean");
bean.setAttribute("class", targetType.getFullyQualifiedTypeName());
bean.setAttribute("id",
StringUtils.uncapitalize(targetType.getSimpleTypeName()));
root.appendChild(bean);
listenerContainer.appendChild(jmsListener);
}
fileManager.createOrUpdateTextFileIfRequired(jmsContextPath,
XmlUtils.nodeToString(document), false);
}
/**
* Creates the injected method that sends a JMS message
*
* @param fieldName
* @param declaredByMetadataId
* @param asynchronous whether the JMS message should be sent asynchronously
* @return a non-<code>null</code> builder
*/
private MethodMetadataBuilder createSendMessageMethod(
final JavaSymbolName fieldName, final String declaredByMetadataId,
final boolean asynchronous) {
final List<JavaType> parameterTypes = Arrays.asList(JavaType.OBJECT);
final List<JavaSymbolName> parameterNames = Arrays
.asList(new JavaSymbolName("messageObject"));
final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
bodyBuilder.appendFormalLine(fieldName
+ ".convertAndSend(messageObject);");
final MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(
declaredByMetadataId, PUBLIC,
new JavaSymbolName("sendMessage"), JavaType.VOID_PRIMITIVE,
AnnotatedJavaType.convertFromJavaTypes(parameterTypes),
parameterNames, bodyBuilder);
if (asynchronous) {
methodBuilder.addAnnotation(new AnnotationMetadataBuilder(ASYNC));
}
return methodBuilder;
}
/**
* Ensures that the Spring config files contain the necessary elements and
* properties to support asynchronous tasks.
*/
private void ensureSpringAsynchronousSupportEnabled() {
final String contextPath = projectOperations.getPathResolver()
.getFocusedIdentifier(Path.SPRING_CONFIG_ROOT,
"applicationContext.xml");
final Document appContext = XmlUtils.readXml(fileManager
.getInputStream(contextPath));
final Element root = appContext.getDocumentElement();
if (DomUtils.findFirstElementByName("task:annotation-driven", root) == null) {
if (root.getAttribute("xmlns:task").length() == 0) {
root.setAttribute("xmlns:task",
"http://www.springframework.org/schema/task");
root.setAttribute(
"xsi:schemaLocation",
root.getAttribute("xsi:schemaLocation")
+ " http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.1.xsd");
}
root.appendChild(new XmlElementBuilder("task:annotation-driven",
appContext).addAttribute("executor", "asyncExecutor")
.build());
root.appendChild(new XmlElementBuilder("task:executor", appContext)
.addAttribute("id", "asyncExecutor")
.addAttribute("pool-size", "${executor.poolSize}").build());
fileManager.createOrUpdateTextFileIfRequired(contextPath,
XmlUtils.nodeToString(appContext), false);
propFileOperations.addPropertyIfNotExists(Path.SPRING_CONFIG_ROOT
.getModulePathId(projectOperations.getFocusedModuleName()),
"jms.properties", "executor.poolSize", "10", true);
}
}
private String getPhysicalLocationCanonicalPath(
final String physicalTypeIdentifier) {
Validate.isTrue(PhysicalTypeIdentifier.isValid(physicalTypeIdentifier),
"Physical type identifier is invalid");
final JavaType javaType = PhysicalTypeIdentifier
.getJavaType(physicalTypeIdentifier);
final LogicalPath path = PhysicalTypeIdentifier
.getPath(physicalTypeIdentifier);
return projectOperations.getPathResolver().getIdentifier(path,
javaType.getRelativeFileName());
}
private boolean hasJmsContext() {
return fileManager.exists(projectOperations.getPathResolver()
.getFocusedIdentifier(Path.SPRING_CONFIG_ROOT,
"applicationContext-jms.xml"));
}
public void injectJmsTemplate(final JavaType targetType,
final JavaSymbolName fieldName, final boolean asynchronous) {
Validate.notNull(targetType, "Java type required");
Validate.notNull(fieldName, "Field name required");
final ClassOrInterfaceTypeDetails targetTypeDetails = typeLocationService
.getTypeDetails(targetType);
Validate.isTrue(targetTypeDetails != null,
"Cannot locate source for '%s'",
targetType.getFullyQualifiedTypeName());
final String declaredByMetadataId = targetTypeDetails
.getDeclaredByMetadataId();
final ClassOrInterfaceTypeDetailsBuilder cidBuilder = new ClassOrInterfaceTypeDetailsBuilder(
targetTypeDetails);
// Create the field
cidBuilder.addField(new FieldMetadataBuilder(declaredByMetadataId,
PRIVATE | TRANSIENT, Arrays
.asList(new AnnotationMetadataBuilder(AUTOWIRED)),
fieldName, JMS_OPERATIONS));
// Create the method
cidBuilder.addMethod(createSendMessageMethod(fieldName,
declaredByMetadataId, asynchronous));
if (asynchronous) {
ensureSpringAsynchronousSupportEnabled();
}
typeManagementService.createOrUpdateTypeOnDisk(cidBuilder.build());
}
public void installJms(final JmsProvider jmsProvider, final String name,
final JmsDestinationType destinationType) {
Validate.isTrue(isJmsInstallationPossible(), "Project not available");
Validate.notNull(jmsProvider, "JMS provider required");
final String jmsContextPath = projectOperations.getPathResolver()
.getFocusedIdentifier(Path.SPRING_CONFIG_ROOT,
"applicationContext-jms.xml");
String amq;
String destType;
switch (destinationType) {
case TOPIC:
amq = destType = "topic";
break;
case DURABLE_TOPIC:
amq = "topic";
destType = "durableTopic";
break;
default:
amq = destType = "queue";
break;
}
final InputStream in;
if (fileManager.exists(jmsContextPath)) {
in = fileManager.getInputStream(jmsContextPath);
}
else {
in = FileUtils.getInputStream(getClass(),
"applicationContext-jms-template.xml");
Validate.notNull(in,
"Could not acquire applicationContext-jms.xml template");
}
final Document document = XmlUtils.readXml(in);
final Element root = document.getDocumentElement();
if (StringUtils.isNotBlank(name)) {
final Element destination = document.createElement("amq:" + amq);
destination.setAttribute("physicalName", name);
destination.setAttribute("id", name);
root.appendChild(destination);
addDefaultDestination(document, name);
}
Element listenerContainer = XmlUtils.findFirstElement(
"/beans/listener-container[@destination-type = '" + destType
+ "']", root);
if (listenerContainer == null) {
listenerContainer = document
.createElement("jms:listener-container");
listenerContainer.setAttribute("connection-factory", "jmsFactory");
listenerContainer.setAttribute("destination-type", destType);
root.appendChild(listenerContainer);
}
DomUtils.removeTextNodes(root);
fileManager.createOrUpdateTextFileIfRequired(jmsContextPath,
XmlUtils.nodeToString(document), false);
updateConfiguration(jmsProvider);
}
public boolean isJmsInstallationPossible() {
return projectOperations.isFocusedProjectAvailable();
}
public boolean isManageJmsAvailable() {
return projectOperations.isFocusedProjectAvailable() && hasJmsContext();
}
private void updateConfiguration(final JmsProvider jmsProvider) {
final Element configuration = XmlUtils.getConfiguration(getClass());
final List<Dependency> dependencies = new ArrayList<Dependency>();
final List<Element> springDependencies = XmlUtils.findElements(
"/configuration/springJms/dependencies/dependency",
configuration);
for (final Element dependencyElement : springDependencies) {
dependencies.add(new Dependency(dependencyElement));
}
final List<Element> jmsDependencies = XmlUtils.findElements(
"/configuration/jmsProviders/provider[@id = '"
+ jmsProvider.name() + "']/dependencies/dependency",
configuration);
for (final Element dependencyElement : jmsDependencies) {
dependencies.add(new Dependency(dependencyElement));
}
projectOperations.addDependencies(
projectOperations.getFocusedModuleName(), dependencies);
}
}