How to create without template

If you want to create a page without template you can do that but it’s not possible using siteadmin console as it’s content page component uses templates to create pages.

  1. Create a node of type cq:Page and save,
  2. Add cq:PageContent node(named jcr:content) under page node
  3. Add/Copy required properties manually.

Understand AEM URL decomposition

Rules of URL decomposition are list below

1. Resource Path

  • The longest substring of the request URL such that the resource path is either the complete request URL
  • Or the next character in the request URL after the resource path is a dot (.), which means the part between host and the first dot in the whole request

2. Selectors

  • If the first character in the request URL after the resource path is a dot,
    the string after the dot up to but not including the last dot before the next slash character or the end of the request URL.
  • If the resource path spans the complete request URL no seletors exist.
  • If only one dot follows the resource path before the end of the request URL or the next slash, also no selectors exist.

3. Extension

  • The string after the last dot after the resource path in the request URL but before the end of the request URL
  • Or the next slash after the resource path in the request URL.

4. Suffix Path

  • If the request URL contains a slash character after the resource path and optional selectors and extension, the path starting with the slash up to the end of the request URL is the suffix path.
  • Otherwise, the suffix path is empty. Note, that after the resource path at least a dot must be in the URL to let Sling detect the resource path.

5. Params

  • The key-value after queston mark(?).

For example, with the URL: http://www.aemtreasury.com/articles/category-name/article-name.print.a4.html/a/b?name=Dale

the URL can be broken down into composite parts:

protocol host path selector extension suffix params
https http://www.aemtreasury.com articles/category-name/article-name print.a4 html / a/b ? name=Dale

References:

  1. “URL Decompositon” section in https://docs.adobe.com/docs/en/aem/6-0/develop/the-basics.html
  2. https://sling.apache.org/documentation/the-sling-engine/url-decomposition.html

Configure the Rich text Editor

It is quit easy to configure the rich text editor as your requirement. The Adobe document is great.

<items jcr:primaryType=”cq:TabPanel”>
<items jcr:primaryType=”cq:WidgetCollection”>
<tab1
jcr:primaryType=”cq:Panel”
title=”Rich Text Editor”>
<items jcr:primaryType=”cq:WidgetCollection”>
<text-value
jcr:primaryType=”cq:Widget”
name=”./text”
xtype=”richtext”
height=”150″
fieldLabel=”Text Value”>
<rtePlugins jcr:primaryType=”nt:unstructured”>
<paraformat jcr:primaryType=”nt:unstructured” features=”*”>
<formats jcr:primaryType=”cq:WidgetCollection”>
    <paragraph jcr:primaryType=”nt:unstructured” tag=”p” description=”Paragraph”/>
     <heading-h2 jcr:primaryType=”nt:unstructured” tag=”h2″ description=”Heading h2″/>
</formats>
</paraformat>
</rtePlugins>
</text-value>
</items>
</tab1>
</items>
</items>

A magic way to get thumbnail in AEM

Today, I just figure out a easy to get a thumbnail of image in AEM by using “thumb” as selector

Example:

If the orginal image is http://localhost:4502/content/page-name/jcr:content/image/file
then the thumbnail of it will be http://localhost:4502/content/page-name/jcr:content/image/file.thumb.jpg
The magic comes from com.day.cq.wcm.core.impl.servlets.ThumbnailServlet which is a core cq servlet to handle thumbnail requests.

Image below is the details I get from http://localhost:4502system/console/components

ThumbnailServlet

How to customize AEM create page default behavior

Problem:
AEM will generate page name depend on the page title when the author click new page button to create a page without specified the name of the page. However sometimes the generated page name is not perfect. E.g. if title is “Is this a title with & and more space” then the generated name will be “is-this-a-title-with—and-more—space”. There are more dashes in the name which I don’t like.

Solution:

1. UI changes: customize the default save behavior of the creating page dialog.

There are 2 solutions on UI changes.

Solution 1 steps: (This the one I used)
1) create folder (nt:folder) /apps/createpagecustomprop

2) Create clientlib (type cq:ClientLibraryFolder) with categories set to “cq.widgets”: /apps/mytest/clientlib

3) Add custom-title.js to /apps/mytest/clientlib/js.txt,

4) Create file custom-title.js /apps/createpagecustomprop/clientlib/custom-title.js (can use /libs/cq/ui/widgets/source/widgets/wcm/Page.Actions.js as refrence)

(function(){
//the original create page dialog fn
var cqCreatePageDialog = CQ.wcm.Page.getCreatePageDialog;

//override function
CQ.wcm.Page.getCreatePageDialog = function(parentPath){
//create dialog by executing the product function

var dialog = cqCreatePageDialog(parentPath);
dialog.params.cmd = “customizedPageCommand”;

var cmdField = dialog.formPanel.findBy(function(comp){
return comp[“name”] == “cmd”;
}, dialog.formPanel);

cmdField[0].setValue(“customizedPageCommand”);
return dialog;
}
})();

Solution 2 steps:
This solution uses the overlay architecture of CQ to extent create page dialog(CQ.wcm.Page.getCreatePageDialog) via overriding /libs/cq/ui/widgets/source/widgets/wcm/Page.Actions.js. However this is not a preferred way to do, because of the scripts won’t be minified.
1) The source of page create dialog is file /libs/cq/ui/widgets/source/widgets/wcm/Page.Actions.js. So, to overlay the actions available in this file, create file /apps/cq/ui/widgets/source/widgets/wcm/Page.Actions.js

$.getScript(“/libs/cq/ui/widgets/source/widgets/wcm/Page.Actions.js”, function(){

var cqCreatePageDialog = CQ.wcm.Page.getCreatePageDialog;

//override function
CQ.wcm.Page.getCreatePageDialog = function(parentPath){
//create dialog by executing the product function

var dialog = cqCreatePageDialog(parentPath);
dialog.params.cmd = “customizedPageCommand”;

var cmdField = dialog.formPanel.findBy(function(comp){
return comp[“name”] == “cmd”;
}, dialog.formPanel);

cmdField[0].setValue(“customizedPageCommand”);
return dialog;
}
});

2. Create customize command to support the new create page

Create java class CustomizedPageCommand implementing WCMCommand and returning the custom action name in getCommandName() which is executed this command by /bin/wcmcommand servlet if a request is sent with the cmd param. And the return value of getCommandName() is used in the UI changes(javascript changes(in my case, it is “customizedPageCommand”).

import com.day.cq.commons.servlets.HtmlStatusResponseHelper;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import com.day.cq.wcm.api.commands.WCMCommand;
import com.day.cq.wcm.api.commands.WCMCommandContext;
import org.apache.commons.collections.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.HtmlResponse;

import javax.jcr.Node;
import javax.jcr.Session;
import java.util.Arrays;
import java.util.List;

@Component(immediate = true, metatype = true, inherit = true)
@Service(value = WCMCommand.class)
public class CustomizedPageCommand implements WCMCommand {
@Override
public HtmlResponse performCommand(WCMCommandContext wcmCommandContext, SlingHttpServletRequest slingHttpServletRequest, SlingHttpServletResponse slingHttpServletResponse, PageManager pageManager) {
HtmlResponse resp = null;

try {
String parentPath = slingHttpServletRequest.getParameter(PARENT_PATH_PARAM);
String template = slingHttpServletRequest.getParameter(TEMPLATE_PARAM);
String pageTitle = slingHttpServletRequest.getParameter(PAGE_TITLE_PARAM);
String pageLabel = slingHttpServletRequest.getParameter(PAGE_LABEL_PARAM);

if (StringUtils.isBlank(pageLabel)) {
String regex = “([^a-zA-Z0-9′]+)’*\\1*”;
String[] split = pageTitle.split(regex);

List<String> titles = Arrays.asList(split);
pageLabel = String.join(“-“, titles);
}

Page page = pageManager.create(parentPath, pageLabel, template, pageTitle);

Session session = slingHttpServletRequest.getResourceResolver().adaptTo(Session.class);

session.save();

resp = HtmlStatusResponseHelper.createStatusResponse(true, “pages iscreated”, page.getPath());
} catch (Exception e) {
resp = HtmlStatusResponseHelper.createStatusResponse(false, e.getMessage());
}

return resp;
}

@Override
public String getCommandName() {
return “customizedPageCommand”;
}
}

Conclusion:

By doing this way we also can customize elements in creating page dialog. For the 2 UI change solutions I personally prefer solution 1

Access Sling Servlet/Service without requiring authentication

Yesterday, I tried to get a Json result from a Sling servlet, However I can’t get the result I want. Finally I figure out I need login first.

My solution is to use @Property(name = “sling.auth.requirements”, value = “-/bin/mysearch”, propertyPrivate = true) to to ensure the servlet can be accessed without requiring authentication.

@SlingServlet(paths = “/bin/mysearch”, methods = {“GET”})
@Property(name = “sling.auth.requirements”, value = “-/bin/mysearch”, propertyPrivate = true)
public class SearchArticlesServlet extends SlingSafeMethodsServlet {

…….
}

The Sling Authentication – Framework document does help a lot, it described below:

Anonymous Login

The SlingAuthenticator provides high level of control with respect to allowing anonymous requests or requiring authentication up front:

  • Global setting of whether anonymous requests are allowed or not. This is the value of the Allow Anonymous Access (auth.annonymous) property of the SlingAuthenticator configuration. This property is supported for backwards compatibility and defaults to true (allowing anonymous access).
  • Specific configuration per URL. The Authentication Requirements (sling.auth.requirements) property of the SlingAuthenticator configuration may provide a list of URLs for which authentication may be required or not: Any entry prefixed with a dash - defines a subtree for which authentication is not required. Any entry not prefixed with a dash or prefixed with a plus + defines a subtree for which authentication is required up front and thus anonymous access is not allowed. This list is empty by default.
  • Any OSGi service may provide a sling.auth.requirements registration property which is used to dynamically extend the authentication requirements from the Authentication Requirements configuration. This may for example be set by AuthenticationHandler implementations providing a login form to ensure access to the login form does not require authentication. The value of this property is a single string, an array of strings or a Collection of strings and is formatted in the same way as the Authentication Requirements configuration property.

The URLs set on the Authentication Requirements configuration property or the sling.auth.requirements service registration property can be absolute paths or URLs like the path service registration property ofAuthenticationHandler services. This allows the limitation of this setup to certain requests by scheme and/or virtual host address.

Examples

  • The LoginServlet contained in the Sling Auth Core bundle registers itself with the service registration property sling.auth.requirements = "-/system/sling/login" to ensure the servlet can be accessed without requiring authentication.
  • An authentication handler may register itself with the service registration property sling.auth.requirements = "-/apps/sample/loginform" to ensure the login form can be rendered without requiring authentication.

Using ResourceResolver in service

Recently, I tried to get ResourceResolver directly from services rather than using request.getResourceResolver().
As API document said,  I can use resolverFactory.getServiceResourceResolver() to get ResourceResolver object.

The main code I used list below:

Map<String, Object> param = new HashMap<String, Object>();
param.put(ResourceResolverFactory.SUBSERVICE, “properPrivilege”);
resolver = resolverFactory.getServiceResourceResolver(param);

The Major part is the param which I give to getServiceResourceResolver got have proper privilege, Otherwise I got Null ResourceResolver object.

How I can use the proper privilege?

I’ve done this by configrating “Apache Sling Service User Mapper Service” via OSGI configuration(http://localhost:4502/system/console/configMgr)

Look at screen shoot below

sling-service-mappingCreate new config in Service mappings section by following the rule:

Provides mappings from service name to user names. Each entry is of the form ‘serviceName [ “:” subServiceName ] “=” userName’ where serviceName and subServiceName identify the service and userName defines the name of the user to provide to the service. Invalid entries are logged and ignored. (user.mapping)

In my case, com.dale.cq.services.tryservice:properPrivilege=properUser   (properUser should be the user given proper read/write privilege , I use the author user for testing).

Note: “com.dale.cq.services.tryservice” used here is  NOT a package ID, it is a BundleId which can be gotten from http://localhost:4502/system/console/bundles
There are 2 way to simplify :

1. Simplify config it to com.dale.cq.services.tryservice=properUser (in my case is  com.dale.cq.services.tryservice=author)

or

2. Just use resolver = resolverFactory.getServiceResourceResolver(null); and config Default User as a properuser with right privilege (I use the author user for testing)

A way to see all vanity urls

A way to see all vanity urls

Here is a easy way to see vanity urls. Simplely follow the steps below:

1.Install the vanityurls-1.0.2.zip in both Author and Publish instance(vanityurls-1.0.2.zip can be download from Adobe Package Share)
2. Access localhost:4502/libs/granite/dispatcher/content/vanityUrls.html

If you’d like to configure Dispatcher to enable access to vanity URLs that are configured for your CQ
1 .Add the following in your dispatcher.any file(Bold part)

/filter {
/0001 { /type “allow” /glob “*” }


/0002 { /type “deny” /url “/articles/category-name/*.html” }
}
/vanity_urls {
/url “/libs/granite/dispatcher/content/vanityUrls.html”
/file “/tmp/vanity_urls”
/delay 300
}
/cache {

2. Restart Apache
3. Log in Apache machine, you will see a vanity_urls file in /tmp

Ref: https://docs.adobe.com/docs/en/dispatcher/disp-config.html#Enabling%20Access%20to%20Vanity%20URLs%20-%20/vanity_urls

How to set configuration of different run modes

The generic rule is:

The configuration in specified run mode is override the generic one.

For examples: I config CQ Html Library manager in my project

The file structure is:

/app

—myproject

——/config

———com.day.cq.widget.impl.HtmlLibraryManagerImpl.xml  (Minify:true, Gzip:true)

——/config.author

———com.day.cq.widget.impl.HtmlLibraryManagerImpl.xml  (Minify:false, Gzip:false)

——/config.author.dev

———com.day.cq.widget.impl.HtmlLibraryManagerImpl.xml  (Minify:false, Gzip:true)

The final configuration is same as(Minify:false, Gzip:true) in config.author.dev

Then I remove configuration under  config.author.dev, the configuration become (Minify:false, Gzip:false) as in config.author

Then I remove configuration under  config.author, the configuration become(Minify:true, Gzip:true) as in config

And here is a good article about set run modes