View Javadoc

1   /*
2    * Copyright (c) 2005-2007 Creative Sphere Limited.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v10.html
7    *
8    * Contributors:
9    *
10   *   Creative Sphere - initial API and implementation
11   *
12   */
13  package org.abstracthorizon.danube.velocity;
14  
15  import org.abstracthorizon.danube.connection.Connection;
16  import org.abstracthorizon.danube.connection.ConnectionException;
17  import org.abstracthorizon.danube.http.HTTPConnection;
18  import org.abstracthorizon.danube.mvc.ModelAndView;
19  import org.abstracthorizon.danube.mvc.View;
20  import org.abstracthorizon.danube.support.InitializationException;
21  import org.abstracthorizon.danube.support.URLUtils;
22  
23  import java.io.File;
24  import java.io.FileNotFoundException;
25  import java.io.IOException;
26  import java.io.Writer;
27  import java.net.URL;
28  import java.util.Map;
29  
30  import org.apache.velocity.Template;
31  import org.apache.velocity.VelocityContext;
32  import org.apache.velocity.app.Velocity;
33  import org.apache.velocity.context.Context;
34  import org.apache.velocity.io.VelocityWriter;
35  import org.apache.velocity.runtime.RuntimeConstants;
36  import org.apache.velocity.runtime.RuntimeSingleton;
37  import org.apache.velocity.servlet.VelocityServlet;
38  import org.apache.velocity.util.SimplePool;
39  
40  /**
41   * <p>
42   *   This is implementation of {@link org.abstracthorizon.danube.mvc.View}
43   * that uses Velocity template engine. Model map is used as map of parameters for
44   * Velocity.
45   * </p>
46   * <p>
47   *   This implementation is based on {@link VelocityServlet}
48   * </p>
49   *
50   * @author Daniel Sendula
51   */
52  public class VelocityViewAdapter implements View {
53  
54      /** The HTTP content type context key. */
55      public static final String CONTENT_TYPE = "default.contentType";
56  
57      /** The default content type for the response */
58      public static final String DEFAULT_CONTENT_TYPE = "text/html";
59  
60      /** Default encoding for the output stream */
61      public static final String DEFAULT_OUTPUT_ENCODING = "ISO-8859-1";
62  
63      /** Cache of writers */
64      private static SimplePool writerPool = new SimplePool(40);
65  
66      /** Path where templates are stored */
67      protected URL templatesURL;
68  
69      /** Path where templates are stored */
70      protected File templatesPath;
71  
72      /** Template suffix */
73      protected String suffix;
74  
75      /**
76       * The default content type.  When necessary, includes the
77       * character set to use when encoding textual output.
78       */
79      protected String contentType;
80  
81      /**
82       * Constructor.
83       * @throws Exception
84       */
85      public VelocityViewAdapter() throws Exception {
86      }
87  
88      /**
89       * This metod initialises Velocity engine.
90       * @throws Exception
91       */
92      public void init() throws InitializationException {
93          contentType = RuntimeSingleton.getString(CONTENT_TYPE, DEFAULT_CONTENT_TYPE);
94          String encoding = RuntimeSingleton.getString(RuntimeConstants.OUTPUT_ENCODING, DEFAULT_OUTPUT_ENCODING);
95          // For non Latin-1 encodings, ensure that the charset is
96          // included in the Content-Type header.
97          if (!DEFAULT_OUTPUT_ENCODING.equalsIgnoreCase(encoding)) {
98              int index = contentType.lastIndexOf("charset");
99              if (index < 0) {
100                 // the charset specifier is not yet present in header.
101                 // append character encoding to default content-type
102                 contentType += "; charset=" + encoding;
103             } else {
104                 // The user may have configuration issues.
105                 Velocity.warn("VelocityViewServlet: Charset was already " + "specified in the Content-Type property.  "
106                         + "Output encoding property will be ignored.");
107             }
108         }
109         Velocity.info("VelocityViewServlet: Default content-type is: " + contentType);
110 
111         Velocity.setProperty(RuntimeConstants.RESOURCE_LOADER, "file");
112         Velocity.setProperty("file.resource.loader.class", AbsoluteFileResourceLoader.class.getName());
113         //Velocity.setProperty("file.resource.loader.path", "/");
114         Velocity.setProperty("file.resource.loader.path", "");
115 
116         try {
117             Velocity.init();
118         } catch (Exception e) {
119             throw new InitializationException(e);
120         }
121     }
122 
123     /**
124      * This method renders output
125      * @param connection http connection
126      * @param modelAndView view name to be used as template's name and model map to be used as attributes
127      * throws ConnectionException
128      */
129     public void render(Connection connection, ModelAndView modelAndView) throws ConnectionException {
130         try {
131             Context context = createContext(modelAndView.getModel());
132 
133             Template template = findTemplate(context, modelAndView.getView());
134 
135             if (template == null) {
136                 Velocity.warn("VelocityViewServlet: couldn't find template to match request.");
137                 return;
138             }
139             // merge the template and context
140             HTTPConnection httpConnection = (HTTPConnection)connection.adapt(HTTPConnection.class);
141             mergeTemplate(template, context, httpConnection);
142         } catch (IOException ioException) {
143             throw new ConnectionException(ioException);
144         } catch (Exception exception) {
145             throw new ConnectionException(exception);
146         }
147     }
148 
149     /**
150      * This method finds template based on view name and &quot;.vm&quot; file extension.
151      * Template is located in provided {@link #templatesLocation}
152      * @param ctx velocity context
153      * @param templateFile
154      * @return template
155      * @throws Exception
156      */
157     protected Template findTemplate(Context ctx, String view) throws Exception {
158         if (templatesPath != null) {
159             File file = new File(templatesPath, view + getSuffix());
160             return RuntimeSingleton.getTemplate(file.getAbsolutePath());
161         }
162 
163         if (templatesURL != null) {
164             URL newURL = URLUtils.addPath(templatesURL, view + getSuffix());
165             return RuntimeSingleton.getTemplate(newURL.toString());
166         }
167 
168         throw new FileNotFoundException("One of templatesPath or templatesURL must be set.");
169     }
170 
171 
172     /**
173      * Creates context based on provided model's map
174      * @param attributes model's map
175      * @return velocity context
176      */
177     @SuppressWarnings("unchecked")
178     protected Context createContext(Map attributes) {
179         VelocityContext ctx = new VelocityContext(attributes);
180         return ctx;
181     }
182 
183     /**
184      * Generates output from given template
185      * @param template template
186      * @param context velocity context
187      * @param connection http connection result to be rendered to
188      * @throws Exception
189      */
190     protected void mergeTemplate(Template template, Context context, HTTPConnection connection) throws Exception {
191         connection.getResponseHeaders().putOnly("Content-Type", contentType);
192 
193         VelocityWriter vw = null;
194         Writer writer = (Writer)connection.adapt(Writer.class);
195         try {
196             vw = (VelocityWriter) writerPool.get();
197             if (vw == null) {
198                 vw = new VelocityWriter(writer, 4 * 1024, true);
199             } else {
200                 vw.recycle(writer);
201             }
202             template.merge(context, vw);
203         } finally {
204             if (vw != null) {
205                 try {
206                     // flush and put back into the pool
207                     // don't close to allow us to play
208                     // nicely with others.
209                     vw.flush();
210                     /* This hack sets the VelocityWriter's internal ref to the
211                      * PrintWriter to null to keep memory free while
212                      * the writer is pooled. See bug report #18951 */
213                     vw.recycle(null);
214                     writerPool.put(vw);
215                 } catch (Exception e) {
216                     Velocity.debug("VelocityViewServlet: " + "Trouble releasing VelocityWriter: " + e.getMessage());
217                 }
218             }
219         }
220     }
221 
222     /**
223      * Returns path where templates are stored
224      * @return Returns the templatePath.
225      */
226     public File getTemplatesPath() {
227         return templatesPath;
228     }
229 
230     /**
231      * Sets path where templates are stored
232      * @param templatesPath path where templates are stored.
233      */
234     public void setTemplatesPath(File templatesPath) {
235         this.templatesPath = templatesPath;
236     }
237 
238     /**
239      * Returns URL where templates are stored
240      * @return returns the templates URL
241      */
242     public URL getTemplatesURL() {
243         return templatesURL;
244     }
245 
246     /**
247      * Sets URL where templates are stored
248      * @param templatesURL URL where templates are stored.
249      */
250     public void setTemplatesURL(URL templatesURL) {
251         this.templatesURL = templatesURL;
252         if (templatesURL.getProtocol().equals("file")) {
253             templatesPath = new File(templatesURL.getFile());
254         }
255     }
256 
257     /**
258      * Returns default content type
259      * @return default content type
260      */
261     public String getContentType() {
262         return contentType;
263     }
264 
265     /**
266      * Sets default content type
267      * @param contentType default content type
268      */
269     public void setContentType(String contentType) {
270         this.contentType = contentType;
271     }
272 
273     /**
274      * Returns template suffix. If not set it is then initialised to &quot;.vm&quot;
275      * @return template suffix
276      */
277     public String getSuffix() {
278         if (suffix == null) {
279             suffix = ".vm";
280         }
281         return suffix;
282     }
283 
284     /**
285      * Sets suffix of templates
286      * @param suffix suffix of templates
287      */
288     public void setSuffix(String suffix) {
289         this.suffix = suffix;
290     }
291 }