Sunday 29 March 2015

Dispatcher Setup on Apache2/Ubuntu14.04 in AEM6 / CQ5

Hi, 

In this blog i would like to demonstrate how to set up Dispatcher Module in Apache2 and Ubuntu14.04. We use the dispatcher for Load Balancing and to Maintain the Cache. I will strict this blog only for How to install Dispatcher module and enable the cache. There are lots of settings as per the use-case we may need to apply on the dispatcher. This blog will help you to create a road map for those.

Use-Case : Set up dispatcher in Ubuntu14.04 and Enable the Cache.

Step 1 : Download the dispatcher from Package Share as per OS you are using. In my case it is :


Step 2 : Ubuntu14.04 has the apache already installed. You can check via hitting http://localhost/. You will get the welcome screen of apache. If it doesn't show, try to troubleshoot or re-install it. By Default Apache install under /etc directory with name of apache2.

Step 3 : Extract the dispatcher module downloaded in Step 1. You will get one folder with the name of conf and one file with .so extension which is the dispatcher module. Set the full permission on them.

Step 4 : Move you dispatcher-apache2.4-4.1.9.so file to /usr/lib/apache2/modules location. Create folder conf under /etc/apache2 and move the file dispatcher.any to /etc/apache2/conf folder.

Step 5 : Now configure your apache2.conf file under /etc/apache2 directory.

a) At line 69 you will find #ServerRoot "/etc/apache2" change it to  

 ServerRoot "/etc/apache2"
 ServerName localhost:80 


b) At the end after line # vim: syntax=apache ts=4 sw=4 sts=4 sr noet add the below snippet :

##### Dispatcher Configuration ######

LoadModule dispatcher_module /usr/lib/apache2/modules/dispatcher-apache2.4-4.1.9.so

<IfModule disp_apache2.c>
    # location of the configuration file. eg: 'conf/dispatcher.any'
    DispatcherConfig conf/dispatcher.any
    # location of the dispatcher log file. eg: 'logs/dispatcher.log'
    DispatcherLog /var/log/apache2/dispatcher.log
    DispatcherLogLevel 3
    DispatcherNoServerHeader 0
    DispatcherDeclineRoot 0
    DispatcherUseProcessedURL 1
    #DispatcherPassError 0
    DispatcherPassError 1   
    SetHandler dispatcher-handler
</IfModule>
SetOutputFilter INCLUDES


c) Restart the apache service using sudo service apache2 restart command. Apache2 server will restart without warning/error means configuration is [OK].

Step 6 : Now configure the dispatcher.any file that you placed under conf folder as in step 4.

a) Change the configuration of rend01 as below :
 /rend01
        {
        # Hostname or IP of the render
        /hostname "localhost"
        # Port of the render
        /port "4503"
        # Connect timeout in milliseconds, 0 to wait indefinitely
        # /timeout "0"
        }


b) Change the stafilelevel, look for  #/statfileslevel "0" and change it to 

          /statfileslevel "1"


Statfile Level tells, dispatcher will invalidate the cache from which level of cached pages. You can look over the adobe docs for more information.

c) Last thing to make sure that you have directory /opt/communique/dispatcher/cache with full permissions. This is the location where cache file will be maintain. You can change it via /docroot "/opt/communique/dispatcher/cache"  element in dispatcher.any.

Step 7 : Start your Publish instance via port 4503. And configure the dispatcher flush agent.

Step 8 : Hit  the URL http://localhost/content/geometrixx-outdoors/en.html. If you are able to see the geometrixx page . It means your publish successfully connected with dispatcher.

Step 9 : Check the directory /opt/communique/dispatcher/cache , it must contains two folder /content and /etc. If these are not there check out the permission for this folder & give full permission.

Two more things to add :
sudo a2enmod include (to enable SSI)
Uncomment  /allowAuthorized "1" from dispatcher.any file

Step 10 : Enjoy Caching :)

Thanks !!!!


Saturday 28 March 2015

Newsletters-Email Campaigns in AEM6/CQ5


Email Marketing is one of the important aspect which enable the organizations to interact with their audience frequently for latest update, news etc. There are lot of Email Marketing softwares are available in the market. AEM also ships with the such functionality where AEM can send the the emails such as newsletters to list of users that exists in JCR.


Use-Case : Send the NewsLetters to the Subscribed Customers.


Here are the steps : 

1) First configure the Day CQ Mail Service via OSGi configuration.
2) Create Brand ---> Create Campaign(under brand) ----> Create Newsletter(under Campaign)


3) Create a Lead through Campaign Console, just mention the Mail and save.


4) Open the Newsletter and change the settings, Just fill From Name, From Address, Subject for now & OK.



5) Now to test the newsletter and to check whether email service properly configured or not. Press Ctrl+Alt+C and select the profile (the lead you created in step 3). Reload the Page.

6) Click on the Test button display on the editbar of newsletter.

7) A dialog will open, where you can add the recipient address and test. If successful, you will get the message as below.


 In case if issue comes then try to publish/replicate your newsletter and lead, also look for the mail configuration.

8) Newsletters are mean to send the list of users subscribed, So create a List from Campaign Console of AEM. This List is same as group in repository.

9) Click on the settings from step 6. And in bottom of dialog add the List (Default Recipient List) that you created in step 8. Ty to keep List name should name meaningful such as newsletters-xxxxxx.


10) Now create a custom component say : Subscribe me. This component will add the subscribed user under the group/list you created. Below snippet of code of jsp :


 <%@include file="../../../global.jsp" %>  
 <%@page session="false" %>  
 <div id="response">  
   <pre style="color: #ff0000"></pre>  
 </div>  
 <form id="subscribe">  
   <input type="text" name="subscribe_email"/></br></br>  
   <button type="submit">Subscribe Now</button>  
 </form>  
 <script>  
   (function ($) {  
     function processForm(e) {  
       $.ajax({  
         url: '/bin/subscriber',  
         dataType: 'text',  
         type: 'post',  
         data: $(this).serialize(),  
         success: function (data) {  
           console.log(data);  
           $('#response pre').html(data);  
         },  
         error: function (jqXhr, textStatus, errorThrown) {  
           console.log(errorThrown);  
         }  
       });  
       e.preventDefault();  
     }  
     $('#subscribe').submit(processForm);  
   })(jQuery);  
 </script>  

11) Create servlet to handle the request for subsciber :


 package com.aem.servlets;  
 import org.apache.commons.lang.RandomStringUtils;  
 import org.apache.felix.scr.annotations.Component;  
 import org.apache.felix.scr.annotations.Properties;  
 import org.apache.felix.scr.annotations.Property;  
 import org.apache.felix.scr.annotations.Service;  
 import org.apache.jackrabbit.api.security.user.Authorizable;  
 import org.apache.jackrabbit.api.security.user.Group;  
 import org.apache.jackrabbit.api.security.user.User;  
 import org.apache.jackrabbit.api.security.user.UserManager;  
 import org.apache.sling.api.SlingHttpServletRequest;  
 import org.apache.sling.api.SlingHttpServletResponse;  
 import org.apache.sling.api.resource.Resource;  
 import org.apache.sling.api.resource.ResourceResolver;  
 import org.apache.sling.api.servlets.SlingAllMethodsServlet;  
 import org.slf4j.Logger;  
 import org.slf4j.LoggerFactory;  
 import javax.jcr.Session;  
 import javax.jcr.ValueFactory;  
 import java.util.Iterator;  
 import java.util.Map;  
 @Component(immediate = true, metatype = false, label = "Create Subscriber")  
 @Service  
 @Properties(value = {  
     @Property(name = "sling.servlet.methods", value = "POST"),  
     @Property(name = "sling.servlet.paths", value = "/bin/subscriber")  
 })  
 public class GenerateSubscriberServlet extends SlingAllMethodsServlet {  
   private static final Logger logger = LoggerFactory.getLogger(GenerateSubscriberServlet.class);  
   @Override  
   protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) {  
     ResourceResolver resourceResolver = request.getResourceResolver();  
     boolean flag = true;  
     String msgToPrint = "";  
     try {  
       Map<String, String> parameterMap = request.getParameterMap();  
       String email_id = "";  
       if (parameterMap.containsKey("subscribe_email") && resourceResolver != null) {  
         email_id = request.getParameter("subscribe_email");  
         UserManager userManager = resourceResolver.adaptTo(UserManager.class);  
         if (userManager != null) {  
 // Change Group Name as you given in step 9.  
           Group group = resourceResolver.resolve("/home/groups/n/newsletters").adaptTo(Group.class);  
           Iterator<Authorizable> authorizableIterator = group.getMembers();  
           while (authorizableIterator.hasNext()) {  
             if (authorizableIterator.next().getPrincipal().getName().equals(email_id)) {  
               msgToPrint = "User already Registered";  
               flag = false;  
               break;  
             }  
           }  
           if (flag) {  
             Session session = resourceResolver.adaptTo(Session.class);  
             ValueFactory valueFactory = session.getValueFactory();  
             User subscriberUser = userManager.createUser(email_id, RandomStringUtils.randomAlphanumeric(20).toUpperCase());  
             subscriberUser.setProperty("cq:authorizableCategory", valueFactory.createValue("mcm"));  
             subscriberUser.setProperty("profile/email", valueFactory.createValue(email_id));  
             group.addMember(subscriberUser);  
             session.save();  
             if (subscriberUser != null) {  
               msgToPrint = "User Registered Successfully";  
             }  
           }  
         }  
       }  
       response.getWriter().write(msgToPrint);  
     } catch (Exception ex) {  
       logger.error(ex.getMessage());  
     }  
   }  
 }  

12) Now build the codebase, Drop the component "Subscribe Me" from Side-Kick and subscribe the user as below :


 You will notify that you are registered. In case existing subscribed user come to re-register again, it will prompt :
Email Validation not so far done in this code, So Enter the valid email address.
13) You can view the member of List/Group created in step 9 via Campaign console & then List (in servlet i have hard-coaded the group name, you can use the OSGi configuration for that also).


 14) Now final steps are to send the email, Open the newsletter created.
15) Click on the send button, list of users populate as per the group/list. Complete the wizard.


 16) You will have newsletter in your Inbox.


Thanks !!!

Monday 16 March 2015

Introduction to Scheduler in AEM6/CQ5


Scheduler in Apache Sling comes in a picture when there is need to run specific job after a regular internals. Intervals we can specify via cron expressions. Here Job we can refer a simple logic that need to perform. Below is the simple demonstration, how to create a scheduler in AEM6.0 that write the content over the node or on a file in crxde.

USE-CASE : Create a sitemap.xml file under /content folder.

Here cron expression  value = "0 * * * * ?" defines scheduler will execute after one minute.
XMLStreamWriter is use to write a xml stream that will get write over the node which wrap the ByteArrayOutputStream.  I did’t used the whole code to create a sitemap as you can get it via acs-commons project.

https://github.com/Adobe-Consulting-Services/acs-aem-commons/blob/master/bundle/src/main/java/com/adobe/acs/commons/wcm/impl/SiteMapServlet.java


=================================================================
Service
@Component(name = "Delta Job", label = "Training Schedular Delta", description = "Produce the file after hour", metatype = true, immediate = true)
@Properties({
        @Property(label = "Quartz Cron Expression", description = "Quartz Scheduler specific cron expression. Do not put unix cron expression", name = "scheduler.expression", value = "0 * * * * ?"),
        @Property(
                label = "Allow concurrent executions",
                description = "Allow concurrent executions of this Scheduled Service",
                name = "scheduler.concurrent",
                boolValue = false,
                propertyPrivate = true
        )
})
public class SimpleSchedular implements Runnable {
    private static Logger logger = LoggerFactory.getLogger(SimpleSchedular.class);
    private PageManager pageManager = null;
    private Page page = null;
    @Reference
    private ResourceResolverFactory resourceResolverFactory;
    private static final String NS = "http://www.sitemaps.org/schemas/sitemap/0.9";
    public void run() {
        try {
            ResourceResolver adminResourceResolver = null;
            adminResourceResolver = resourceResolverFactory.getAdministrativeResourceResolver(null);
            XMLOutputFactory outputFactory = XMLOutputFactory.newFactory();
            try {
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                Session session = adminResourceResolver.adaptTo(Session.class);
                XMLStreamWriter stream = outputFactory.createXMLStreamWriter(bos);
                stream.writeStartDocument("1.0");
                stream.writeStartElement("", "urlset", NS);
                stream.writeNamespace("", NS);
                // Here you can write a logic for nested xml elements.
                stream.writeEndElement();
                stream.writeEndDocument();
                ValueFactory vf = session.getValueFactory();
                Binary binary = vf.createBinary(new ByteArrayInputStream(bos.toByteArray()));
                Node rootNode = session.getRootNode();
                Node contentNode = rootNode.getNode("content");
                            if(!contentNode.hasNode("sitemap.xml")) {
                    Node sitemapNode = contentNode.addNode("sitemap.xml", "nt:file");
                    Node resNode = sitemapNode.addNode("jcr:content", "nt:resource");
                    resNode.setProperty("jcr:data", binary);
                } else {
                    Node resNode = contentNode.getNode("sitemap.xml").getNode("jcr:content");
                    resNode.setProperty("jcr:data", binary);
                }
                logger.info("End Document");
                session.save();
            } catch (XMLStreamException e) {
                logger.error("Error is :: " + e.getMessage());
            } catch (Exception ioex) {
                logger.error("Error is ioe :: " + ioex.getMessage());
            }
        } catch (Exception ex) {
            logger.error("Error is general" + ex.getMessage());
        }
    }

}


Refernces :

http://sling.apache.org/documentation/bundles/scheduler-service-commons-scheduler.html
http://adobe-consulting-services.github.io/acs-aem-commons/features/simple-sitemap.html