Sending HTML mail for site invitation(alfresco 3.4d)

You may have read my previous blog of Html Mail Action inside alfresco 3.4d.If you have not read this , than before continuing with this you need to read that blog.

Below is the screen shot of site invitation page in alfresco using which we can invite user to join site.



Normally if we want to change site invitation template , than we need to modify invite-email.ftl file which is located at /Data Dictionary/Email Templates/invite location.Now as you all know for adding HTML content inside any templates in alfresco the custom action should support HTML content.Previously we have created an action which supports HTML content.Now in this case for site invitation it executes in built mail action, which does not supports html content.For this we need to modify site invitation workflow so that it executes customized mail action.
Follow below steps for that.


Step 1 : Customize Site Invitation Workflow
File which needs to be customized for modifying this workflow is located on below location tomcat/webapps/alfresco/WEB-INF/classes/alfresco/workflow/invitation-nominated_processdefinition.xml.Content of that file is as below.





   
   
   
   
      

      
         
      
   

   
      
         #{bpm_assignee.properties['cm:userName']}
      
   

   
      
      
         
      
      
         
      
      
         
      
   

   
      
      
   

   
      
      
   

   


In above file if you notice line number 15.We have changed java package.For redeploying workflow definition there are 2 ways.
First is changing tomcat\webapps\alfresco\WEB-INF\classes\alfresco\bootstrap-context.xml file and making redeploy true and than restart server.
Second is do hot deployment of workflow.One of my colleague has written very good blog on Hot Deployment of Alfresco Workflow.

Step 2 : Change Site Invitation Java Custom Action

First we will modify a file which is referenced in site invitation workflow.In that file we will set html parameter true.Below will be the content of that file.
/*
 * 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/>.
 */

package org.alfresco.repo.invitation.site.ds;

import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.*;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.alfresco.repo.i18n.MessageService;
import org.alfresco.repo.invitation.site.ds.InviteSender;
import org.alfresco.repo.jscript.ScriptNode;
import org.alfresco.repo.model.Repository;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.repo.workflow.jbpm.JBPMSpringActionHandler;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.namespace.NamespaceService;
import org.jbpm.graph.exe.ExecutionContext;
import org.springframework.beans.factory.BeanFactory;

public class SendInviteAction extends JBPMSpringActionHandler
{
    // TODO Select Version Id.
    private static final long serialVersionUID = 8133039174866049136L;

    private InviteSender inviteSender;
    private NamespaceService namespaceService;

    @Override
    protected void initialiseHandler(BeanFactory factory)
    {
        Repository repository = (Repository) factory.getBean("repositoryHelper");
        ServiceRegistry services = (ServiceRegistry) factory.getBean(ServiceRegistry.SERVICE_REGISTRY);
        MessageService messageService= (MessageService) factory.getBean("messageService");
        inviteSender = new InviteSender(services, repository, messageService);
        namespaceService = services.getNamespaceService();
    }

    public void execute(final ExecutionContext context) throws Exception
    {

        Collection<String> propertyNames = Arrays.asList(wfVarInviteeUserName,//
                    wfVarResourceName,//
                    wfVarInviterUserName,//
                    wfVarInviteeUserName,//
                    wfVarRole,//
                    wfVarInviteeGenPassword,//
                    wfVarResourceName,//
                    wfVarInviteTicket,//
                    wfVarServerPath,//
                    wfVarAcceptUrl,//
                    wfVarRejectUrl,
                    InviteSender.WF_INSTANCE_ID);
        Map<String, String> properties = makePropertiesFromContext(context, propertyNames);

        String packageName = WorkflowModel.ASSOC_PACKAGE.toPrefixString(namespaceService).replace(":", "_");
        ScriptNode packageNode = (ScriptNode) context.getVariable(packageName);
        String packageRef = packageNode.getNodeRef().toString();
        properties.put(InviteSender.WF_PACKAGE, packageRef);
        
        String instanceName=WorkflowModel.PROP_WORKFLOW_INSTANCE_ID.toPrefixString(namespaceService).replace(":", "_");
        String instanceId = (String) context.getVariable(instanceName);
        properties.put(InviteSender.WF_INSTANCE_ID, instanceId);
        properties.put(InviteSender.IS_HTML,"true");
        inviteSender.sendMail(properties);
    }

    private Map<String, String> makePropertiesFromContext(ExecutionContext context, Collection<String> propertyNames)
    {
        Map<String, String> props = new HashMap<String, String>();
        for (String name : propertyNames)
        {
            String value = (String) context.getVariable(name);
            props.put(name, value);
        }
        return props;
    }
}

Now we will modify another file which is actually sending mail.In this we need to create our html action if the parameter html is true and we will set html parameter true if we want html content.Below is content of that file.Check line number 114 and 115 for this on below file.
/*
 * 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/>.
 */

package org.alfresco.repo.invitation.site.ds;

import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.*;

import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.alfresco.model.ContentModel;
import org.alfresco.repo.action.executer.MailActionExecuter;
import org.alfresco.repo.i18n.MessageService;
import org.alfresco.repo.model.Repository;
import org.alfresco.repo.search.SearcherException;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.invitation.InvitationException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.repository.TemplateService;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.SearchParameters;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.site.SiteService;
import org.springframework.extensions.surf.util.ParameterCheck;
import org.springframework.extensions.surf.util.URLEncoder;

/**
 * This class is responsible for sending email invitations, allowing nominated
 * user's to join a Site.
 * 
 * @author Nick Smith
 */
public class InviteSender
{
    public static final String WF_INSTANCE_ID = "wf_instanceId";
    public static final String WF_PACKAGE = "wf_package";

    private static final List<String> expectedProperties = Arrays.asList(wfVarInviteeUserName,//
                wfVarResourceName,//
                wfVarInviterUserName,//
                wfVarInviteeUserName,//
                wfVarRole,//
                wfVarInviteeGenPassword,//
                wfVarResourceName,//
                wfVarInviteTicket,//
                wfVarServerPath,//
                wfVarAcceptUrl,//
                wfVarRejectUrl, WF_INSTANCE_ID,//
                WF_PACKAGE);
    public static final String IS_HTML="IS_HTML";
    public static final String MAILHTMLACTION="mailHtmlAction";
    private final ActionService actionService;
    private final NodeService nodeService;
    private final PersonService personService;
    private final SearchService searchService;
    private final SiteService siteService;
    private final TemplateService templateService;
    private final Repository repository;
    private final MessageService messageService;
    
    public InviteSender(ServiceRegistry services, Repository repository, MessageService messageService)
    {
        this.actionService = services.getActionService();
        this.nodeService = services.getNodeService();
        this.personService = services.getPersonService();
        this.searchService = services.getSearchService();
        this.siteService = services.getSiteService();
        this.templateService = services.getTemplateService();
        this.repository = repository;
        this.messageService = messageService;
    }

    /**
     * Sends an invitation email.
     * 
     * @param properties A Map containing the properties needed to send the
     *            email.
     */
    public void sendMail(Map<String, String> properties)
    {
        checkProperties(properties);
        ParameterCheck.mandatory("Properties", properties);
        NodeRef inviter = personService.getPerson(properties.get(wfVarInviterUserName));
        String inviteeName = properties.get(wfVarInviteeUserName);
        NodeRef invitee = personService.getPerson(inviteeName);
        Action mail = null;
        if(properties.get(InviteSender.IS_HTML).equals("true")){
         mail=actionService.createAction(MAILHTMLACTION);
            mail.setParameterValue("html",true);
        }else{
         mail=actionService.createAction(MailActionExecuter.NAME);
        }
        mail.setParameterValue(MailActionExecuter.PARAM_FROM, getEmail(inviter));
        mail.setParameterValue(MailActionExecuter.PARAM_TO, getEmail(invitee));
        mail.setParameterValue(MailActionExecuter.PARAM_SUBJECT, buildSubject(properties));
        String mailText = buildMailText(properties, inviter, invitee);
        mail.setParameterValue(MailActionExecuter.PARAM_TEXT, mailText);
        actionService.executeAction(mail, getWorkflowPackage(properties));
    }

    /**
     * @param properties
     */
    private void checkProperties(Map<String, String> properties)
    {
        Set<String> keys = properties.keySet();
        if (!keys.containsAll(expectedProperties))
        {
            LinkedList<String> missingProperties = new LinkedList<String>(expectedProperties);
            missingProperties.removeAll(keys);
            throw new InvitationException("The following mandatory properties are missing:\n" + missingProperties);
        }
    }

    private String buildSubject(Map<String, String> properties)
    {
     return messageService.getMessage("invitation.invitesender.email.subject", getSiteName(properties));
    }

    private String buildMailText(Map<String, String> properties, NodeRef inviter, NodeRef invitee)
    {
        String template = getEmailTemplate();
        Map<String, Object> model = makeDefaultModel();
        Map<String, String> args = buildArgs(properties, inviter, invitee);
        model.put("args", args);
        return templateService.processTemplate(template, model);
    }

    private String getEmailTemplate()
    {
        NodeRef template = getEmailTemplateNodeRef();
        return template.toString();
    }

    private Map<String, String> buildArgs(Map<String, String> properties, NodeRef inviter, NodeRef invitee)
    {
        String params = buildUrlParamString(properties);
        String serverPath = properties.get(wfVarServerPath);
        String acceptLink = serverPath + properties.get(wfVarAcceptUrl) + params;
        String rejectLink = serverPath + properties.get(wfVarRejectUrl) + params;

        Map<String, String> args = new HashMap<String, String>();
        args.put("inviteePersonRef", invitee.toString());
        args.put("inviterPersonRef", inviter.toString());
        args.put("siteName", getSiteName(properties));
        args.put("inviteeSiteRole", getRoleName(properties));
        args.put("inviteeUserName", properties.get(wfVarInviteeUserName));
        args.put("inviteeGenPassword", properties.get(wfVarInviteeGenPassword));
        args.put("acceptLink", acceptLink);
        args.put("rejectLink", rejectLink);
        return args;
    }

    private String getRoleName(Map<String, String> properties) {
     String roleName = properties.get(wfVarRole);
     String role = messageService.getMessage("invitation.invitesender.email.role."+roleName);
     if(role == null)
     {
   role = roleName;
  }
     return role;
 }

 private Map<String, Object> makeDefaultModel()
    {
        NodeRef person = repository.getPerson();
        NodeRef companyHome = repository.getCompanyHome();
        NodeRef userHome = repository.getUserHome(person);
        Map<String, Object> model = templateService.buildDefaultModel(person, companyHome, userHome, null, null);
        return model;
    }

    private String getEmail(NodeRef person)
    {
        return (String) nodeService.getProperty(person, ContentModel.PROP_EMAIL);
    }

    private NodeRef getWorkflowPackage(Map<String, String> properties)
    {
        String packageRef = properties.get(WF_PACKAGE);
        return new NodeRef(packageRef);
    }

    private NodeRef getEmailTemplateNodeRef()
    {
        StoreRef spacesStore = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");
        String query = " PATH:\"app:company_home/app:dictionary/app:email_templates/cm:invite/cm:invite-email.ftl\"";

        SearchParameters searchParams = new SearchParameters();
        searchParams.addStore(spacesStore);
        searchParams.setLanguage(SearchService.LANGUAGE_LUCENE);
        searchParams.setQuery(query);

        ResultSet results = null;
        try
        {
            results = searchService.query(searchParams);
            List<NodeRef> nodeRefs = results.getNodeRefs();
            if (nodeRefs.size() == 1)
                return nodeRefs.get(0);
            else
                throw new InvitationException("Cannot find the email templatte!");
        }
        catch (SearcherException e)
        {
            throw new InvitationException("Cannot find the email templatte!", e);
        }
        finally
        {
            if (results != null)
                results.close();
        }
    }

    private String buildUrlParamString(Map<String, String> properties)
    {
        StringBuilder params = new StringBuilder("?inviteId=");
        params.append(properties.get(WF_INSTANCE_ID));
        params.append("&inviteeUserName=");
        params.append(URLEncoder.encode(properties.get(wfVarInviteeUserName)));
        params.append("&siteShortName=");
        params.append(properties.get(wfVarResourceName));
        params.append("&inviteTicket=");
        params.append(properties.get(wfVarInviteTicket));
        return params.toString();
    }

    private String getSiteName(Map<String, String> properties)
    {
        String siteFullName = properties.get(wfVarResourceName);
        SiteInfo site = siteService.getSite(siteFullName);
        if (site == null)
            throw new InvitationException("The site " + siteFullName + " could not be found.");

        String siteName = site.getShortName();
        String siteTitle = site.getTitle();
        if (siteTitle != null && siteTitle.length() > 0)
        {
            siteName = siteTitle;
        }
        return siteName;
    }
}


Step 3 :Add html content in site invitation template
Now modify the content of site invitation template, as we are doing normally.