View Javadoc

1   /**
2    * This file Copyright (c) 2003-2013 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.rendering.engine;
35  
36  import info.magnolia.cms.core.AggregationState;
37  import info.magnolia.cms.filters.AbstractMgnlFilter;
38  import info.magnolia.context.MgnlContext;
39  import info.magnolia.jcr.wrapper.ChannelVisibilityContentDecorator;
40  import info.magnolia.registry.RegistrationException;
41  import info.magnolia.rendering.template.TemplateDefinition;
42  import info.magnolia.rendering.template.registry.TemplateDefinitionRegistry;
43  
44  import java.io.IOException;
45  import java.io.InputStream;
46  import java.util.Collections;
47  
48  import javax.jcr.Node;
49  import javax.jcr.Property;
50  import javax.jcr.RepositoryException;
51  import javax.jcr.Session;
52  import javax.servlet.FilterChain;
53  import javax.servlet.ServletException;
54  import javax.servlet.ServletOutputStream;
55  import javax.servlet.http.HttpServletRequest;
56  import javax.servlet.http.HttpServletResponse;
57  
58  import org.apache.commons.io.IOUtils;
59  import org.apache.commons.lang.StringUtils;
60  import org.apache.commons.lang.math.NumberUtils;
61  import org.apache.jackrabbit.JcrConstants;
62  import org.slf4j.Logger;
63  import org.slf4j.LoggerFactory;
64  
65  /**
66   * Filter responsible for rendering the current aggregation state,
67   * by delegating to the appropriate TemplateRenderer or by serving
68   * binary content.
69   */
70  public class RenderingFilter extends AbstractMgnlFilter {
71  
72      private static final Logger log = LoggerFactory.getLogger(RenderingFilter.class);
73  
74      private final RenderingEngine renderingEngine;
75  
76      private final TemplateDefinitionRegistry templateDefinitionRegistry;
77  
78      public RenderingFilter(RenderingEngine renderingEngine, TemplateDefinitionRegistry templateDefinitionRegistry) {
79          this.renderingEngine = renderingEngine;
80          this.templateDefinitionRegistry = templateDefinitionRegistry;
81      }
82  
83      @Override
84      public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException{
85          final AggregationState aggregationState = MgnlContext.getAggregationState();
86  
87          String templateName = aggregationState.getTemplateName();
88          if (StringUtils.isNotEmpty(templateName)) {
89              try {
90                  // don't reset any existing status code, see MAGNOLIA-2005
91                  // response.setStatus(HttpServletResponse.SC_OK);
92                  if (response != MgnlContext.getWebContext().getResponse()) {
93                      log.warn("Context response not synced. This may lead to discrepancies in rendering.");
94                  }
95  
96                  Node content = aggregationState.getMainContentNode();
97  
98                  // if the content isn't visible output a 404
99                  if (!isVisible(content, request, response, aggregationState)) {
100                     if (!response.isCommitted()) {
101                         response.sendError(HttpServletResponse.SC_NOT_FOUND);
102                     }
103                     else {
104                         log.info("Unable to redirect to 404 page for {}, response is already committed", request.getRequestURI());
105                     }
106                     return;
107                 }
108 
109                 render(content, templateName, response);
110 
111                 try {
112                     response.flushBuffer();
113                 }
114                 catch (IOException e) {
115                     // don't log at error level since tomcat typically throws a
116                     // org.apache.catalina.connector.ClientAbortException if the user stops loading the page
117                     log.debug("Exception flushing response " + e.getClass().getName() + ": " + e.getMessage(), e);
118                 }
119 
120             }
121             catch (RenderException e) {
122                 // TODO better handling of rendering exception
123                 // TODO dlipp: why not move this section up to the actual call to render() -> that's the only place where a RenderException could occur...
124                 log.error(e.getMessage(), e);
125                 throw new ServletException(e);
126             }
127             catch (Exception e) {
128                 // TODO dlipp: there's no other checked exceptions thrown in the code above - is it correct to react like that???
129                 log.error(e.getMessage(), e);
130                 if (!response.isCommitted()) {
131                     response.setContentType("text/html");
132                 }
133                 throw new RuntimeException(e);
134             }
135         }
136         else {
137             // direct request
138             handleResourceRequest(aggregationState, request, response);
139         }
140 
141         // TODO don't make it a dead end
142         //      currently we can't process the chain because there is no content/nop servlet
143         // chain.doFilter(request, response);
144     }
145 
146     protected boolean isVisible(Node content, HttpServletRequest request, HttpServletResponse response, AggregationState aggregationState) {
147 
148         // if there's a channel set test if the content is excluded for the current channel
149         if (aggregationState.getChannel() != null) {
150             String currentChannel = aggregationState.getChannel().getName();
151             if (StringUtils.isNotEmpty(currentChannel) && !currentChannel.equalsIgnoreCase("all")) {
152                 ChannelVisibilityContentDecorator decorator = new ChannelVisibilityContentDecorator(currentChannel);
153                 return decorator.evaluateNode(content);
154             }
155         }
156 
157         return true;
158     }
159 
160     protected void render(Node content, String templateName, HttpServletResponse response) throws RenderException {
161 
162         TemplateDefinition templateDefinition;
163         try {
164             templateDefinition = templateDefinitionRegistry.getTemplateDefinition(templateName);
165         }
166         catch (RegistrationException e) {
167             throw new RenderException(e);
168         }
169 
170         ResponseOutputProvider out = null;
171         final String targetArea = MgnlContext.getAttribute("mgnlArea");
172         try {
173             if (StringUtils.isEmpty(targetArea)) {
174                 out = new ResponseOutputProvider(response);
175             } else if (!content.hasNode(targetArea)) {
176                 log.warn("The target area [~mgnlArea=" + targetArea + "~] on the page [{}] is not a node in the {} repository. " +
177                         "Rendering whole page.", content.getPath(), content.getSession().getWorkspace().getName());
178                 out = new ResponseOutputProvider(response);
179             } else {
180                 out = new FilteringResponseOutputProvider(response);
181             }
182         } catch (Exception e) {
183             log.error("Exception caught.", e);
184         }
185         renderingEngine.initListeners(out);
186         renderingEngine.render(content, templateDefinition, Collections.<String, Object> emptyMap(), out);
187     }
188 
189 
190     /**
191      * Get the requested resource and copy it to the ServletOutputStream, bit by bit.
192      * @param request HttpServletRequest as given by the servlet container
193      * @param response HttpServletResponse as given by the servlet container
194      * @throws IOException standard servlet exception
195      */
196     protected void handleResourceRequest(AggregationState aggregationState, HttpServletRequest request, HttpServletResponse response) throws IOException {
197 
198         final String resourceHandle = aggregationState.getHandle();
199 
200         log.debug("handleResourceRequest, resourceHandle=\"{}\"", resourceHandle);
201 
202         if (StringUtils.isNotEmpty(resourceHandle)) {
203 
204 
205             InputStream is = null;
206             try {
207                 Session session = MgnlContext.getJCRSession(aggregationState.getRepository());
208                 is = getNodedataAsStream(resourceHandle, session, response);
209                 if (null != is) {
210                     // don't reset any existing status code, see MAGNOLIA-2005
211                     // response.setStatus(HttpServletResponse.SC_OK);
212                     sendUnCompressed(is, response);
213                     IOUtils.closeQuietly(is);
214                     return;
215                 }
216             }
217             catch (IOException e) {
218                 // don't log at error level since tomcat tipically throws a
219                 // org.apache.catalina.connector.ClientAbortException if the user stops loading the page
220                 log.debug("Exception while dispatching resource " + e.getClass().getName() + ": " + e.getMessage(), e);
221                 return;
222             }
223             catch (Exception e) {
224                 log.error("Exception while dispatching resource  " + e.getClass().getName() + ": " + e.getMessage(), e);
225                 return;
226             }
227             finally {
228                 IOUtils.closeQuietly(is);
229             }
230         }
231         log.debug("Resource not found, redirecting request for [{}] to 404 URI", request.getRequestURI());
232 
233         if (!response.isCommitted()) {
234             response.sendError(HttpServletResponse.SC_NOT_FOUND);
235         }
236         else {
237             log.info("Unable to redirect to 404 page for {}, response is already committed", request.getRequestURI());
238         }
239 
240     }
241 
242     /**
243      * Send data as is.
244      * @param is Input stream for the resource
245      * @param response HttpServletResponse as received by the service method
246      * @throws IOException standard servlet exception
247      */
248     private void sendUnCompressed(InputStream is, HttpServletResponse response) throws IOException {
249         ServletOutputStream os = response.getOutputStream();
250         byte[] buffer = new byte[8192];
251         int read;
252         while ((read = is.read(buffer)) > 0) {
253             os.write(buffer, 0, read);
254         }
255         os.flush();
256         IOUtils.closeQuietly(os);
257     }
258 
259     private InputStream getNodedataAsStream(String path, Session session, HttpServletResponse res) {
260 
261         log.debug("getNodedataAstream for path \"{}\"", path);
262 
263         try {
264             Node atom = session.getNode(path);
265             if (atom != null) {
266                 if (atom.hasProperty(JcrConstants.JCR_DATA)) {
267                     Property sizeProperty = atom.getProperty("size");
268                     String sizeString = sizeProperty == null ? "" : sizeProperty.getString();
269                     if (NumberUtils.isNumber(sizeString)) {
270                         res.setContentLength(Integer.parseInt(sizeString));
271                     }
272                     Property streamProperty = atom.getProperty(JcrConstants.JCR_DATA);
273                     return streamProperty.getStream();
274                 }
275             }
276 
277             log.warn("Resource not found: [{}]", path);
278         }
279         catch (RepositoryException e) {
280             log.error("RepositoryException while reading Resource [" + path + "]", e);
281         }
282         return null;
283     }
284 
285 }