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!

No comments:

Post a Comment