info.magnolia.rendering.engine.RenderingFilter.java Source code

Java tutorial

Introduction

Here is the source code for info.magnolia.rendering.engine.RenderingFilter.java

Source

/**
 * This file Copyright (c) 2003-2012 Magnolia International
 * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
 *
 *
 * This file is dual-licensed under both the Magnolia
 * Network Agreement and the GNU General Public License.
 * You may elect to use one or the other of these licenses.
 *
 * This file is distributed in the hope that it will be
 * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
 * Redistribution, except as permitted by whichever of the GPL
 * or MNA you select, is prohibited.
 *
 * 1. For the GPL license (GPL), you can redistribute and/or
 * modify this file under the terms of the GNU General
 * Public License, Version 3, as published by the Free Software
 * Foundation.  You should have received a copy of the GNU
 * General Public License, Version 3 along with this program;
 * if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * 2. For the Magnolia Network Agreement (MNA), this file
 * and the accompanying materials are made available under the
 * terms of the MNA which accompanies this distribution, and
 * is available at http://www.magnolia-cms.com/mna.html
 *
 * Any modifications to this file must keep this entire header
 * intact.
 *
 */
package info.magnolia.rendering.engine;

import info.magnolia.cms.core.AggregationState;
import info.magnolia.cms.filters.AbstractMgnlFilter;
import info.magnolia.context.MgnlContext;
import info.magnolia.jcr.wrapper.ChannelVisibilityContentDecorator;
import info.magnolia.registry.RegistrationException;
import info.magnolia.rendering.template.TemplateDefinition;
import info.magnolia.rendering.template.registry.TemplateDefinitionRegistry;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;

import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.jackrabbit.JcrConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Filter responsible for rendering the current aggregation state,
 * by delegating to the appropriate TemplateRenderer or by serving
 * binary content.
 */
public class RenderingFilter extends AbstractMgnlFilter {

    private static final Logger log = LoggerFactory.getLogger(RenderingFilter.class);

    private final RenderingEngine renderingEngine;

    private final TemplateDefinitionRegistry templateDefinitionRegistry;

    public RenderingFilter(RenderingEngine renderingEngine, TemplateDefinitionRegistry templateDefinitionRegistry) {
        this.renderingEngine = renderingEngine;
        this.templateDefinitionRegistry = templateDefinitionRegistry;
    }

    @Override
    public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        final AggregationState aggregationState = MgnlContext.getAggregationState();

        String templateName = aggregationState.getTemplateName();
        if (StringUtils.isNotEmpty(templateName)) {
            try {
                // don't reset any existing status code, see MAGNOLIA-2005
                // response.setStatus(HttpServletResponse.SC_OK);
                if (response != MgnlContext.getWebContext().getResponse()) {
                    log.warn("Context response not synced. This may lead to discrepancies in rendering.");
                }

                Node content = aggregationState.getMainContent().getJCRNode();

                // if the content isn't visible output a 404
                if (!isVisible(content, request, response, aggregationState)) {
                    if (!response.isCommitted()) {
                        response.sendError(HttpServletResponse.SC_NOT_FOUND);
                    } else {
                        log.info("Unable to redirect to 404 page for {}, response is already committed",
                                request.getRequestURI());
                    }
                    return;
                }

                render(content, templateName, response);

                try {
                    response.flushBuffer();
                } catch (IOException e) {
                    // don't log at error level since tomcat typically throws a
                    // org.apache.catalina.connector.ClientAbortException if the user stops loading the page
                    log.debug("Exception flushing response " + e.getClass().getName() + ": " + e.getMessage(), e);
                }

            } catch (RenderException e) {
                // TODO better handling of rendering exception
                // TODO dlipp: why not move this section up to the actual call to render() -> that's the only place where a RenderException could occur...
                log.error(e.getMessage(), e);
                throw new ServletException(e);
            } catch (Exception e) {
                // TODO dlipp: there's no other checked exceptions thrown in the code above - is it correct to react like that???
                log.error(e.getMessage(), e);
                if (!response.isCommitted()) {
                    response.setContentType("text/html");
                }
                throw new RuntimeException(e);
            }
        } else {
            // direct request
            handleResourceRequest(aggregationState, request, response);
        }

        // TODO don't make it a dead end
        //      currently we can't process the chain because there is no content/nop servlet
        // chain.doFilter(request, response);
    }

    protected boolean isVisible(Node content, HttpServletRequest request, HttpServletResponse response,
            AggregationState aggregationState) {

        // if there's a channel set test if the content is excluded for the current channel
        if (aggregationState.getChannel() != null) {
            String currentChannel = aggregationState.getChannel().getName();
            if (StringUtils.isNotEmpty(currentChannel) && !currentChannel.equalsIgnoreCase("all")) {
                ChannelVisibilityContentDecorator decorator = new ChannelVisibilityContentDecorator(currentChannel);
                return decorator.evaluateNode(content);
            }
        }

        return true;
    }

    protected void render(Node content, String templateName, HttpServletResponse response) throws RenderException {

        TemplateDefinition templateDefinition;
        try {
            templateDefinition = templateDefinitionRegistry.getTemplateDefinition(templateName);
        } catch (RegistrationException e) {
            throw new RenderException(e);
        }
        renderingEngine.render(content, templateDefinition, Collections.<String, Object>emptyMap(),
                new ResponseOutputProvider(response));
    }

    /**
     * Get the requested resource and copy it to the ServletOutputStream, bit by bit.
     * @param request HttpServletRequest as given by the servlet container
     * @param response HttpServletResponse as given by the servlet container
     * @throws IOException standard servlet exception
     */
    protected void handleResourceRequest(AggregationState aggregationState, HttpServletRequest request,
            HttpServletResponse response) throws IOException {

        final String resourceHandle = aggregationState.getHandle();

        log.debug("handleResourceRequest, resourceHandle=\"{}\"", resourceHandle);

        if (StringUtils.isNotEmpty(resourceHandle)) {

            InputStream is = null;
            try {
                Session session = MgnlContext.getJCRSession(aggregationState.getRepository());
                is = getNodedataAsStream(resourceHandle, session, response);
                if (null != is) {
                    // don't reset any existing status code, see MAGNOLIA-2005
                    // response.setStatus(HttpServletResponse.SC_OK);
                    sendUnCompressed(is, response);
                    IOUtils.closeQuietly(is);
                    return;
                }
            } catch (IOException e) {
                // don't log at error level since tomcat tipically throws a
                // org.apache.catalina.connector.ClientAbortException if the user stops loading the page
                log.debug("Exception while dispatching resource " + e.getClass().getName() + ": " + e.getMessage(),
                        e);
                return;
            } catch (Exception e) {
                log.error("Exception while dispatching resource  " + e.getClass().getName() + ": " + e.getMessage(),
                        e);
                return;
            } finally {
                IOUtils.closeQuietly(is);
            }
        }
        log.debug("Resource not found, redirecting request for [{}] to 404 URI", request.getRequestURI());

        if (!response.isCommitted()) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
        } else {
            log.info("Unable to redirect to 404 page for {}, response is already committed",
                    request.getRequestURI());
        }

    }

    /**
     * Send data as is.
     * @param is Input stream for the resource
     * @param response HttpServletResponse as received by the service method
     * @throws IOException standard servlet exception
     */
    private void sendUnCompressed(InputStream is, HttpServletResponse response) throws IOException {
        ServletOutputStream os = response.getOutputStream();
        byte[] buffer = new byte[8192];
        int read;
        while ((read = is.read(buffer)) > 0) {
            os.write(buffer, 0, read);
        }
        os.flush();
        IOUtils.closeQuietly(os);
    }

    private InputStream getNodedataAsStream(String path, Session session, HttpServletResponse res) {

        log.debug("getNodedataAstream for path \"{}\"", path);

        try {
            Node atom = session.getNode(path);
            if (atom != null) {
                if (atom.hasProperty(JcrConstants.JCR_DATA)) {
                    Property sizeProperty = atom.getProperty("size");
                    String sizeString = sizeProperty == null ? "" : sizeProperty.getString();
                    if (NumberUtils.isNumber(sizeString)) {
                        res.setContentLength(Integer.parseInt(sizeString));
                    }
                    Property streamProperty = atom.getProperty(JcrConstants.JCR_DATA);
                    return streamProperty.getStream();
                }
            }

            log.warn("Resource not found: [{}]", path);
        } catch (RepositoryException e) {
            log.error("RepositoryException while reading Resource [" + path + "]", e);
        }
        return null;
    }

}