Wednesday 11 April 2018

Authenticating Web Console Users against the JCR Repository

Ever encountered a use case to provide users logged in to the JCR, access to the Felix Web Console?

The Apache Sling Web Console Security Provider bundle verifies user credentials and grants access to the Apache Felix Web console based on registered JCR repository users.

The following configuration is used to add users that have to be granted access:



User Names - The list of users granted access to web console.
Group Names - The list of groups whose members are granted access to the web console.

The default configuration explicitly grants the user 'admin' access to the felix web console.

If the web console security provider bundle is not activated, then the default web console authentication is used. 

Hope this helps!

Tuesday 13 June 2017

extraClientlibs in AEM

To define styling and behavior of our AEM components, we create client libraries that defines our custom CSS and JS. In order to create clientlibs that load only in the Authoring mode, the general practice is to create a client library and load that only in the Author mode:

<sly data-sly-test.author="${wcmmode.edit || wcmmode.design}" 
data-sly-call="${clientlib.js @categories='custom.authorjs'}" /> </sly>

Further, if we want our client library loaded for all dialogs, we can set the category property to 'cq.authoring.dialog'. This would let the client library load for all dialogs.

This created performance issues sometimes when my client library is too big and I try loading it in all dialogs even when it is not needed. Sometimes, I would just want to have my client library only for a specific component's dialog.

To have my client library loaded solely for my component dialog, I need to the set the property 'extraClientLibs' of my dialog to the category name of the client library.

Here is an example:



Hope this helps!



Wednesday 7 June 2017

Sling Pipes

Ever encountered a situation where code changes were introduced after the client started authoring and some pages had to be re-authored? Ever spent time writing code just to modify a few hundred pages that were already authored, or with removing a component from hundreds of authored pages? Have you struggled to modify content already in the repository? Need a script to change existing production content? Sling Pipes to the rescue.
Sling Pipes is a tool for doing extract – transform – load operations through a resource tree configuration. This tiny toolset provides the ability to do such transformations with proven and reusable blocks, called pipes, streaming resources from one to the other.
A pipe is a JCR node with:
  • sling:resourceType property – Must be a pipe type registered by the plumber
  • name property – Used in bindings as an id
  • path property – Defines pipe’s input
  • expr property – Expression through which the pipe will execute
  • additionalBinding node – Node you can add to set “global” bindings (property=value) in pipe execution
  • additionalScripts – Multivalue property to declare scripts that can be reused in expressions
  • conf child node – Contains addition configuration of the pipe

Registered Pipes:

Container Pipes
Pipe
Description
sling:resourceType
Container Pipe
assemble a sequence of pipes
slingPipes/container
ReferencePipe
execute the pipe referenced in path property
slingPipes/reference

Reader Pipes
Pipe
Description
sling:resourceType
Base Pipe
outputs what is in input
slingPipes/base
SlingQuery Pipe
executes $(getInput()).children(expression)
slingPipes/slingQuery
JsonPipe
feeds bindings with remote json
slingPipes/json
MultiPropertyPipe
iterates through values of input multi value property and write them to bindings
slingPipes/multiProperty
XPathPipe
retrieve resources resulting of an xpath query
slingPipes/xpath
AuthorizablePipe
retrieve authorizable resource corresponding to the id passed in expression
slingPipes/authorizable
ParentPipe
outputs the parent resource of input resource
slingPipes/parent
FilterPipe
outputs the input resource if its matches its configuration
slingPipes/filter

Writer Pipes
Pipe
Description
sling:resourceType
Write Pipe
writes given nodes & properties to current input
slingPipes/write
MovePipe
JCR move of current input to target path (can be a node or a property)
slingPipes/mv
RemovePipe
removes the input resource
slingPipes/rm
PathPipe
get or create path given in expression
slingPipes/path

Here is a demo video with more on how to use and execute sling pipes in AEM.

Vid
More details can be found in the official documentation at https://sling.apache.org/documentation/bundles/sling-pipes.html

Monday 29 May 2017

Component Error Handling AEM

There might be a use case when we would want to handle errors at the component level. We would not want a single component throwing errors breaking the entire page. To handle such a situation in AEM, we can write a filter that does not let the component render on the page when it has errors.

Here is a sample example:


@Component(
        label = "Component Level Error Handler",
        description = "Handles errors at the component level. Allows blank HTML renditions to display for erring in publish mode",
        metatype = true
)
@Service
public class ComponentErrorHandlerImpl implements Filter {

    private static final Logger LOGGER = LoggerFactory.getLogger(ComponentErrorHandlerImpl.class);
    static final String BLANK_HTML = "";

    private static final boolean DEFAULT_PUBLISH_ENABLED = true;
    private boolean publishModeEnabled = DEFAULT_PUBLISH_ENABLED;

    @Property(label = "Publish Error Handling",
            description = "Enable handling of Edit-mode errors (PREVIEW and READONLY)",
            boolValue = DEFAULT_PUBLISH_ENABLED)
    public static final String PROP_PUBLISH_ENABLED = "publish.enabled";

    static final String REQ_ATTR_PREVIOUSLY_PROCESSED =
            ComponentErrorHandlerImpl.class.getName() + "_previouslyProcessed";

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain chain) throws IOException, ServletException {
        {
            final SlingHttpServletRequest request = (SlingHttpServletRequest) servletRequest;
            final SlingHttpServletResponse response = (SlingHttpServletResponse) servletResponse;

            if (!this.accepts(request, response)) {
                chain.doFilter(request, response);
                return;
            }

            final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request;
            final SlingHttpServletResponse slingResponse = (SlingHttpServletResponse) response;

            if (publishModeEnabled
                    && WCMMode.DISABLED.equals(WCMMode.fromRequest(slingRequest))
                    && !this.isFirstInChain(slingRequest)) {
                // Publish Modes; Requires special handling in Published Modes - do not process first filter chain
                this.doFilterWithErrorHandling(slingRequest, slingResponse, chain, BLANK_HTML);
            } else {
                // Normal Behavior
                chain.doFilter(request, response);
            }
        }

    }

    private boolean isFirstInChain(final SlingHttpServletRequest request) {
        if (request.getAttribute(REQ_ATTR_PREVIOUSLY_PROCESSED) != null) {
            return false;
        } else {
            request.setAttribute(REQ_ATTR_PREVIOUSLY_PROCESSED, true);
            return true;
        }
    }

    private void doFilterWithErrorHandling(final SlingHttpServletRequest slingRequest,
                                           final SlingHttpServletResponse slingResponse,
                                           final FilterChain chain,
                                           final String pathToHTML) throws ServletException, IOException {

        try {
            chain.doFilter(slingRequest, slingResponse);
        } catch (final Exception ex) {
            // Handle error using the Component Error Handler HTML
            this.handleError(slingResponse, slingRequest.getResource(), pathToHTML, ex);
        }
    }

    private void handleError(final SlingHttpServletResponse slingResponse, final Resource resource,
                             final String pathToHTML, final Throwable ex) throws IOException {
        // Log the error to the log files, so the exception is not lost
        LOGGER.error(ex.getMessage(), ex);

        // Write the html out to the response
        this.writeErrorHTML(slingResponse, resource, pathToHTML);
    }

    private void writeErrorHTML(final SlingHttpServletResponse slingResponse, final Resource resource,
                                final String pathToHTML) throws IOException {
        LOGGER.info("Component-Level Error Handling trapped error for: {}",
                resource.getPath());

        //print blank HTML
        slingResponse.getWriter().print(pathToHTML);
    }


    protected final boolean accepts(final SlingHttpServletRequest request, final SlingHttpServletResponse response) {


        try {
            if (!(request.getRequestURI().endsWith(".html")) ||
                    !(response.getContentType().contains("html"))) {
                // Do not inject around non-HTML requests
                return false;
            }

        } catch (NullPointerException e) {
            return false;

        }

        final ComponentContext componentContext = WCMUtils.getComponentContext(request);
        if (componentContext == null) {
            // ComponentContext is null
            return false;
        } else if (componentContext.getComponent() == null) {
            // Component is null
            return false;
        } else if (componentContext.isRoot()) {
            // Suppress on root context
            return false;
        }

        return true;
    }


    @Override
    public void destroy() {

    }


    @Activate
    public final void activate(final Map<String, String> config) {
        final String legacyPrefix = "prop.";

        publishModeEnabled = PropertiesUtil.toBoolean(config.get(PROP_PUBLISH_ENABLED),
                PropertiesUtil.toBoolean(config.get(legacyPrefix + PROP_PUBLISH_ENABLED),
                        DEFAULT_PUBLISH_ENABLED));

    }

}


Hope this helps!

Wednesday 24 May 2017

AEM Search Suggestions

AEM provides out of the box Suggester API to populate suggestions for a particular keyword. com.day.cq.search.suggest.Suggester API provides methods to fetch suggestions and it provides a spell check feature as well.

An example would be:

import com.day.cq.search.suggest.Suggester;
import com.soh.services.SearchSuggestion;
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.api.resource.ResourceResolver;
import org.apache.sling.commons.json.JSONArray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.Session;

/**
 * SOH Search Suggester
 */
@Component
@Service(SearchSuggestion.class)
public class SearchSuggestionImpl implements SearchSuggestion {
    /**
     * The Suggester Index
     */
    private static final String INDEX = "oak-cq:Page";

    /**
     * CQ Suggester Reference
     */
    @Reference
    private Suggester suggester;

    /**
     * 
     * @param keyword
     *            to search for
     * @return JSONArray containing search suggestions
     */
    @Override
    public JSONArray getSuggestions(String keyword, int noOfItems, ResourceResolver resourceResolver) {
        final boolean spellCheck = true;
        final JSONArray suggestions = new JSONArray();
        final Session session = resourceResolver.adaptTo(Session.class);
        int counter = 0;
        for (final String suggestion : suggester.getSuggestions(session, INDEX, keyword, spellCheck)) {
            counter++;
            suggestions.put(suggestion);
            if (counter == noOfItems) {
                break;
            }
        }
        if (suggestions.length() < 1) {
            final String spellSuggestion = suggester.spellCheck(session, keyword);
            for (final String suggestion : suggester.getSuggestions(session, INDEX, spellSuggestion, spellCheck)) {
                counter++;
                suggestions.put(suggestion);
                if (counter == noOfItems) {
                    break;
                }
            }
        }

        return suggestions;
    }
}

Further, we can configure our index to specify which JCR properties do we want to include while populating suggestions. useInSuggest="{Boolean}true" is the property that we have to specify. Here is a sample index which includes jcr:title property in the suggestions:


<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:oak="http://jackrabbit.apache.org/oak/ns/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:rep="internal"
    jcr:mixinTypes="[rep:AccessControllable]"
    jcr:primaryType="nt:unstructured">
    <customLuceneIndex
        jcr:primaryType="oak:QueryIndexDefinition"
        async="async"
        evaluatePathRestrictions="{Boolean}true"
        includedPaths="[/content/myproject]"
        reindex="{Boolean}false"
        type="lucene">
        <analyzers jcr:primaryType="nt:unstructured">
            <default
                jcr:primaryType="nt:unstructured"
                class="org.apache.lucene.analysis.standard.StandardAnalyzer">
                <stopwords/>
            </default>
        </analyzers>
        <indexRules jcr:primaryType="nt:unstructured">
            <cq:Page jcr:primaryType="nt:unstructured">
                <properties jcr:primaryType="nt:unstructured">
                    <jcrTitle
                        jcr:primaryType="nt:unstructured"
                        name="jcr:content/jcr:title"
                        nodeScopeIndex="{Boolean}true"
                        ordered="{Boolean}false"
                        propertyIndex="{Boolean}true"
                        type="String"
                        useInSuggest="{Boolean}true"/>
                </properties>
            </cq:Page>
        </indexRules>
    </customLuceneIndex>
</jcr:root>



Hope this helps!

Monday 22 May 2017

Too Many Calls Excpetion - AEM 6.2

If we have a number of components in our AEM 6.2 Author instance and we are also running samplecontent on our AEM, then we might encounter this error:

org.apache.sling.api.request.TooManyCallsException

This happens to occur when the number of components are typically more than 1000.

An ideal solution would be to restrict the number of components we use in an AEM instance and deleting the geometrixx content. However, there might come a use case where we are bound to deal with it.

Here are the configuration details which are needed to be taken care of:

1. Go to the Felix Configuration Managerh: http://localhost:4502/system/console/configMgr
2. Search for 'Apache Sling Main Servlet'
3. Edit the 'Number of calls per Request' - It should be high enough not to limit request processing and at the same time not too high not to break the mechanism to limit the resources in case of errors.


Tuesday 4 October 2016

adaptTo() 2016 - Day 3

The third and final day at adaptTo() 2016. Finally the grand finale!

Session Details:
  • Managing Cloud Performance and Large Data sets by Mike Tilburg and Tom Blackford
  • OSGi R7 by Carsten Ziegeler and David Bosschaert
  • Test-driven development with AEM by Jan Wloka
  • Introduction to Sling Pipes by Nicolas Peltier
  • AC tool by Jochen Koschorke and Roland Gruber
  • APM - AEM Permission Management by Mateusz ChromiƄski
  • Using Thymeleaf for rendering HTML and Text in AEM 6.x by Oliver Lietz
  • Hey Sling, what are you doing? Sling Tracer to the rescue by Chetan Mehrotra
  • Apache Sling and Karaf for Web Sites and Applications by Oliver Lietz
Loved being a part of adaptTo() 2016!