package org.springframework.roo.addon.email;
import static org.springframework.roo.addon.email.MailProtocol.SMTP;
import static org.springframework.roo.model.SpringJavaType.ASYNC;
import static org.springframework.roo.model.SpringJavaType.AUTOWIRED;
import static org.springframework.roo.model.SpringJavaType.JAVA_MAIL_SENDER_IMPL;
import static org.springframework.roo.model.SpringJavaType.MAIL_SENDER;
import static org.springframework.roo.model.SpringJavaType.SIMPLE_MAIL_MESSAGE;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.propfiles.PropFileOperations;
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.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.DomUtils;
import org.springframework.roo.support.util.PairList;
import org.springframework.roo.support.util.XmlElementBuilder;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Implementation of {@link MailOperationsImpl}.
*
* @author Stefan Schmidt
* @since 1.0
*/
@Component
@Service
public class MailOperationsImpl implements MailOperations {
private static final String LOCAL_MESSAGE_VARIABLE = "mailMessage";
private static final Logger LOGGER = HandlerUtils
.getLogger(MailOperationsImpl.class);
private static final int PRIVATE_TRANSIENT = Modifier.PRIVATE
| Modifier.TRANSIENT;
private static final String SPRING_TASK_NS = "http://www.springframework.org/schema/task";
private static final String SPRING_TASK_XSD = "http://www.springframework.org/schema/task/spring-task-3.1.xsd";
private static final AnnotatedJavaType STRING = new AnnotatedJavaType(
JavaType.STRING);
private static final String TEMPLATE_MESSAGE_FIELD = "templateMessage";
@Reference private FileManager fileManager;
@Reference private PathResolver pathResolver;
@Reference private ProjectOperations projectOperations;
@Reference private PropFileOperations propFileOperations;
@Reference private TypeLocationService typeLocationService;
@Reference private TypeManagementService typeManagementService;
public void configureTemplateMessage(final String from, final String subject) {
final String contextPath = getApplicationContextPath();
final Document document = XmlUtils.readXml(fileManager
.getInputStream(contextPath));
final Element root = document.getDocumentElement();
final Map<String, String> props = new HashMap<String, String>();
if (StringUtils.isNotBlank(from) || StringUtils.isNotBlank(subject)) {
Element smmBean = getSimpleMailMessageBean(root);
if (smmBean == null) {
smmBean = document.createElement("bean");
smmBean.setAttribute("class",
SIMPLE_MAIL_MESSAGE.getFullyQualifiedTypeName());
smmBean.setAttribute("id", "templateMessage");
}
if (StringUtils.isNotBlank(from)) {
Element smmProperty = XmlUtils.findFirstElement(
"//property[@name='from']", smmBean);
if (smmProperty != null) {
smmBean.removeChild(smmProperty);
}
smmProperty = document.createElement("property");
smmProperty.setAttribute("value", "${email.from}");
smmProperty.setAttribute("name", "from");
smmBean.appendChild(smmProperty);
props.put("email.from", from);
}
if (StringUtils.isNotBlank(subject)) {
Element smmProperty = XmlUtils.findFirstElement(
"//property[@name='subject']", smmBean);
if (smmProperty != null) {
smmBean.removeChild(smmProperty);
}
smmProperty = document.createElement("property");
smmProperty.setAttribute("value", "${email.subject}");
smmProperty.setAttribute("name", "subject");
smmBean.appendChild(smmProperty);
props.put("email.subject", subject);
}
root.appendChild(smmBean);
DomUtils.removeTextNodes(root);
fileManager.createOrUpdateTextFileIfRequired(contextPath,
XmlUtils.nodeToString(document), false);
}
if (props.size() > 0) {
propFileOperations.addProperties(Path.SPRING_CONFIG_ROOT
.getModulePathId(projectOperations.getFocusedModuleName()),
"email.properties", props, true, true);
}
}
/**
* Returns the canonical path of the user project's applicationContext.xml
* file.
*
* @return a non-blank path
*/
private String getApplicationContextPath() {
return pathResolver.getFocusedIdentifier(Path.SPRING_CONFIG_ROOT,
"applicationContext.xml");
}
/**
* Generates the "send email" method to be added to the domain type
*
* @param mailSenderName the name of the MailSender field (required)
* @param async whether to send the email asynchronously
* @param targetClassMID the MID of the class to receive the method
* @param mutableTypeDetails the type to which the method is being added
* (required)
* @return a non-<code>null</code> method
*/
private MethodMetadataBuilder getSendMethod(
final JavaSymbolName mailSenderName, final boolean async,
final String targetClassMID,
final ClassOrInterfaceTypeDetailsBuilder cidBuilder) {
final String contextPath = getApplicationContextPath();
final Document document = XmlUtils.readXml(fileManager
.getInputStream(contextPath));
final Element root = document.getDocumentElement();
// Make a builder for the created method's body
final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
// Collect the types and names of the created method's parameters
final PairList<AnnotatedJavaType, JavaSymbolName> parameters = new PairList<AnnotatedJavaType, JavaSymbolName>();
if (getSimpleMailMessageBean(root) == null) {
// There's no SimpleMailMessage bean; use a local variable
bodyBuilder.appendFormalLine(SIMPLE_MAIL_MESSAGE
.getFullyQualifiedTypeName()
+ " "
+ LOCAL_MESSAGE_VARIABLE
+ " = new "
+ SIMPLE_MAIL_MESSAGE.getFullyQualifiedTypeName() + "();");
// Set the from address
parameters.add(STRING, new JavaSymbolName("mailFrom"));
bodyBuilder.appendFormalLine(LOCAL_MESSAGE_VARIABLE
+ ".setFrom(mailFrom);");
// Set the subject
parameters.add(STRING, new JavaSymbolName("subject"));
bodyBuilder.appendFormalLine(LOCAL_MESSAGE_VARIABLE
+ ".setSubject(subject);");
}
else {
// A SimpleMailMessage bean exists; auto-wire it into the entity and
// use it as a template
final List<AnnotationMetadataBuilder> smmAnnotations = Arrays
.asList(new AnnotationMetadataBuilder(AUTOWIRED));
final FieldMetadataBuilder smmFieldBuilder = new FieldMetadataBuilder(
targetClassMID, PRIVATE_TRANSIENT, smmAnnotations,
new JavaSymbolName(TEMPLATE_MESSAGE_FIELD),
SIMPLE_MAIL_MESSAGE);
cidBuilder.addField(smmFieldBuilder);
// Use the injected bean as a template (for thread safety)
bodyBuilder.appendFormalLine(SIMPLE_MAIL_MESSAGE
.getFullyQualifiedTypeName()
+ " "
+ LOCAL_MESSAGE_VARIABLE
+ " = new "
+ SIMPLE_MAIL_MESSAGE.getFullyQualifiedTypeName()
+ "("
+ TEMPLATE_MESSAGE_FIELD + ");");
}
// Set the to address
parameters.add(STRING, new JavaSymbolName("mailTo"));
bodyBuilder
.appendFormalLine(LOCAL_MESSAGE_VARIABLE + ".setTo(mailTo);");
// Set the message body
parameters.add(STRING, new JavaSymbolName("message"));
bodyBuilder.appendFormalLine(LOCAL_MESSAGE_VARIABLE
+ ".setText(message);");
bodyBuilder.newLine();
bodyBuilder.appendFormalLine(mailSenderName + ".send("
+ LOCAL_MESSAGE_VARIABLE + ");");
final MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(
targetClassMID, Modifier.PUBLIC, new JavaSymbolName(
"sendMessage"), JavaType.VOID_PRIMITIVE,
parameters.getKeys(), parameters.getValues(), bodyBuilder);
if (async) {
if (DomUtils.findFirstElementByName("task:annotation-driven", root) == null) {
// Add asynchronous email support to the application
if (StringUtils.isBlank(root.getAttribute("xmlns:task"))) {
// Add the "task" namespace to the Spring config file
root.setAttribute("xmlns:task", SPRING_TASK_NS);
root.setAttribute("xsi:schemaLocation",
root.getAttribute("xsi:schemaLocation") + " "
+ SPRING_TASK_NS + " " + SPRING_TASK_XSD);
}
root.appendChild(new XmlElementBuilder(
"task:annotation-driven", document).addAttribute(
"executor", "asyncExecutor").build());
root.appendChild(new XmlElementBuilder("task:executor",
document).addAttribute("id", "asyncExecutor")
.addAttribute("pool-size", "${executor.poolSize}")
.build());
// Write out the new Spring config file
fileManager.createOrUpdateTextFileIfRequired(contextPath,
XmlUtils.nodeToString(document), false);
// Update the email properties file
propFileOperations.addPropertyIfNotExists(
pathResolver.getFocusedPath(Path.SPRING_CONFIG_ROOT),
"email.properties", "executor.poolSize", "10", true);
}
methodBuilder.addAnnotation(new AnnotationMetadataBuilder(ASYNC));
}
return methodBuilder;
}
/**
* Finds the SimpleMailMessage bean in the Spring XML file with the given
* root element
*
* @param root
* @return <code>null</code> if there is no such bean
*/
private Element getSimpleMailMessageBean(final Element root) {
return XmlUtils.findFirstElement("/beans/bean[@class = '"
+ SIMPLE_MAIL_MESSAGE.getFullyQualifiedTypeName() + "']", root);
}
public void injectEmailTemplate(final JavaType targetType,
final JavaSymbolName fieldName, final boolean async) {
Validate.notNull(targetType, "Java type required");
Validate.notNull(fieldName, "Field name required");
final List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>();
annotations.add(new AnnotationMetadataBuilder(AUTOWIRED));
// Obtain the physical type and its mutable class details
final String declaredByMetadataId = typeLocationService
.getPhysicalTypeIdentifier(targetType);
final ClassOrInterfaceTypeDetails existing = typeLocationService
.getTypeDetails(targetType);
if (existing == null) {
LOGGER.warning("Aborting: Unable to find metadata for target type '"
+ targetType.getFullyQualifiedTypeName() + "'");
return;
}
final ClassOrInterfaceTypeDetailsBuilder cidBuilder = new ClassOrInterfaceTypeDetailsBuilder(
existing);
// Add the MailSender field
final FieldMetadataBuilder mailSenderFieldBuilder = new FieldMetadataBuilder(
declaredByMetadataId, PRIVATE_TRANSIENT, annotations,
fieldName, MAIL_SENDER);
cidBuilder.addField(mailSenderFieldBuilder);
// Add the "sendMessage" method
cidBuilder.addMethod(getSendMethod(fieldName, async,
declaredByMetadataId, cidBuilder));
typeManagementService.createOrUpdateTypeOnDisk(cidBuilder.build());
}
public void installEmail(final String hostServer,
final MailProtocol protocol, final String port,
final String encoding, final String username, final String password) {
Validate.notBlank(hostServer, "Host server name required");
final String contextPath = getApplicationContextPath();
final Document document = XmlUtils.readXml(fileManager
.getInputStream(contextPath));
final Element root = document.getDocumentElement();
boolean installDependencies = true;
final Map<String, String> props = new HashMap<String, String>();
Element mailBean = XmlUtils.findFirstElement("/beans/bean[@class = '"
+ JAVA_MAIL_SENDER_IMPL.getFullyQualifiedTypeName() + "']",
root);
if (mailBean != null) {
root.removeChild(mailBean);
installDependencies = false;
}
mailBean = document.createElement("bean");
mailBean.setAttribute("class",
JAVA_MAIL_SENDER_IMPL.getFullyQualifiedTypeName());
mailBean.setAttribute("id", "mailSender");
final Element property = document.createElement("property");
property.setAttribute("name", "host");
property.setAttribute("value", "${email.host}");
mailBean.appendChild(property);
root.appendChild(mailBean);
props.put("email.host", hostServer);
if (protocol != null) {
final Element pElement = document.createElement("property");
pElement.setAttribute("value", "${email.protocol}");
pElement.setAttribute("name", "protocol");
mailBean.appendChild(pElement);
props.put("email.protocol", protocol.getProtocol());
}
if (StringUtils.isNotBlank(port)) {
final Element pElement = document.createElement("property");
pElement.setAttribute("name", "port");
pElement.setAttribute("value", "${email.port}");
mailBean.appendChild(pElement);
props.put("email.port", port);
}
if (StringUtils.isNotBlank(encoding)) {
final Element pElement = document.createElement("property");
pElement.setAttribute("name", "defaultEncoding");
pElement.setAttribute("value", "${email.encoding}");
mailBean.appendChild(pElement);
props.put("email.encoding", encoding);
}
if (StringUtils.isNotBlank(username)) {
final Element pElement = document.createElement("property");
pElement.setAttribute("name", "username");
pElement.setAttribute("value", "${email.username}");
mailBean.appendChild(pElement);
props.put("email.username", username);
}
if (StringUtils.isNotBlank(password)) {
final Element pElement = document.createElement("property");
pElement.setAttribute("name", "password");
pElement.setAttribute("value", "${email.password}");
mailBean.appendChild(pElement);
props.put("email.password", password);
if (SMTP.equals(protocol)) {
final Element javaMailProperties = document
.createElement("property");
javaMailProperties.setAttribute("name", "javaMailProperties");
final Element securityProps = document.createElement("props");
javaMailProperties.appendChild(securityProps);
final Element prop = document.createElement("prop");
prop.setAttribute("key", "mail.smtp.auth");
prop.setTextContent("true");
securityProps.appendChild(prop);
final Element prop2 = document.createElement("prop");
prop2.setAttribute("key", "mail.smtp.starttls.enable");
prop2.setTextContent("true");
securityProps.appendChild(prop2);
mailBean.appendChild(javaMailProperties);
}
}
DomUtils.removeTextNodes(root);
fileManager.createOrUpdateTextFileIfRequired(contextPath,
XmlUtils.nodeToString(document), false);
if (installDependencies) {
updateConfiguration(projectOperations.getFocusedModuleName());
}
propFileOperations.addProperties(Path.SPRING_CONFIG_ROOT
.getModulePathId(projectOperations.getFocusedModuleName()),
"email.properties", props, true, true);
}
public boolean isEmailInstallationPossible() {
return projectOperations.isFocusedProjectAvailable();
}
public boolean isManageEmailAvailable() {
return projectOperations.isFocusedProjectAvailable()
&& fileManager.exists(getApplicationContextPath());
}
private void updateConfiguration(final String moduleName) {
final Element configuration = XmlUtils.getConfiguration(getClass());
final List<Dependency> dependencies = new ArrayList<Dependency>();
final List<Element> emailDependencies = XmlUtils.findElements(
"/configuration/email/dependencies/dependency", configuration);
for (final Element dependencyElement : emailDependencies) {
dependencies.add(new Dependency(dependencyElement));
}
projectOperations.addDependencies(moduleName, dependencies);
}
}