/**
* 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;
}
}