Showing posts with label AEM component errors. Show all posts
Showing posts with label AEM component errors. Show all posts

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!