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.