Category Archives: Adobe CQ

Developer Opinion: A few basic problems in Sightly template

AEM Sightly has been a big buzz in AEM technology & there are thousands of articles written on it. Most interesting things to notice that most of the articles only talk about syntax & how to use sightly code. This is great for the Developers to just re-use code.

In this post, I would like to raise some important questions which I have not found in any articles or posts. If anyone finds answer & explanation, I would thank him if he/she let me know in comments. Will keep recording other Sightly problems but for now, Here are some of them:

Why else Condition not supported?

As far as I know, most of the languages support else condition whether language is scripting, template or OOPS except AEM Sightly template language. We don’t know what super logic & architecture drawbacks it has to support else condition. One argument is that Adobe does not want else condition in HTML tags. Fair enough but what problem you may have with ‘else’. I understand that Nobody wants business logic to be in HTML. But, That is the best practices Adobe can recommend.

Let’s understand a scenario for the clarity & why else is equally important. 

Let’s say, Based on some authoring checkbox, You would like to serve different HTML Content (i.e two types of DOM structure). If you don’t have else then developer has to put if with boolean condition & another if with the negative condition.

<sly data-sly-test=${IsFlagChecked}> // show checked content </sly>

<sly data-sly-test=${! IsFlagChecked}> // show non checked content </sly>

The workaround with NOT operator is fine & by this logic, someone might think that else statement isn’t required at all when there is a workaround. My argument would be workaround should not be objective when you design a new template language. There must be some solid use case not to support that.

Why no argument allowed in get method?

Let’s consider a scenario where you want to get output from Sling Model in different instances with the HTML but output varies based on parameters passed to the method. Can you pass the parameters to the GET method mentioned in USE class? The answer is NO. 

Passing parameters from Sightly is only allowed at the time of USE class initialization. It means you should know all types of inputs to different methods before calling the GET method. And, Sightly is forcing you to have global variables initialized when USE class gets initialized. In general, Not a good idea to keep every business logic with global variables. It seems to me that AEM works good because the objective is to generate HTML & cache it. Nothing else.

Why No method overloading not supported?

When you have a common abstract USE class with a bunch of common defined methods & you wish to overload that method with a different parameter, that is not the possible at all. You can’t call your overloaded method. Your USE class must have their own method & supporting GETTER method for the global variables. Basically, end up creating extra codes which may not be required at all.

Again, I would not deny that this can’t be solved with work around. However, There must be some explanation for not following concept which we have been practising & grow up learning many common design principles. 

Final Thoughts

I believe there may have some good reasons to do the way it is now. But, logically it doesn’t seem right to me. And, my intention is to get those answer through this article. Hoping that someone might know the answers.

Advertisement

AEM Solution: The easiest way to copy content from one AEM to another.

Moving Content in AEM is a big task regularly. In my personal opinion, it is big task for everybody. Let me try to explain in details. Let’s consider a scenario where you want to move content from one AEM environment to another. The easy thing is to do to use AEM Package manager. That is good. And just build a package from one AEM, download it & install somewhere else. Easy process? You may think it is but it is not. From the Business perspective, the Package Manager tool totally sucks & for the following reasons:

Lack of basic features in Package Manager:  There are many basic features missing. Some of them are:

  • No way you can schedule the content package as a whole. And, if 100 pages to be scheduled then Each individual pages must be scheduled to replicate them.
  • No way you could upload the individual pages content from one environment to another if individual pages are the parent pages in the content hierarchy. All the content has to be overridden.
  • Not easy to revert the certain content if installed by the package manager. either whole content or nothing can be reverted.

Not easy to use by the Non-Technical Person:  Authoring team must have a working knowledge of package manager tool. I know you might think working knowledge? My answer would be YES. Someone needs to know how to upload, build, install, download & uninstall etc. And needs access to the packages when someone can misuse it.

Time-consuming & does not work in most of the cases: Downloading from one environment & uploading in another is very old fashion & time-consuming. For heavy content like size GB’s, It does not even work. 

So, Here are the list of possible Solutions:

  • TWC Grabbit is one of them. It was developed by one of our team members however not sure if it is working in all the AEM versions. It has so many dependencies & Needs to install & managed in source & destination. But it was a quite good one.
  • AEM Package Manager Out of the box.
  • Copy whole source CRX-QUICKSTART folder & override the destination: Not a feasible option if the content has to be moved to production from stage or from stage to prod. Also not a solution if you want to move the only fewer pages or images. However, Not bad solution for Dev & QA but comes with lots of maintenance once the content is overridden.

The most easiest way move content regularly

All above solutions require some level of additional maintance however there is another the most easy solution. You need to have just two things: create a servlet in source code & Configure destination replication agent in source AEM Instance. Follow below steps to understand clearly.

Pros of this solution:

  • First, a good thing is that it is pretty easy & you can replication any JCR path. Include a content package, one page/child pages, one image/set of images. if you replicate a content package then no need to install in the destination environment. And, Helpful when you just need some pages in your QA or dev from the stage. Not whole content.
  • No dependency. No installation. Just one servlet, replication agent. And, using out of the box API. 
  • Pretty extensible. You can build fancy UI out of it & make it a tool out of it.
  • Cross-environment replication & replication only for content movement. Any environment can be a source or destination. Having a separate replication agent just for copying content does not cause any replication queue issue.
  • Cons is it is still using replication API & not any fancy third-party solution.

NOTE: I have build a tool which solves all the issue a content package has. But, not yet sure if I could simply provide source code here. However, let me know if you need some help or idea to understand the full solution.

Agent Configuration in AEM Source Instance: AEM content source is the AEM instance (author or publish) where you would be fetching content and destination where you want to upload content.

Replication authoring – Nothing different from other replication agent except Triggers configuration. Do the same as you see in the snapshot.

Hit this URL from a browser after your servlet & agent is done:  http://localhost:4502/bin/support/content/publisher?path=etc/packages/abc.zip&destEnvName=QA&publishChildNodes=true.&nbsp; publishChildNodes is required when you want to publish child nodes also.

Replication Request Handler

import com.day.cq.replication.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.sling.SlingServlet;
import org.apache.http.HttpStatus;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * Sample URL http://localhost:4502/bin/support/content/publisher?path=etc/packages/abc.zip&destEnvName=QA&publishChildNodes=true
 */
@SlingServlet (paths = "/bin/support/content/publisher",
 methods = "GET", metatype = true, label = "Content publisher to publish content across environments")
public class PackagePublisher extends SlingAllMethodsServlet {
    private static final Logger LOGGER = LoggerFactory.getLogger(PackagePublisher.class);

    @Reference
    private Replicator replicator;
    private List<String> activatedPathsList;
    @Override
    public final void doGet(final SlingHttpServletRequest request, final SlingHttpServletResponse response) throws IOException {
  String requestPath = request.getParameter("path");
  String publishChildNodes = request.getParameter("publishChildNodes");
  final String destEnvName = request.getParameter("destEnvName");
  if (StringUtils.isNotBlank(requestPath) && StringUtils.isNotBlank(destEnvName)) {
      activatedPathsList = new ArrayList<String>();
     Session userSession = request.getResourceResolver().adaptTo(Session.class);
  ReplicationOptions replicationOptions = new ReplicationOptions();
 AgentFilter agentFilter = new AgentFilter() {
    public boolean isIncluded(Agent agent) {
 if(agent.getId().toLowerCase().contains(destEnvName.toLowerCase())) {                   return true;
                    }
                    return false;
                }
            };
            replicationOptions.setFilter(agentFilter);
            LOGGER.info("replication starting ");
            try {
                replicator.replicate(userSession, ReplicationActionType.ACTIVATE, requestPath, replicationOptions);
                Resource childResource = request.getResourceResolver().getResource(requestPath);
                if ("true".equalsIgnoreCase(publishChildNodes)) {
                       publishChildPages(childResource, userSession, replicationOptions);
                }
                for (String path: activatedPathsList){
                    LOGGER.info("Activate paths" + path );
                }
                response.setStatus(HttpStatus.SC_OK);
                response.getWriter().print("given path is replicated to given environment. Check in destination env.");
            } catch (ReplicationException e) {
                response.setStatus(HttpStatus.SC_BAD_REQUEST);
                response.getWriter().print("Check Parameters. Also check author replication agents for " + destEnvName);
                e.printStackTrace();
            }catch (Exception ex){
                response.setStatus(HttpStatus.SC_BAD_REQUEST);
                response.getWriter().print("Something was wrong!!");
            }
        } else{
            response.setStatus(HttpStatus.SC_BAD_REQUEST);
            response.getWriter().print("Parameters are not passed.");
        }
    }

    private void publishChildPages(Resource childResource, Session userSession,
                                   ReplicationOptions replicationOptions) throws ReplicationException {
             if (childResource != null) {
                Iterator<Resource> itr = childResource.listChildren();
                while (itr.hasNext()) {
                    Resource temp = itr.next();
                    if (!temp.getPath().contains("rep:policy") && !temp.getPath().contains("jcr:content")) {
                        if (temp.hasChildren()) {
                            publishChildPages(temp, userSession, replicationOptions);
                        }
                        activatedPathsList.add(temp.getPath());
                        replicator.replicate(userSession, ReplicationActionType.ACTIVATE, temp.getPath(), replicationOptions);
                    }
                 }
            }
    }
}

Final Thought

I found it very easy in day to day work when you want to move content here & their. However, if there is any confusion & question. leave a comment. will respond asap. thanks.

You can further extend this utility and have automatic script to package and transport from source instance to destination.

AEM Solution: How to override third-party OSGI Config in AEM

Overview

An overriding concept is a pretty known subject in AEM. Components can be overridden in an application with sling resolution technique. Similarly, Apache sling has a resolution order to resolve OSGi configs as well. Basically, application specific config’s can override AEM Libs configs. For more details on OSGI config & their resolution order, Read my posts here.

Scenarios/Problems

Let’s consider a scenario of a project where you have to use another AEM Project or third-party source code like ACS Commons. And, There are some services which you want to use but with different configuration. Assume services do have configurations in place.

Now you might be thinking that what is the big deal in that. We can upload third-party code & change the configuration. Well, That is the whole point. The moment you change the configuration for your application, One more maintenance issue happens. Got it? No?

No worry. The issue is that every time you update/install third-party code for your application, You have to manually change the configuration. You might think it is okay. But it is not when you have to support the same in every environment after every deployment. Someone has to fix every time. And what if the configuration has to be updated in prod publish environment. It is tough to maintain. Let me explain with config files.

#Thirdparty config’s XML File match for ContentFeed service under config.publish folder & apps folder is /apps/abc/thirdparty/. ContentFeed Service is part of platform code base.

#Base config: com.abc.core.contentFeed.xml
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
    jcr:primaryType="sling:OsgiConfig"
    contentFeedPath="https://example.com/api/core/platform"/>

#Your application would like to override this config. And apps is /apps/myapp etc.

#Brand specific config: com.abc.core.contentFeed.xml

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
    jcr:primaryType="sling:OsgiConfig"
    contentFeedPath="https://example.com/api/feed<myapp-feeds>"/>

Now, ContentFeed service does not pick your application-specific config & even though you have the same config in your application. It always picks up the third party config. And it happens ContentFeed service is part of platform source code.  I hope the problem is clear to you now. let’s see a solution.


General Solution

A general approach is to keep modifying third-party configs in every environment after each deployment. And, When QA reports this or that not working, a new person struggle to find why it is not working. Most of us do not remember that service configs are overridden by the deployment. The second approach is to override the full code itself with different service name & configurations. Must easy to maintain but having duplicate code & same bugs as third-party has. And fixing bugs every time something fixed in the third-party source.

Alternative Solution

Solution to above problem is simple. Make sure your app config file name is the same as third-party config. Read about XML config & service naming convention. AEM OSGi Config Resolution Order

To make sure Service picks your configuration, You need to create the same XML config under the same run mode folder & with additional run mode info because that is how AEM put precedence over other config’s. Let me explain with source code.

#Third-Party config file name is com.abc.core.contentFeed.xml
folder structure is: /apps/third-party/config.publish, config.author etc.

#Your app config name must be com.abc.core.contentFeed.xml
your Folder structure for config must be: /apps/third-party/config.publish.qa, /apps/third-party/config.publish.stage etc.

I hope the solution is clear to you. Solution seems easy but it solves general problem. Leave a comment if you have any question or feedback. thanks in advance.

AEM Solution: How to create an AEM Custom global object?

Overview

As an AEM developer, you know that there are a set of global objects available in AEM at the view layer. Whether you can use JSP or New template framework (HTL). Most commons global objects are currentPage, current resource, current design object etc.

These set of objects are initialized by the AEM application by default & available to use them in every component.

Do you really need custom object?

With given architecture of AEM & Sling framework to resolve the resource, there is a situation when you need your own custom object to be available across the application components. Just like global objects.

Let’s consider a multi-brand site where you need some brand-specific information, Objects or JSON etc should be available at each brand application level. It means object should be available at every component.

General Solution

General practice is that have a common bean/Use class gets initialized at each component level. Performance wise, initializing a bean at every component level, isn’t that bad. Because most of the content gets cached. However, It is an issue when you have got that information/object through service. You can’t make a web service from every component. You could still do that if you are an architect & developer has an alternative solution. So let’s see an alternative solution.

Alternative Solution

An alternative solution is pretty simple. Just like any other AEM global objects, keep your custom object available at every component by default. No initialization of any objects. Make sense? let’s see the following steps to create global custom objects.

  • Create a new class with an appropriate name (hard to find the appropriate name but try it). 
  • Make sure new class is registered as Component in OSGI Console.
  • Sling Framework provides an interface BindingValuesProvider. Implement this interface. This interface provides addBindings(Bindings bindings) method.
  • Bindings parameter works like a map. You need to put key & value in it. For instance, bindings.put(“myObject”, “valueOfCustomObject”);
  • Now, “myObject” can be used as a global variable anywhere in the components.

Sample src Code

# CustomGlobalObject
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.scripting.api.BindingsValuesProvider;
import javax.script.Bindings;
@Component
@Service
public class CustomGlobalObject implements BindingsValuesProvider {
    @Reference
    IMyService iMyService;
    @Override
    public void addBindings(Bindings bindings) {
        bindings.put("mylist", iMyService.getGlobalObject());
    }
}
#Service Interface
import java.util.List;
public interface IMyService {
    List getGlobalObject();
}
#Service Interface implementation

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import java.util.ArrayList;
import java.util.List;
@Component
@Service
public class MyService implements IMyService{
    @Override
    public List<String> getGlobalObject() {
        List list = new ArrayList();
        list.add("a"); list.add("b"); list.add("c");
        return list;
    }
}
# Entry in Sightly HTML component
Custom object: ${mylist}
#Output
Custom object: a,b,c

Final Thought: 

There could be other good solutions to solve the problem which I described in this post. Would love to hear others ideas. Don’t hesitate to share your thoughts in the comment section. For any doubt or question, leave a comment. will do our best to answer your questions. Thanks in advance.

References

AEM Solution: How to get OSGI Service object in POJO?

Overview

In AEM, OSGI Container supports dependency injection which means one OSGi service can be injected into another service using @Reference annotation. Dependency injection design is a well-known design pattern.   In this post, would like to explain what are the ways to get a reference of OSGi Service? 

Problems/Scenarios

As you know, In some case you are not able to get the object using @Reference annotation. Basically, OSGI container does not allow you to inject NON-OSGI classes (POJO) into another class. 

This happens when you have a class which is not registered as OSGI Component & Service. In such cases, You are left with the following option. Get the service object through a parameter to our class or get the service object through Sling request object. In Sightly model, referencing of services are possible now.

Solutions

Here is the example how to get service object through Sling request object.

// Fetching service reference from request object.
public class Example{
 public ServiceObject YouServiceReference(SlingHttpRequest request){
      final SlingBindings bindings = (SlingBindings) request.getAttribute(SlingBindings.class.getName());
      SlingScriptHelper slingScriptHelper = bindings.getSling();
     YouServiceReference service = slingScriptHelper.getService(YouServiceReference.class);
        return service
   }
}
#Wiht Sightly POJO
public class Example extends WCMUsePojo{
 public ServiceObject YouServiceReference(SlingHttpRequest request){
      return getSlingScriptHelper.getService(YouServiceReference.class);
   }
}