View Javadoc

1   package org.apache.velocity.tools.view;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.IOException;
23  import java.io.StringWriter;
24  import javax.servlet.ServletConfig;
25  import javax.servlet.ServletException;
26  import javax.servlet.http.HttpServletRequest;
27  import javax.servlet.http.HttpServletResponse;
28  import org.apache.velocity.Template;
29  import org.apache.velocity.context.Context;
30  import org.apache.velocity.exception.MethodInvocationException;
31  
32  /**
33   * Extension of the VelocityViewServlet to perform "two-pass"
34   * layout rendering and allow for a customized error screen.
35   *
36   * @author Nathan Bubna
37   * @version $Id: VelocityLayoutServlet.java 823236 2009-10-08 17:24:48Z nbubna $
38   */
39  public class VelocityLayoutServlet extends VelocityViewServlet
40  {
41  
42      /** serial version id */
43      private static final long serialVersionUID = -4521817395157483487L;
44  
45      /**
46       * The velocity.properties key for specifying the
47       * servlet's error template.
48       */
49      public static final String PROPERTY_ERROR_TEMPLATE =
50          "tools.view.servlet.error.template";
51  
52      /**
53       * The velocity.properties key for specifying the
54       * relative directory holding layout templates.
55       */
56      public static final String PROPERTY_LAYOUT_DIR =
57          "tools.view.servlet.layout.directory";
58  
59      /**
60       * The velocity.properties key for specifying the
61       * servlet's default layout template's filename.
62       */
63      public static final String PROPERTY_DEFAULT_LAYOUT =
64          "tools.view.servlet.layout.default.template";
65  
66  
67      /**
68       * The default error template's filename.
69       */
70      public static final String DEFAULT_ERROR_TEMPLATE = "Error.vm";
71  
72      /**
73       * The default layout directory
74       */
75      public static final String DEFAULT_LAYOUT_DIR = "layout/";
76  
77      /**
78       * The default filename for the servlet's default layout
79       */
80      public static final String DEFAULT_DEFAULT_LAYOUT = "Default.vm";
81  
82  
83      /**
84       * The context key that will hold the content of the screen.
85       *
86       * This key ($screen_content) must be present in the layout
87       * template for the current screen to be rendered.
88       */
89      public static final String KEY_SCREEN_CONTENT = "screen_content";
90  
91      /**
92       * The context/parameter key used to specify an alternate
93       * layout to be used for a request instead of the default layout.
94       */
95      public static final String KEY_LAYOUT = "layout";
96  
97  
98      /**
99       * The context key that holds the {@link Throwable} that
100      * broke the rendering of the requested screen.
101      */
102     public static final String KEY_ERROR_CAUSE = "error_cause";
103 
104     /**
105      * The context key that holds the stack trace of the error that
106      * broke the rendering of the requested screen.
107      */
108     public static final String KEY_ERROR_STACKTRACE = "stack_trace";
109 
110     /**
111      * The context key that holds the {@link MethodInvocationException}
112      * that broke the rendering of the requested screen.
113      *
114      * If this value is placed in the context, then $error_cause
115      * will hold the error that this invocation exception is wrapping.
116      */
117     public static final String KEY_ERROR_INVOCATION_EXCEPTION = "invocation_exception";
118 
119 
120     protected String errorTemplate;
121     protected String layoutDir;
122     protected String defaultLayout;
123 
124     /**
125      * Initializes Velocity, the view servlet and checks for changes to
126      * the initial layout configuration.
127      *
128      * @param config servlet configuration parameters
129      */
130     public void init(ServletConfig config) throws ServletException
131     {
132         // first do VVS' init()
133         super.init(config);
134 
135         // check for default template path overrides
136         errorTemplate =
137             getVelocityProperty(PROPERTY_ERROR_TEMPLATE, DEFAULT_ERROR_TEMPLATE);
138         layoutDir =
139             getVelocityProperty(PROPERTY_LAYOUT_DIR, DEFAULT_LAYOUT_DIR);
140         defaultLayout =
141             getVelocityProperty(PROPERTY_DEFAULT_LAYOUT, DEFAULT_DEFAULT_LAYOUT);
142 
143         // preventive error checking! directory must end in /
144         if (!layoutDir.endsWith("/"))
145         {
146             layoutDir += '/';
147         }
148 
149         // log the current settings
150         getLog().info("VelocityLayoutServlet: Error screen is '"+errorTemplate+"'");
151         getLog().info("VelocityLayoutServlet: Layout directory is '"+layoutDir+"'");
152         getLog().info("VelocityLayoutServlet: Default layout template is '"+defaultLayout+"'");
153 
154         // for efficiency's sake, make defaultLayout a full path now
155         defaultLayout = layoutDir + defaultLayout;
156     }
157 
158 
159     /**
160      * Overrides VelocityViewServlet to check the request for
161      * an alternate layout
162      *
163      * @param ctx context for this request
164      * @param request client request
165      */
166     protected void fillContext(Context ctx, HttpServletRequest request)
167     {
168         String layout = findLayout(request);
169         if (layout != null)
170         {
171             // let the template know what its new layout is
172             ctx.put(KEY_LAYOUT, layout);
173         }
174     }
175 
176     /**
177      * Searches for a non-default layout to be used for this request.
178      * This implementation checks the request parameters and attributes.
179      */
180     protected String findLayout(HttpServletRequest request)
181     {
182         // check if an alternate layout has been specified
183         // by way of the request parameters
184         String layout = request.getParameter(KEY_LAYOUT);
185         // also look in the request attributes 
186         if (layout == null) 
187         { 
188             layout = (String)request.getAttribute(KEY_LAYOUT); 
189         }
190         return layout;
191     }
192 
193     /**
194      * Overrides VelocityViewServlet.mergeTemplate to do a two-pass
195      * render for handling layouts
196      */
197     protected void mergeTemplate(Template template, Context context,
198                                  HttpServletResponse response)
199         throws IOException
200     {
201         //
202         // this section is based on Tim Colson's "two pass render"
203         //
204         // Render the screen content
205         StringWriter sw = new StringWriter();
206         template.merge(context, sw);
207         // Add the resulting content to the context
208         context.put(KEY_SCREEN_CONTENT, sw.toString());
209 
210         // Check for an alternate layout
211         //
212         // we check after merging the screen template so the screen
213         // can overrule any layout set in the request parameters
214         // by doing #set( $layout = "MyLayout.vm" )
215         Object obj = context.get(KEY_LAYOUT);
216         String layout = (obj == null) ? null : obj.toString();
217         if (layout == null)
218         {
219             // no alternate, use default
220             layout = defaultLayout;
221         }
222         else
223         {
224             // make it a full(er) path
225             layout = layoutDir + layout;
226         }
227 
228         try
229         {
230             //load the layout template
231             template = getTemplate(layout);
232         }
233         catch (Exception e)
234         {
235             getLog().error("Can't load layout \"" + layout + "\"", e);
236 
237             // if it was an alternate layout we couldn't get...
238             if (!layout.equals(defaultLayout))
239             {
240                 // try to get the default layout
241                 // if this also fails, let the exception go
242                 template = getTemplate(defaultLayout);
243             }
244         }
245 
246         // Render the layout template into the response
247         super.mergeTemplate(template, context, response);
248     }
249 
250 
251     /**
252      * Overrides VelocityViewServlet to display user's custom error template
253      */
254     protected void error(HttpServletRequest request,
255                          HttpServletResponse response,
256                          Throwable e)
257     {
258         try
259         {
260             // get a velocity context
261             Context ctx = createContext(request, response);
262 
263             Throwable cause = e;
264 
265             // if it's an MIE, i want the real cause and stack trace!
266             if (cause instanceof MethodInvocationException)
267             {
268                 // put the invocation exception in the context
269                 ctx.put(KEY_ERROR_INVOCATION_EXCEPTION, e);
270                 // get the real cause
271                 cause = ((MethodInvocationException)e).getWrappedThrowable();
272             }
273 
274             // add the cause to the context
275             ctx.put(KEY_ERROR_CAUSE, cause);
276 
277             // grab the cause's stack trace and put it in the context
278             StringWriter sw = new StringWriter();
279             cause.printStackTrace(new java.io.PrintWriter(sw));
280             ctx.put(KEY_ERROR_STACKTRACE, sw.toString());
281 
282             // retrieve and render the error template
283             Template et = getTemplate(errorTemplate);
284             mergeTemplate(et, ctx, response);
285 
286         }
287         catch (Exception e2)
288         {
289             // d'oh! log this
290             getLog().error("Error during error template rendering", e2);
291             // then punt the original to a higher authority
292             super.error(request, response, e);
293         }
294     }
295 
296 
297 }