Showing posts with label Servlet. Show all posts
Showing posts with label Servlet. Show all posts

Wednesday, 1 January 2020

A servlet to Query Pages containing particular components.

If you want to get your list of pages having particular components authored in it inside your project content folder in the form of json response, then here's a servlet for you.

The servlet is registered at path - /bin/generate/pagesreport to which you need to pass your page folder path (default is /content) and components path (separated by comma) as a query-param and it will output the list of pages (its title, path, template and component resourceType) as a json response.

What this servlet is doing is - it is forming a Predicate Map of Query having-
- path (content path from the input you provide)
- node type (nt:unstructured) 
- property (sling:resourceType)
- property value (list of components path from the input you provide)
and using it in QueryBuilder API and fetching the results in form of page node paths.

Further it is processing the hits (page node paths) from the search result to form the json response which you can consume by dong an ajax call.

This is the servlet for it-
package com.aem.community.wf.core.servlets;

import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.sling.SlingServlet;
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.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.commons.json.JSONArray;
import org.apache.sling.commons.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.day.cq.search.PredicateGroup;
import com.day.cq.search.Query;
import com.day.cq.search.QueryBuilder;
import com.day.cq.search.result.Hit;
import com.day.cq.search.result.SearchResult;

import javax.jcr.Session;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@SlingServlet(paths = "/bin/generate/pagesreport")
public class PageQueryServlet extends SlingSafeMethodsServlet {

 private static final long serialVersionUID = 1L;
 private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
 @Reference
 private QueryBuilder builder;
 
 @Reference
 private ResourceResolverFactory resolverFactory;
 
 @SuppressWarnings("deprecation")
 @Override
 protected void doGet(SlingHttpServletRequest request, 
                 SlingHttpServletResponse response) throws IOException {
  
     String pageRootPath = request.getParameter("pageroot");   
     if(pageRootPath == null){
      pageRootPath = "/content";
     }
     String compType = request.getParameter("comptype");   
     if(compType == null){
      compType = "weretail/components/content/productgrid";
     }else{
      compType = compType.replace("/apps/", "");
      logger.info("Logging comp type is: "+compType);
     }
     long totalMatches = 0;
     
     response.setCharacterEncoding("UTF-8");
     response.setContentType("application/json");
     
     JSONObject json = new JSONObject();
     JSONArray jsonarray = new JSONArray();
     JSONObject tempJson;
     
     try { 
         ResourceResolver resolver = request.getResourceResolver();
         Session session = resolver.adaptTo(Session.class);
      
         Map map = new HashMap();             
         map.put("path", pageRootPath);
         map.put("type", "nt:unstructured");
         map.put("property", "sling:resourceType");
         
         String[] patharr = compType.split(",");
         for (int i=0; i<patharr.length; i++){
             map.put("property."+i+"_value", patharr[i]);
         }
         map.put("p.offset", "0");
         map.put("p.limit", "-1");
                           
         Query query = builder.createQuery(PredicateGroup.create(map), session);                    
                     
         SearchResult result = query.getResult();
         totalMatches = result.getTotalMatches();                             
         String hitpath;
         Resource pageResource;
         String pagepath;
         String jcrpagepath;
         List<String> pagesList = new ArrayList<String>();
         int pagecounter = 0;
         int compcounter = 1;
         
         for (Hit hit : result.getHits()) {
            hitpath = hit.getPath();   
            tempJson = new JSONObject();
            pagepath = hitpath.split("/jcr:content")[0];
            jcrpagepath = pagepath +"/jcr:content";
            pageResource = resolver.getResource(jcrpagepath); 
                
            if (pageResource != null && !pagesList.contains(pagepath)) { 
              pagecounter++;
              ValueMap valueMap = pageResource.getValueMap(); 
              tempJson.put("pagename", 
                            valueMap.get("jcr:title", String.class));
              tempJson.put("pagerestype", 
                            valueMap.get("sling:resourceType", String.class));
              tempJson.put("pagetemplate", 
                            valueMap.get("cq:template", String.class));
              tempJson.put("pagepath", pagepath);
              jsonarray.put(tempJson);
              pagesList.add(pagepath);
            }
         }
         json.put("totalcomponents", totalMatches);
         json.put("totalpages", pagecounter);
         json.put("pages", jsonarray);
         logger.info("Total" + totalMatches + "paths were found: \n");
         
     } catch (Exception e) {
      logger.error("Exception caught: "+e);
 }
     response.getWriter().print(json.toString());
    
    }
}

Below is the sample url to call this servlet by passing apps folder path-
http://localhost:4502/bin/generate/pagesreport?pageroot=/content&comptype=weretail/components/content/productgrid,weretail/components/content/text

And this is the screenshot of the response by hitting above url-

A servlet to Query Components inside your apps folder.

If you want to get your list of components inside your project apps folder in the form of json response, then here's a servlet for you.

The servlet is registered at path - /bin/generate/componentsreport  to which you need to pass your project folder path (default is /apps) as a query-param and it will output the list of components (their name and path) as a json response.

What this servlet is doing is - it is forming a Predicate Map of Query having the path (apps path from the input you provide) and node type (cq:Component) and using it in QueryBuilder API and fetching the results in form of component paths.
Further it is processing the hits (components path) from the search result to form the json response which you can consume by dong an ajax call.

This is the servlet for it-
package com.aem.community.wf.core.servlets;

import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.sling.SlingServlet;
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.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.commons.json.JSONArray;
import org.apache.sling.commons.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.day.cq.search.PredicateGroup;
import com.day.cq.search.Query;
import com.day.cq.search.QueryBuilder;
import com.day.cq.search.result.Hit;
import com.day.cq.search.result.SearchResult;

import javax.jcr.Session;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@SlingServlet(paths = "/bin/generate/componentsreport")
public class ComponentQueryServlet extends SlingSafeMethodsServlet {

 private static final long serialVersionUID = 1L;
 private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
 @Reference
        private QueryBuilder builder;
 
 @Reference
        private ResourceResolverFactory resolverFactory;
 
 @SuppressWarnings("deprecation")
 @Override
        protected void doGet(SlingHttpServletRequest request, 
                SlingHttpServletResponse response) throws IOException {
        
  
     String compPath = request.getParameter("comppath");   
     if(compPath == null){
      compPath = "/apps";
     }
     long totalMatches = 0;
     
     response.setCharacterEncoding("UTF-8");
 response.setContentType("application/json");
     
     JSONObject json = new JSONObject();
     JSONArray jsonarray = new JSONArray();
     JSONObject tempJson;
     
     try { 
      ResourceResolver resolver = request.getResourceResolver();
      Session session = resolver.adaptTo(Session.class);
      
      Map map = new HashMap();             
         map.put("path", compPath);
         map.put("type", "cq:Component");
         map.put("p.offset", "0");
         map.put("p.limit", "-1");
                           
         Query query = builder.createQuery(PredicateGroup.create(map),
                                                                     session);                    
                     
         SearchResult result = query.getResult();
         totalMatches = result.getTotalMatches();                             
         String hitpath;
         Resource compResource;
         String compTitle;
         
         for (Hit hit : result.getHits()) {
                hitpath = hit.getPath();   
                tempJson = new JSONObject();
                
                compResource = resolver.getResource(hitpath); 
         if (compResource != null) { 
                    ValueMap valueMap = compResource.getValueMap(); 
                    compTitle = valueMap.get("jcr:title", String.class);
                    tempJson.put("componentname", compTitle);
                }
         tempJson.put("componentpath", hitpath);
         jsonarray.put(tempJson);
         }
         json.put("totalcomponents", totalMatches);
         json.put("components", jsonarray);
         logger.info("Total" + totalMatches + "paths were found: \n");
         
     } catch (Exception e) {
      logger.error("Exception caught: "+e);
 }
     response.getWriter().print(json.toString());
    
    }
}

Below is the sample url to call this servlet by passing apps folder path-
http://localhost:4502/bin/generate/componentsreport?comppath=/apps/weretail

And this is the screenshot of the response by hitting above url-



  

A servlet to output html markup template of your AEM page.

If you want to get the html markup template of your AEM page which doesn't have css or js and pure html with white-spaces stripped off, here's a servlet for you.

You can invoke this servlet in any of your aem page by using the page path with 'template' selector and it will output the plain html for you.
Ex: http://localhost:4502/content/we-train/en.template.html

In few of the projects, when aem is integrated with SPA framework, where the role of aem is just hosting pages with structure and content and the client-side rendering is done through external SPA framework, it is often required to deliver the html from aem to front-end SPA team. In those cases, this servlet can be utilized.

So below is the AEM servlet registered with default resourceType - sling/servlet/default, and selector as template, so it can be invoked on any page.
I have written the comments as well in the code for understanding what each piece is doing. 

package com.foo.community.core.servlets;

import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.sling.SlingServlet;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.engine.SlingRequestProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.day.cq.contentsync.handler.util.RequestResponseFactory;
import com.day.cq.wcm.api.WCMMode;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * A default resourceType servlet called with 'template' selector to output
 * html template without css and js
 */
@SlingServlet(methods = {"GET"},
resourceTypes = {"sling/servlet/default"},
selectors = {"template"})
public class TemplateServlet extends SlingSafeMethodsServlet {

    private static final long serialVersionUID = 1L;
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
    /** Service to create HTTP Servlet requests and responses */
    @Reference
    private RequestResponseFactory requestResponseFactory;
    
    /** Service to process requests through Sling */
    @Reference
    private SlingRequestProcessor requestProcessor;

    @Override
    protected void doGet(final SlingHttpServletRequest request,
            final SlingHttpServletResponse response) throws ServletException,
              IOException {
        
        String requestPath = request.getRequestPathInfo().getResourcePath() +
             "." + request.getRequestPathInfo().getExtension();
        logger.info("requestPath is : "+requestPath);
  
        /** Setup request */
        HttpServletRequest req = 
                        requestResponseFactory.createRequest("GET", requestPath);
        WCMMode.DISABLED.toRequest(req);

        /** Setup response */
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        HttpServletResponse resp = requestResponseFactory.createResponse(out);

        /** Process request through Sling */
        requestProcessor.processRequest(req, resp, request.getResourceResolver());
        String htmlcontent = out.toString();
     
        /** Replacing js and css files extension in html */
        String content = htmlcontent.replace(".js", ".nojs");
        content = content.replace(".css", ".nocss");
  
        /** Setting up writer to write html template */
         PrintWriter writer = null;
        if (StringUtils.isNotBlank(content)) {
            content = content.replaceAll("(\r?\n+\t?\\s*)", "\n");
            content = content.replaceAll("\n", ""); 
            logger.info("content written is :\n"+content);
            response.setCharacterEncoding("UTF-8");  
            response.setContentType("text/html");  
            writer = response.getWriter();
            writer.write(content);
            writer.close();
        }
    }
}

Below is the screenshot of how any page looks like in aem author-



and by using template selector on the above page, you will get it's html output-

Sunday, 17 June 2018

Content Copier Servlet to perform JCR copy operation

This post is a part-2 of this post. In my previous post i have created a servlet invoker utility component and I will use that tool to invoke the Content copier servlet which is demonstrated below.

This servlet performs the JCR workspace copy operation. To demonstrate a particular use case i have worked on, there was a bunch of pages in a folder of type (sling:OrderedFolder) in CQ5 inside /content and more sub-pages inside the sub folder of type (sling:OrderedFolder) in it, and the ask was to migrate the pages from folder structure (nested) in CQ5 to AEM page structure.

To achieve the same I have written a servlet which accepts the source(path of the parent source folder) and destination (path of the parent destination page) paths as arguments and perform the workspace copy operation.

The primary method used to perform the operation is
Workspace.copy(String srcAbsPath, String destAbsPath)
This method copies the subgraph rooted at, and including, the node at srcAbsPath to the new location at destAbsPath.

Servlet to achieve the functionality-
package com.foo.community.core.servlets;

import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.sling.SlingServlet;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;

import javax.jcr.Repository;
import javax.servlet.ServletException;
import java.io.IOException;
import javax.jcr.NodeIterator;

import javax.jcr.Session; 
import javax.jcr.Node; 
import javax.jcr.Workspace;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 


@SuppressWarnings("serial")
@SlingServlet(paths = "/system/utility/servletinvoker")
public class ContentCopierServlet extends SlingSafeMethodsServlet {

  private static final Logger log = LoggerFactory.getLogger(this.getClass());

  @Reference
  private Repository repository;
  @Override
  protected void doGet(final SlingHttpServletRequest req,
   final SlingHttpServletResponse resp) throws ServletException, IOException {

     String message="Initial servlet message";
     String requestPath1 = req.getParameter("path1");
     String requestPath2 = req.getParameter("path2");
     try {
 Session session= req.getResourceResolver().adaptTo(Session.class);
 copyNodes(requestPath1,requestPath2,session);
 message= "Content copied from: "+ requestPath1+ " to: "+ requestPath2; 
     } 
     catch (Exception e) {
 message="Error performing operation: " + e.getMessage();
 log.error("copyServlet main error", e);
 e.printStackTrace();
     }
     resp.getWriter().println(message);
    }
 
   private void copyNodes(String requestPath1, String requestPath2, Session session)
                                                              throws Exception{
 
    Workspace workspace = session.getWorkspace();
    String resrc = requestPath1.substring(1,requestPath1.length()); 
    String redes = requestPath2.substring(1,requestPath2.length()); 
  
    Node rootnode = session.getRootNode();
    Node srcnodepath = rootnode.getNode(resrc);
    Node destnode = rootnode.getNode(redes);
  
    if (srcnodepath.hasNodes()) {
 NodeIterator worknodes = srcnodepath.getNodes();
 while (worknodes.hasNext()) {
    Node childnde = worknodes.nextNode();
    
    // formation of destination path and its relative path variables
    String[] parts = childnde.getPath().split("/");
    String destrelativepath = parts[parts.length-1];
    String destpath = destnode.getPath() + "/" + destrelativepath;
    
    if (destnode.hasNode(destrelativepath)) {
    
  String prtype= childnde.getProperty("jcr:primaryType").getString();
  log.info("primary type is: "+prtype);
  int flag=0;
  if(prtype.equals("sling:OrderedFolder")){
   copyNodes(childnde.getPath(),destpath,session);
   flag=1;
  }     
     
  if(!destpath.contains("jcr:content") && flag==0){
   destnode.getNode(destrelativepath).remove();
   session.save();
   log.info("src path is: "+childnde.getPath());
   log.info("dest path is: "+destpath);
   workspace.copy(childnde.getPath(), destpath);
  }
    }else{
  String srpath= childnde.getPath();
  workspace.copy(srpath, destpath);  
           }
 }
       }
     } 
}


Before beginning the operation, make sure to have the destination page structure(skeleton) ready for this to work.
All you have to do is to create the destination pages corresponding to folders of your source.
If your source contains the sub-folders, you have to create a nested page structure in your destination also. 
The screenshot of CRX before and after the operation is shown below:


The Servlet is registered as path,  so you can make an ajax call from your utility component and pass the source and destination paths as an argument.
Also, mostly the utilities like these are performed on author instance so you can find the proper path for the servlet to be registered at.

So, this was my use case, but if you have similar such requirement, you know how to do JCR workspace copy operation.

Wednesday, 13 June 2018

Servlet Invoker Utility Component

This post will be a beginning of various posts in which I will create a Servlet Invoker utility component and through this component invoke the servlet for various operations in AEM by passing parameters from our component as an argument to the servlet.

Often times, we create a utility in AEM in form of Services, Servlets etc. and call them by various means using Sling Models, WCMUSEPojo etc. which is not consistent though it achieves your functionality, but here through a common component we can invoke our Servlets and pass parameters to it and achieve our functionality.

To begin with, create a component in AEM and it's dialog. The dialog can contains the field for which servlet to invoke and what parameters to pass to the Servlet .
It's html contains a simple Button on press of which our servlet will invoke.

For ex:
<sly data-sly-use.clientlib="/libs/granite/sightly/templates/clientlib.html">

   <h2>Welcome to Servlet Invoker Utility Component</h2>

   <strong>Source path:</strong> <span id="path1">${properties.path1}</span> <br>
   <strong>Destination path:</strong> <span id="path2">${properties.path2}</span>
   <br><br>

   <input id="submitButton" type="submit" title="Submit" value="Begin Operation">

   <div id="result">
       <h3 data-sly-test="${properties.path1 && properties.path1}">
                        Press Begin Operation button to invoke servlet</h3>
       <h3 data-sly-test="${!(properties.path1 && properties.path1)}">
                        Please author source and destination path</h3>
   </div>

<sly data-sly-call="${clientlib.js @ categories='servlet.invoke'}" />
It looks something like this:















Along with the html file of the component, create a clientlibs for the component with your categories say servlet.invoke and write a JS code which contains a logic to do an ajax call to your servlet on press of the Button. Also you can pass the parameters to your servlets through the same ajax call which you can get from your html.
For ex.
(function ($, document) {
    'use strict';
    $("#submitButton").click(function(){
        var r = confirm("This will invoke the servlet and begin operation.
                         Do you want to proceed? ");
        if (r == true) {
            $("#result").html('<h3>
                          Operation in progress...Please wait</h3>');
            $.ajax({
                    type : "GET",
                    url : '/system/utility/servletinvoker',

                    data : {
                        path1: $("#path1").html(), path2:$("#path2").html()
                    },
                    success : function(data) {

                        $("#result").html('<h3>
                                          '+data+'</h3>');
                    },
                    error : function(XMLHttpRequest, textStatus, errorThrown) {
                        $("#result").html('<h3>
                    Error calling utility servlet: '+errorThrown+'</h3>');
                    }
            });}});

})(Granite.$, document);
Notice the url of the servlet for ajax call, by this you can understand that the servlet is registered via path, Since most probably, you will be using this utility component in author instance, you can find out a path for the servlet to be registered in AEM.

Now, you are good to go with your simple component. In my next post, I'll create a servlet and show you how to invoke it through this component.