AnsweredAssumed Answered

[ Solved ] Spring bean injection in Custom mail workflow

Question asked by paride on Nov 30, 2012
Latest reply on Jan 9, 2013 by paride
Hi all,
i'm experiencing some problems with a custom workflow (in Share) i'm developing on Alfresco Community 4.0.
The workflow is very simple:

1) The user inserts an e-mail address
2) The user selects an attachment
3) The user starts the workflow

This script creates the mail:


var l = bpm_package.children.length;
var mail = actions.create("mail");
mail.parameters.to = em_mailToAddress;
mail.parameters.subject = "You have a file to view! ";
mail.parameters.from = initiator.properties["cm:email"];
mail.parameters.text = "Kindly approve the document ";
if( l ==1 )
{
  mail.parameters.node=bpm_package.children[0];
  mail.execute(bpm_package.children[0]);
}
else
{
   mail.execute(bpm_package);
}

In the file outboundSMTP-context.xml i changed the class reference in the bean id="mail", replacing
<bean id="mail" class="org.alfresco.repo.action.executer.MailActionExecuter"
with
<bean id="mail" class="org.alfresco.sample.CustomMailActionExecuter"

I had to add this custom class to manage attachments that are not supported in standard Alfresco.

Now to the point: everything works fine if i access alfresco explorer and then alfresco share to start the workflow.
But…if i don't access the explorer before, accessing directly to share, the workflow doesn't work!

I found that the problem is in the Faces Context that, in the second scenario, is found NULL.
The log proves that:

org.springframework.mail.MailPreparationException: Could not prepare mail; nested exception is java.lang.IllegalArgumentException: FacesContext must not be null
   at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:367)
   at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:344)
   at org.alfresco.sample.CustomMailActionExecuter.executeImpl(CustomMailActionExecuter.java:488)
   at org.alfresco.repo.action.executer.ActionExecuterAbstractBase.execute(ActionExecuterAbstractBase.java:196)
   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
   at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
   at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
   at java.lang.reflect.Method.invoke(Method.java:597)
   at org.alfresco.repo.management.subsystems.SubsystemProxyFactory$1.invoke(SubsystemProxyFactory.java:65)
   at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
   at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
   at $Proxy249.execute(Unknown Source)

In another discussion i found this:
"The problem here is that you are using beans that reference the JSF context. This is only initialised if you use the Alfresco Explorer client (a JSF app). The correct procedure is to use Spring injection of the bean definitions that you require for your action bean"

How can i do this? How can i find out what beans are involved?
The only thing i know is that the problem (obviously!!!) is the attachment and, precisely, i think that the Faces Context is needed when retrieving nodes from repository.
Hereunder the bean involved (outboundSMTP-context.xml)

 <bean id="mail" class="org.alfresco.sample.CustomMailActionExecuter"
      parent="action-executer">
      <!– <bean id="mail" class="org.alfresco.repo.action.executer.MailActionExecuter" –>
      <property name="mailService">
         <ref bean="mailService"></ref>
      </property>
      <property name="templateService">
         <ref bean="templateService"></ref>
      </property>
      <property name="personService">
         <ref bean="personService"></ref>
      </property>
      <property name="authenticationService">
         <ref bean="authenticationService"></ref>
      </property>
      <property name="nodeService">
         <ref bean="nodeService"></ref>
      </property>
      <property name="authorityService">
         <ref bean="authorityService"></ref>
      </property>
      <property name="serviceRegistry">
         <ref bean="ServiceRegistry"></ref>
      </property>
      <property name="headerEncoding">
         <value>${mail.header}</value>
      </property>
      <property name="fromAddress">
         <value>${mail.from.default}</value>
      </property>
      <property name="repoRemoteUrl">
         <value>${repo.remote.url}</value>
      </property>
      <property name="sendTestMessage">
         <value>${mail.testmessage.send}</value>
      </property>
      <property name="testMessageTo">
         <value>${mail.testmessage.to}</value>
      </property>
      <property name="testMessageSubject">
         <value>${mail.testmessage.subject}</value>
      </property>
      <property name="testMessageText">
         <value>${mail.testmessage.text}</value>
      </property>
   </bean>

and the "CustomMailExecuter.java"

package org.alfresco.sample;

/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/

import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.faces.context.FacesContext;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.action.ParameterDefinitionImpl;
import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
import org.alfresco.repo.action.executer.MailActionExecuter;
import org.alfresco.repo.action.executer.TestModeable;
import org.alfresco.repo.template.DateCompareMethod;
import org.alfresco.repo.template.HasAspectMethod;
import org.alfresco.repo.template.I18NMessageMethod;
import org.alfresco.repo.template.TemplateNode;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.TemplateService;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.util.TempFileProvider;
import org.alfresco.web.bean.repository.Repository;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.validator.EmailValidator;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.mail.javamail.MimeMessagePreparator;

/**
* Mail action executor implementation.
*
* @author Roy Wetherall -editet by Savic Prvoslav Savic.Prvoslav@gmail.com MailActionExecuter
*/
public class CustomMailActionExecuter extends ActionExecuterAbstractBase implements InitializingBean, TestModeable
{
   private static Log logger = LogFactory.getLog(MailActionExecuter.class);

   /**
    * Action executor constants
    */
   public static final String NAME = "mail";
   public static final String PARAM_TO = "to";
   public static final String PARAM_TO_MANY = "to_many";
   public static final String PARAM_SUBJECT = "subject";
   public static final String PARAM_TEXT = "text";
   public static final String PARAM_FROM = "from";
   public static final String PARAM_TEMPLATE = "template";
   public static final String PARAM_NODE = "node";

   /**
    * From address
    */
   private static final String FROM_ADDRESS = "alfresco@alfresco.org";

   private static final String REPO_REMOTE_URL = "http://localhost:8080/alfresco";

   /**
    * The java mail sender
    */
   private JavaMailSender javaMailSender;

   /**
    * The Template service
    */
   private TemplateService templateService;

   /**
    * The Person service
    */
   private PersonService personService;

   /**
    * The Authentication service
    */
   private AuthenticationService authService;

   /**
    * The Node Service
    */
   private NodeService nodeService;

   /**
    * The Authority Service
    */
   private AuthorityService authorityService;

   /**
    * The Service registry
    */
   private ServiceRegistry serviceRegistry;

   /**
    * Mail header encoding scheme
    */
   private String headerEncoding = null;

   /**
    * Default from address
    */
   private String fromAddress = null;

   /**
    * Default alfresco installation url
    */
   private String repoRemoteUrl = null;

   private boolean sendTestMessage = false;
   private String testMessageTo = null;
   private String testMessageSubject = "Test message";
   private String testMessageText = "This is a test message.";

   /**
    * Test mode prevents email messages from being sent. It is used when unit testing when we don't actually want to
    * send out email messages.
    *
    * MER 20/11/2009 This is a quick and dirty fix. It should be replaced by being "mocked out" or some other better
    * way of running the unit tests.
    */
   private boolean testMode = false;
   private MimeMessage lastTestMessage;

   /**
    * @param javaMailSender
    *            the java mail sender
    */
   public void setMailService(JavaMailSender javaMailSender)
   {
      this.javaMailSender = javaMailSender;
   }

   /**
    * @param templateService
    *            the TemplateService
    */
   public void setTemplateService(TemplateService templateService)
   {
      this.templateService = templateService;
   }

   /**
    * @param personService
    *            the PersonService
    */
   public void setPersonService(PersonService personService)
   {
      this.personService = personService;
   }

   /**
    * @param authService
    *            the AuthenticationService
    */
   public void setAuthenticationService(AuthenticationService authService)
   {
      this.authService = authService;
   }

   /**
    * @param serviceRegistry
    *            the ServiceRegistry
    */
   public void setServiceRegistry(ServiceRegistry serviceRegistry)
   {
      this.serviceRegistry = serviceRegistry;
   }

   /**
    * @param authorityService
    *            the AuthorityService
    */
   public void setAuthorityService(AuthorityService authorityService)
   {
      this.authorityService = authorityService;
   }

   /**
    * @param nodeService
    *            the NodeService to set.
    */
   public void setNodeService(NodeService nodeService)
   {
      this.nodeService = nodeService;
   }

   /**
    * @param headerEncoding
    *            The mail header encoding to set.
    */
   public void setHeaderEncoding(String headerEncoding)
   {
      this.headerEncoding = headerEncoding;
   }

   /**
    * @param fromAddress
    *            The default mail address.
    */
   public void setFromAddress(String fromAddress)
   {
      this.fromAddress = fromAddress;
   }

   /**
    *
    * @param repoRemoteUrl
    *            The default alfresco installation url
    */
   public void setRepoRemoteUrl(String repoRemoteUrl)
   {
      this.repoRemoteUrl = repoRemoteUrl;
   }

   public void setTestMessageTo(String testMessageTo)
   {
      this.testMessageTo = testMessageTo;
   }

   public void setTestMessageSubject(String testMessageSubject)
   {
      this.testMessageSubject = testMessageSubject;
   }

   public void setTestMessageText(String testMessageText)
   {
      this.testMessageText = testMessageText;
   }

   public void setSendTestMessage(boolean sendTestMessage)
   {
      this.sendTestMessage = sendTestMessage;
   }

   @Override
   public void init()
   {
      super.init();
      if (sendTestMessage)
      {
         Map<String, Serializable> params = new HashMap<String, Serializable>();
         params.put(PARAM_TO, testMessageTo);
         params.put(PARAM_SUBJECT, testMessageSubject);
         params.put(PARAM_TEXT, testMessageText);

         Action ruleAction = serviceRegistry.getActionService().createAction(NAME, params);
         executeImpl(ruleAction, null);
      }
   }

   /**
    * Initialise bean
    */
   public void afterPropertiesSet() throws Exception
   {
      if (fromAddress == null || fromAddress.length() == 0)
      {
         fromAddress = FROM_ADDRESS;
      }

      if (repoRemoteUrl == null || repoRemoteUrl.length() == 0)
      {
         repoRemoteUrl = REPO_REMOTE_URL;
      }
   }

   /**
    * Send an email message
    *
    * @throws AlfrescoRuntimeExeption
    */
   @Override
   protected void executeImpl(final Action ruleAction, final NodeRef actionedUponNodeRef)
   {
      // Create the mime mail message
      MimeMessagePreparator mailPreparer = new MimeMessagePreparator()
      {
         @SuppressWarnings("unchecked")
         public void prepare(MimeMessage mimeMessage) throws MessagingException
         {

            if (logger.isDebugEnabled())
            {
               logger.debug(ruleAction.getParameterValues());
            }

            MimeMessageHelper message = new MimeMessageHelper(mimeMessage);

            NodeRef nodeRef = (NodeRef) ruleAction.getParameterValue(PARAM_NODE);

            if (nodeRef == null)
            {
               message = new MimeMessageHelper(mimeMessage);
            }
            else
            {

               message = new MimeMessageHelper(mimeMessage, true);
            }
            if (nodeRef != null)
            {

               ContentReader contentReader = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getContentService().getReader(nodeRef,
                  ContentModel.PROP_CONTENT);

               File file = TempFileProvider.createTempFile("mail", "action");

               contentReader.getContent(file);

               NodeService nodeService = Repository.getServiceRegistry(FacesContext.getCurrentInstance()).getNodeService();
               if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TITLED))
               {
                  String title = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE);
                  if (title != null && title.trim().equals(""))
                  {
                     message.addAttachment(title, file);
                  }
               }
               else
               {
                  String nodeName = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);

                  message.addAttachment(nodeName, file);

               }

               message.addAttachment("test", file);
            }

            //

            // set header encoding if one has been supplied
            if (headerEncoding != null && headerEncoding.length() != 0)
            {
               // mimeMessage.setHeader("Content-Transfer-Encoding", headerEncoding);

            }

            // set recipient
            String to = (String) ruleAction.getParameterValue(PARAM_TO);
            if (to != null && to.length() != 0)
            {
               message.setTo(to);
            }
            else
            {
               // see if multiple recipients have been supplied - as a list of authorities
               Serializable authoritiesValue = ruleAction.getParameterValue(PARAM_TO_MANY);
               List<String> authorities = null;
               if (authoritiesValue != null)
               {
                  if (authoritiesValue instanceof String)
                  {
                     authorities = new ArrayList<String>(1);
                     authorities.add((String) authoritiesValue);
                  }
                  else
                  {
                     authorities = (List<String>) authoritiesValue;
                  }
               }

               if (authorities != null && authorities.size() != 0)
               {
                  List<String> recipients = new ArrayList<String>(authorities.size());
                  for (String authority : authorities)
                  {
                     AuthorityType authType = AuthorityType.getAuthorityType(authority);
                     if (authType.equals(AuthorityType.USER))
                     {
                        if (personService.personExists(authority) == true)
                        {
                           NodeRef person = personService.getPerson(authority);
                           String address = (String) nodeService.getProperty(person, ContentModel.PROP_EMAIL);
                           if (address != null && address.length() != 0 && validateAddress(address))
                           {
                              recipients.add(address);
                           }
                        }
                     }
                     else if (authType.equals(AuthorityType.GROUP))
                     {
                        // else notify all members of the group
                        Set<String> users = authorityService.getContainedAuthorities(AuthorityType.USER, authority, false);
                        for (String userAuth : users)
                        {
                           if (personService.personExists(userAuth) == true)
                           {
                              NodeRef person = personService.getPerson(userAuth);
                              String address = (String) nodeService.getProperty(person, ContentModel.PROP_EMAIL);
                              if (address != null && address.length() != 0)
                              {
                                 recipients.add(address);
                              }
                           }
                        }
                     }
                  }

                  message.setTo(recipients.toArray(new String[recipients.size()]));
               }
               else
               {
                  // No recipiants have been specified
                  logger.error("No recipiant has been specified for the mail action");
               }
            }

            // set subject line
            message.setSubject((String) ruleAction.getParameterValue(PARAM_SUBJECT));

            // See if an email template has been specified
            String text = null;
            NodeRef templateRef = (NodeRef) ruleAction.getParameterValue(PARAM_TEMPLATE);
            if (templateRef != null)
            {
               // build the email template model
               Map<String, Object> model = createEmailTemplateModel(actionedUponNodeRef);

               // process the template against the model
               text = templateService.processTemplate("freemarker", templateRef.toString(), model);
            }

            // set the text body of the message
            if (text == null)
            {
               text = (String) ruleAction.getParameterValue(PARAM_TEXT);
            }
            message.setText(text, true);

            // set the from address
            NodeRef person = personService.getPerson(authService.getCurrentUserName());

            String fromActualUser = null;
            if (person != null)
            {
               fromActualUser = (String) nodeService.getProperty(person, ContentModel.PROP_EMAIL);
            }
            if (fromActualUser != null && fromActualUser.length() != 0)
            {
               message.setFrom(fromActualUser);
            }
            else
            {
               String from = (String) ruleAction.getParameterValue(PARAM_FROM);
               if (from == null || from.length() == 0)
               {
                  message.setFrom(fromAddress);
               }
               else
               {
                  message.setFrom(from);
               }
            }
         }
      };

      try
      {
         // Send the message unless we are in "testMode"
         if (!testMode)
         {
            javaMailSender.send(mailPreparer);
         }
         else
         {
            try
            {
               MimeMessage mimeMessage = javaMailSender.createMimeMessage();
               mailPreparer.prepare(mimeMessage);
               lastTestMessage = mimeMessage;
            }
            catch (Exception e)
            {
               System.err.println(e);
            }
         }
      }
      catch (MailException e)
      {
         String to = (String) ruleAction.getParameterValue(PARAM_TO);
         if (to == null)
         {
            Object obj = ruleAction.getParameterValue(PARAM_TO_MANY);
            if (obj != null)
            {
               to = obj.toString();
            }
         }

         logger.error("Failed to send email to " + to, e);

         throw new AlfrescoRuntimeException("Failed to send email to:" + to, e);
      }
   }

   /**
    * Return true if address has valid format
    *
    * @param address
    * @return
    */
   private boolean validateAddress(String address)
   {
      boolean result = false;

      EmailValidator emailValidator = EmailValidator.getInstance();
      if (emailValidator.isValid(address))
      {
         result = true;
      }
      else
      {
         logger.error("Failed to send email to '" + address + "' as the address is incorrectly formatted");
      }

      return result;
   }

   /**
    * @param ref
    *            The node representing the current document ref
    *
    * @return Model map for email templates
    */
   private Map<String, Object> createEmailTemplateModel(NodeRef ref)
   {
      Map<String, Object> model = new HashMap<String, Object>(8, 1.0f);

      NodeRef person = personService.getPerson(authService.getCurrentUserName());
      model.put("person", new TemplateNode(person, serviceRegistry, null));
      model.put("document", new TemplateNode(ref, serviceRegistry, null));
      NodeRef parent = serviceRegistry.getNodeService().getPrimaryParent(ref).getParentRef();
      model.put("space", new TemplateNode(parent, serviceRegistry, null));

      // current date/time is useful to have and isn't supplied by FreeMarker by default
      model.put("date", new Date());

      // add custom method objects
      model.put("hasAspect", new HasAspectMethod());
      model.put("message", new I18NMessageMethod());
      model.put("dateCompare", new DateCompareMethod());
      model.put("url", new URLHelper(repoRemoteUrl));

      return model;
   }

   /**
    * Add the parameter definitions
    */
   @Override
   protected void addParameterDefinitions(List<ParameterDefinition> paramList)
   {
      paramList.add(new ParameterDefinitionImpl(PARAM_TO, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_TO)));
      paramList.add(new ParameterDefinitionImpl(PARAM_TO_MANY, DataTypeDefinition.ANY, false, getParamDisplayLabel(PARAM_TO_MANY), true));
      paramList.add(new ParameterDefinitionImpl(PARAM_SUBJECT, DataTypeDefinition.TEXT, true, getParamDisplayLabel(PARAM_SUBJECT)));
      paramList.add(new ParameterDefinitionImpl(PARAM_TEXT, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_TEXT)));
      paramList.add(new ParameterDefinitionImpl(PARAM_FROM, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_FROM)));
      paramList.add(new ParameterDefinitionImpl(PARAM_TEMPLATE, DataTypeDefinition.NODE_REF, false, getParamDisplayLabel(PARAM_TEMPLATE), false,
         "ac-email-templates"));
   }

   public void setTestMode(boolean testMode)
   {
      this.testMode = testMode;
   }

   public boolean isTestMode()
   {
      return testMode;
   }

   /**
    * Returns the most recent message that wasn't sent because TestMode had been enabled.
    */
   public MimeMessage retrieveLastTestMessage()
   {
      return lastTestMessage;
   }

   public static class URLHelper
   {
      String contextPath;
      String serverPath;

      public URLHelper(String repoRemoteUrl)
      {
         String[] parts = repoRemoteUrl.split("/");
         this.contextPath = "/" + parts[parts.length - 1];
         this.serverPath = parts[0] + "//" + parts[2];
      }

      public String getContext()
      {
         return this.contextPath;
      }

      public String getServerPath()
      {
         return this.serverPath;
      }
   }
}


Any suggestions?
Thanks in advance,

Paride

Outcomes