Multifields in Touch UI AEM 6.

I have been using touch ui entirely across all the client from last year or so. I have come across several articles online working with Touch UI and multifields. Multifields always being inconsistent, i always had to devise new strategies to cater to ever changing multifield requirements of clients.

I came across ACS Touch UI multifield and that happen to be very consistent and have started using it across all the clients. ACS uses definition list component where they have shown sample of how they use the multifield. The location of the component is \apps\acs-commons\components\content\definition-list. The component stores the data in the jcr as json. If you can write up a generic iterator which i have you can use this multifield across any component. Happy Component development.

Global AEM spell checker

I have recently started working a AEM library of common features. We have started adding a lot of stuff within that library. A recent addition to the library is AEM/CQ5 spell checker.

AEM CQ Spell Checker Utility
The project aem spell checker has been created using maven archetype 7 project.

Modules

Spell checker utility consists of two modules. Spell checking in both the modules in done using Jazzy spell checker utility. It is a very common spell checking utility used in J2ee projects. It is easy to integrate it with existing projects as well. All the code is servlet based which are registered under /bin/aemfeatures/* so this path should be enabled in case you want to run this code via dispatcher. The servlets have properties to provided phonet and dictionary files. Currently, both of these files have to live under crx-quickstart/conf directory. Samples files have been checked into the core/resource directory. Adding a word is a simple as adding in the en.0 file. Below should be modified in SpellCheckerServlet if you want to put directory in a custom field.

@SlingServlet(paths=”/bin/aemfeatures/spellChecker”, metatype=true) @Properties(value = { @Property(label=”dictionary.location”,name = “dictionary.location”, value = “crx-quickstart/conf/en.0″), @Property(label=”phonet.location”,name = “phonet.location”, value = “crx-quickstart/conf/phonet.en”)})

1) Global Spell Checker -: The global spell checker allows you to spell check the whole website in one go. You should have access to latest content package for running this module. The project only works on the publisher instance as for each page a server request is made and dom in parsed and misspelled words are found. The logic to do all the iteration is quite fast. Whole Geometrixx was parsed and spell checked in 20-30 seconds. Once the results are returned all the words with their pages references are downloaded to users computer automatically as csv.

The spell checker was also extended to provide a raw .txt file with comma separated words which will not be returned in the csv list.

2) Page Spell Checker -: It is a single page spell checking mechanism. Whatever misspelled words are found on the page they get highlighted in yellow much like what happens with RTE spell checking utility. All the misspelled words are found first and then Jquery highlighter plugin highlights them in yellow colour on the page.

The code can be downloaded from GITHUB https://github.com/sahilthadhani/cq-aem-spellchecker

The page spell checker works by replacing the buttons or menu in the sidekick/coral UI. Basically, it makes an ajax call to the server to find out the misspelled words and with the help of jquery highlighter plugin highlights them. So here is the code for adding a new button in Sidekick.js file and you just add this button to the list of buttons in sidekick

//Button definition
this.spellCheckButton = new CQ.Ext.Button({
“iconCls”: “cq-sidekick-reload”,
“tooltip”: {
“title”: “spell Check”,
“text”: CQ.I18n.getMessage(“Spell check the page”),
“autoHide”: true
},
“handler”: function() {
var arr = {};
var bodyText = $(‘body’).html();
var bodyFrameText = $(‘iframe’).contents().find(‘body’).html();
if(bodyFrameText.length>bodyText.length){
bodyText=bodyFrameText;
}
arr [‘bodyText’] = bodyText;
$.ajax({
url: “/bin/aemfeatures/spellChecker”,
type: ‘POST’,
data: arr,
async: false,
success:
function(data, textStatus, xhr) {
$(“body”).highlight($.unique(xhr.responseJSON));
$(‘iframe’).contents().find(‘body’).highlight($.unique(xhr.responseJSON), {wordsOnly: true});
},
error:
function(xhr, textStatus, errorThrown) {
$(“#result”).html(“textStatus : ” + textStatus);

}
});
}
});

// Adding the button to the list.

addButtons: function() {
var bbar = this.getBottomToolbar();
bbar.removeAll(true);
bbar.add([
this.editButton,
this.previewButton,
this.spellCheckButton
]);

2) For doing a global spell check a page already exists within the code base /content/aemfeatures/spell-checker-demo.html. Make sure that you run the code on the publisher.

How to check if the page is published? CQ5/AEM

If you want to know if the page is published or not you can use the below utility method to know if the page is published or not

public static Boolean isPublished(Resource resource) {
Boolean activated;
ReplicationStatus status = null;
activated = false;
if (resource != null) {
try {
Page page = resource.adaptTo(Page.class);
status = page.adaptTo(ReplicationStatus.class);
} catch (Exception e) {
LOG.debug(e.getMessage(), e);
}
if (status != null) {
activated = status.isActivated();
}
}
return activated;
}

Configure SSL on Windows/Linux environment CQ5/AEM

I was recently asked to work on https on my local environment of AEM. Since i am using windows, i had a little bit of trouble configuring SSL.

I was following this article http://docs.adobe.com/docs/en/cq/5-6-1/deploying/config-ssl.html to configure SSL over the developer environment. Please see that since it is just a dev environment you don’t need a trust store hence only follow the steps for developer environment.

The only problem was the parameter below

org.apache.felix.https.keystore String [quickstart_dir]/ssl/cqkeystore.keystore

Now, this parameter uses absolute path and not relative. So my windows settings were akin to below. The ssl directory was placed alongside the crx-quickstart and the jar file.  It is very easy to configure SSL. For linux the path can changes to /opt/abc/ssl/cqkeystore.keystore

org.apache.felix.https.keystore String C:/cq/ssl/cqkeystore.keystore

Automate the creation of component and clientlibs in AEM/CQ5

While working for a client, i got a requirement. The requirement was simple they had front end team which was producing angular apps and i had to use the developed app and create an AEM component with clientlibs automatically with all the editing capabilities liked dialog etc. For anyone who isn’t aware about angular app, angular apps are one page apps and they use Angular JS. So for most part except header and footer nothing is going to be shared among all the apps that the front end devs were producing.

The solution that i devised was to use grunt. Grunt has several apis for file manipulation, json iteration etc. While creating the dialogs, i asked the front end devs to provide me a json which i then iterated and successfully produced the dialog.xml.

I also used the js and css that front end produced to produce the clientlib and js.tx and css.txt file. This was all done using grunt. Finally, they also produced html which i had to then parse and convert into sightly compatible html file.

The process took a while to deliver but now there is no upfront cost to the client in terms of having a AEM developer. The front guys can get the component produced by executing just a command.

The implementation was client specific but can help someone in developing something along the lines if there is any such need.

Create Maven Project for CQ5/AEM using archetype 7,8

Before, i started working with AEM 6, i was building CQ5 maven project with archetype version ~1.

Recently, i had to setup another project and i did the below steps to produce a maven project. Please note that archetype 7&8 are compatible with AEM 6.

How to Archetype 8 with MAVEN and CQ5

1) Install Maven and configure it with the environment variables etc.

2) Use the latest archetype available at

https://github.com/Adobe-Marketing-Cloud/aem-project-archetype

3) Currently, the latest archetype version available is 8.

4) Before you create an empty project you have to access if you need additional sub modules inside the project. It is always good to create a parent project and add the modules inside them, even if you don’t need them right away. This enables you to add sub modules inside if needed later on.

5) If you want to create a parent module use the command below.

mvn archetype:generate -DarchetypeGroupId=org.codehaus.mojo.archetypes -DarchetypeArtifactId=pom-root -DarchetypeVersion=RELEASE

This will generate a folder with a pom file where the packaging type will be pom.

6) Now, go into the directory that you created and run the command below to generate maven project for CQ, you can also generate a project using the command below if needed for dev purposes.

mvn -X archetype:generate -DarchetypeGroupId=com.adobe.granite.archetypes -DarchetypeArtifactId=aem-project-archetype -DarchetypeVersion=8 -DarchetypeRepository=adobe-public-releases

Once the project generates, you will see that it gets automatically added to the parent project. Inspect the pom file for changes.

Errors that you might encounter when you create this project

1) content-package-plugin cannot be resolved. I removed the version number and it was downloaded fine from the adobe remote repository.

2) The same can happen for enforcer plugin. Since you can’t browse the maven repository of Adobe on your browser as it is just maven compatible , you will end up experimenting with few versions and plugins.

3) After the project generates, you also need to run mvn clean install and the profile autoInstallPackage. This might generate additional errors. The common one being that it-launcher project will fail every time.  I couldn’t asses if I really needed this project and hence removed it from the pom.xml file module section and removed the directory as well.

Archetype 7 works well without issues and hence is recommended to use. 

Below command will help you generate such a project with AEM and MAVEN and Archetype version 7.

mvn archetype:generate -DgroupId=de.test -DartifactId=testAEM -DarchetypeArtifactId=sample-project-archetype -DarchetypeGroupId=com.adobe.granite.archetypes -DarchetypeVersion=7 -DarchetypeCatalog=https://repo.adobe.com/nexus/content/groups/public/archetype-catalog.xml -DinteractiveMode=true

Prerequisites for maven to work.

You need to have adobe repository reference in your maven settings.xml file.

<profiles>

<profile>

<id>adobe-public</id>

 

<activation>

<activeByDefault>true</activeByDefault>

</activation>

 

<properties>

<releaseRepository-Id>adobe-public-releases</releaseRepository-Id>

<releaseRepository-Name>Adobe Public Releases</releaseRepository-Name>

<releaseRepository-URL>http://repo.adobe.com/nexus/content/groups/public</releaseRepository-URL&gt;

</properties>

<repositories>

<repository>

<id>adobe-public-releases</id>

<name>Adobe Public Repository</name>

<url>http://repo.adobe.com/nexus/content/groups/public</url&gt;

<releases>

<enabled>true</enabled>

<updatePolicy>never</updatePolicy>

</releases>

<snapshots>

<enabled>false</enabled>

</snapshots>

</repository>

</repositories>

 

<pluginRepositories>

<pluginRepository>

<id>adobe-public-releases</id>

<name>Adobe Public Repository</name>

<url>http://repo.adobe.com/nexus/content/groups/public</url&gt;

<releases>

<enabled>true</enabled>

<updatePolicy>never</updatePolicy>

</releases>

<snapshots>

<enabled>false</enabled>

</snapshots>

</pluginRepository>

</pluginRepositories>

</profile>

</profiles>

Jcr Sql 2 vs Xpath queries comparison and point of view CQ5/AEM

I was quite fascinated by the world of XPath and the way i could configure a quick map and pass it to the queryBuilder interface to run a simple query and parse results. All my dreams shattered when i was told Adobe has deprecated the support of XPath queries and you are now required to use the JCR – SQL2 queries. The next search class that i ended up writing was for JCR and all was good until we really started pushing the content. The response time of the queries climbed up rapidly and we started feeling the pain of working with JCR queries.

JCR queries shouldn’t be used at all for multiple paths, by this i mean if you want to search under two different paths with one query you will end up with a very high response times. XPath queries or Query Builder seems to do that fine. For simple queries, you won’t find much difference between the response times of JcrSql queries and Xpath queries but as you build complex queries and adding multiple predicate you will soon start to find yourself in deeper and deeper holes.

For this very reason, i believe Adobe has not been able to deprecate Xpath or Query builder as everything is backwards compatible.

The performance of XPath isn’t very good, if you put it under heavy load but it has to be heavy load more than 500 concurrent search requests or something. The average response times for us for not so complex queries under load were approx 5 seconds under very heavy load for us. We then decided to use listChildren method of Resource interface as this is quite fast. 100x faster and it is not an exaggeration. If you are building such a system, i hope you will get your caching strategies right. Try to save your query results for easier retrieval afterwards and make less number of queries as you can offload a lot of that to the caching server.

So, build your systems with some of advice mentioned above and you should be ok.

 

Workflows gone into loop and occupied all the disk space AEM/CQ5

Some times invariably the workflows go into loop due to mis-configuration by developers in these cases you can check the status of the workflows using

http://xxx:4502/etc/reports/workflowreport.html // This will report a very high number in the case above is true.

What can you to fix up the disk space in CQ in case above

Suggest you try using http://xxx:4502/system/console/jmx/com.adobe.granite.workflow%3Atype%3DMaintenance to purge completed worklows.

After that you might still need to run the repository garbage collection to free up the space (http://xxx:4502/system/console/jmx/com.adobe.granite%3Atype%3DRepository).

This is just a step to free up some space temporarily but ideally the configuration steps should be looked at again and fixed.

How to send attachments in email CQ5/AEM

There are often requirements for sending attachments in emails. I was using upload OOTB form component and i had slightly customized the OOTB form component but whenever i was sending email on my local dev machine i was not receiving the attachments in email. 

Basically, you need to have instance of multipart email (apache commons library) and once you have that, it is easy enough to send that email .

The below code sample is what i used to send the attachment in the email.

Please see that this is not production ready and not complete code. You may have to place some pieces here and there.

 try {  
                List<String> contentNamesList = new ArrayList<String>();  
                Iterator<String> names = FormsHelper.getContentRequestParameterNames(request);  
                while (names.hasNext()) {  
                     String name = (String)names.next();  
                     contentNamesList.add(name);  
                }  
                Collections.sort(contentNamesList);  
                List<String> namesList = new ArrayList<String>();  
                Iterator<Resource> fields = FormsHelper.getFormElements(request.getResource());  
                while (fields.hasNext()) {  
                     Resource field = (Resource)fields.next();  
                     FieldDescription[] descs = FieldHelper.getFieldDescriptions(request, field);  
                     for (FieldDescription desc : descs)  
                     {  
                          contentNamesList.remove(desc.getName());  
                          if (!desc.isPrivate()) {  
                               namesList.add(desc.getName());  
                          }  
                     }  
                }  
                namesList.addAll(contentNamesList);  
                List<RequestParameter> attachments = new ArrayList<RequestParameter>();  
                for (String name : namesList) {  
                     RequestParameter rp = request.getRequestParameter(name);  
                     if (null!=rp && !rp.isFormField() &&rp.getSize() > 0L) {  
                          attachments.add(rp);  
                     }  
                }  
                MultiPartEmail mpEmail;  
                Email email;  
                if (attachments.size() > 0) {  
                     mpEmail = new HtmlEmail();  
                     email = mpEmail;  
                     for (RequestParameter rp : attachments) {  
                          ByteArrayDataSource ea = new ByteArrayDataSource(rp.getInputStream(), rp.getContentType());  
                          mpEmail.attach(ea, rp.getFileName(), rp.getFileName());  
                     }  
                } else {  
                     email = new HtmlEmail();  
                }  
                email.setCharset("utf-8");  
                email.setMsg(getEmailBody(//get body);  
                for (String rec : mailTo) {  
                     email.addTo(rec);  
                }  
                email.setSubject(//set subject);  
                email.setFrom(//set from);  
                getMessageGatewayService().getGateway(Email.class).send(email);  
           }  
           catch (EmailException e) {  
                throw new ServletException("Form can't be posted",e);  
           }  

javax.jcr.query.InvalidQueryException: Encountered “{“

I was querying the jcr using query builder interface and by mistake supplied the type as NodeType.NT_UNSTRUCTURED in the query builder map. But when i supplied the constant String to QueryBuilder, it fixed the problem.

com.day.cq.search.impl.builder.QueryImpl Could not run xpath query javax.jcr.query.InvalidQueryException: Encountered “{” at line 1, column 54.
Was expecting:
<QNameForItemType> …
for statement: for $v in /jcr:root/home/users/consumers//element(*, {http://www.jcp.org/jcr/nt/1.0}unstructured)[(@userGuid = ‘s3r5E8OLEQ3cMuVqNX’)] return $v
at org.apache.jackrabbit.spi.commons.query.xpath.XPathQueryBuilder.<init>(XPathQueryBuilder.java:304)
at org.apache.jackrabbit.spi.commons.query.xpath.XPathQueryBuilder.createQuery(XPathQueryBuilder.java:336)
at org.apache.jackrabbit.spi.commons.query.xpath.QueryBuilder.createQueryTree(QueryBuilder.java:39)
at org.apache.jackrabbit.spi.commons.query.QueryParser.parse(QueryParser.java:57)
at org.apache.jackrabbit.core.query.lucene.QueryImpl.<init>(QueryImpl.java:91)
at org.apache.jackrabbit.core.query.lucene.SearchIndex.createExecutableQuery(SearchIndex.java:722)
at org.apache.jackrabbit.core.query.QueryImpl.init(QueryImpl.java:115)
at org.apache.jackrabbit.core.SearchManager.createQuery(SearchManager.java:243)
at org.apache.jackrabbit.core.query.QueryManagerImpl$QueryFactoryImpl$2.createQuery(QueryManagerImpl.java:222)
at org.apache.jackrabbit.core.query.CompoundQueryFactory.createQuery(CompoundQueryFactory.java:67)
at org.apache.jackrabbit.core.query.QueryManagerImpl$2.perform(QueryManagerImpl.java:95)