001: /*
002: * JFox - The most lightweight Java EE Application Server!
003: * more details please visit http://www.huihoo.org/jfox or http://www.jfox.org.cn.
004: *
005: * JFox is licenced and re-distributable under GNU LGPL.
006: */
007: package org.jfox.mvc.velocity;
008:
009: import java.io.File;
010: import java.io.IOException;
011: import java.io.InputStream;
012: import java.io.OutputStreamWriter;
013: import java.io.PrintWriter;
014: import java.io.StringWriter;
015: import java.util.HashMap;
016: import java.util.Map;
017: import java.util.Properties;
018: import javax.servlet.ServletConfig;
019: import javax.servlet.ServletException;
020: import javax.servlet.ServletOutputStream;
021: import javax.servlet.http.HttpServletRequest;
022: import javax.servlet.http.HttpServletResponse;
023:
024: import org.apache.velocity.Template;
025: import org.apache.velocity.VelocityContext;
026: import org.apache.velocity.app.Velocity;
027: import org.apache.velocity.app.VelocityEngine;
028: import org.apache.velocity.app.event.EventCartridge;
029: import org.apache.velocity.app.event.implement.IncludeRelativePath;
030: import org.apache.velocity.context.Context;
031: import org.apache.velocity.exception.ParseErrorException;
032: import org.apache.velocity.io.VelocityWriter;
033: import org.apache.velocity.runtime.RuntimeSingleton;
034: import org.apache.velocity.util.SimplePool;
035: import org.jfox.mvc.ActionContext;
036: import org.jfox.mvc.PageContext;
037: import org.jfox.mvc.Render;
038: import org.jfox.mvc.WebContextLoader;
039: import org.jfox.mvc.servlet.ControllerServlet;
040:
041: /**
042: * Velocity Render
043: *
044: * handle .vm .vhtml .vhtm .tmpl
045: *
046: * @author <a href="mailto:jfox.young@gmail.com">Young Yang</a>
047: */
048: public class VelocityRender implements Render {
049:
050: public final static String VELOCITY_PROPERTIES = "velocity.properties";
051: private String inputEncoding = "UTF-8";
052: private String outputEncoding = "UTF-8";
053:
054: /**
055: * �个 Module 对应的 VelocityEngine
056: * module view base dir => module's velocity engine
057: */
058: private static Map<String, VelocityEngine> baseDir2VelocityEngineMap = new HashMap<String, VelocityEngine>();
059:
060: /**
061: * The HTTP content type context key.
062: */
063: public static final String CONTENT_TYPE = "default.contentType";
064:
065: /**
066: * The default content type for the response
067: */
068: public static final String DEFAULT_CONTENT_TYPE = "text/html";
069:
070: /**
071: * Encoding for the output stream
072: */
073: public static final String DEFAULT_OUTPUT_ENCODING = "ISO-8859-1";
074:
075: /**
076: * The default content type, itself defaulting to {@link
077: * #DEFAULT_CONTENT_TYPE} if not configured.
078: */
079: private static String defaultContentType;
080:
081: /**
082: * Cache of writers
083: */
084: private final static SimplePool writerPool = new SimplePool(40);
085:
086: public static final EventCartridge ec = new EventCartridge();
087: static {
088: //#include #parse 支�相对路径
089: ec.addEventHandler(new IncludeRelativePath());
090: }
091:
092: /**
093: * Performs initialization of this servlet. Called by the servlet
094: * container on loading.
095: *
096: * @param config The servlet configuration to apply.
097: * @throws javax.servlet.ServletException
098: */
099: public void init(ServletConfig config) throws ServletException {
100: /*
101: * do whatever we have to do to init Velocity
102: */
103: initVelocity(config);
104:
105: /*
106: * Now that Velocity is initialized, cache some config.
107: */
108: defaultContentType = RuntimeSingleton.getString(CONTENT_TYPE,
109: DEFAULT_CONTENT_TYPE);
110: }
111:
112: protected void initVelocity(ServletConfig servletConfig)
113: throws ServletException {
114: try {
115: for (String moduleDirName : WebContextLoader
116: .getModuleDirNames()) {
117: String modulePath = WebContextLoader
118: .getModulePathByModuleDirName(moduleDirName);
119: File moduleDir = WebContextLoader
120: .getModuleDirByModuleDirName(moduleDirName);
121: String templateBaseDir = moduleDir.getCanonicalFile()
122: .getAbsoluteFile().getPath()
123: + "/" + ControllerServlet.getViewDir();
124: Properties p = loadConfiguration(servletConfig);
125: // use loader path by module
126: p.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH,
127: templateBaseDir);
128: // overwrite velocity default avalog log system
129: p.setProperty(Velocity.RUNTIME_LOG_LOGSYSTEM_CLASS,
130: Log4jLogSystem.class.getName());
131:
132: //overwrite encoding by ControllerServet
133: p.setProperty(Velocity.INPUT_ENCODING,
134: ControllerServlet.DEFAULT_ENCODING);
135: p.setProperty(Velocity.OUTPUT_ENCODING,
136: ControllerServlet.DEFAULT_ENCODING);
137: inputEncoding = p.getProperty(Velocity.INPUT_ENCODING);
138: outputEncoding = p
139: .getProperty(Velocity.OUTPUT_ENCODING);
140:
141: VelocityEngine ve = new VelocityEngine();
142: // EventCartridge ec = new EventCartridge();
143: ve.init(p);
144: // 注册相对 module template base dirï¼Œä»¥ä¾¿æ ¹æ?®è®¿é—®çš„ URL,æ?¥åˆ¤æ–访问的Module,获得 VelocityEngine
145: baseDir2VelocityEngineMap.put(modulePath + "/"
146: + ControllerServlet.getViewDir(), ve);
147: }
148: } catch (Exception e) {
149: throw new ServletException("Error initializing Velocity: "
150: + e, e);
151: }
152: }
153:
154: /**
155: * 从 velocity.properties ä¸è£…è½½ velocity çš„é…?ç½®
156: *
157: * @param config servlet config
158: * @throws IOException io exception
159: */
160: protected Properties loadConfiguration(ServletConfig config)
161: throws IOException {
162: InputStream in = this .getClass().getClassLoader()
163: .getResourceAsStream(VELOCITY_PROPERTIES);
164: Properties p = new Properties();
165: p.load(in);
166: in.close();
167: return p;
168: }
169:
170: public void render(HttpServletRequest request,
171: HttpServletResponse response) throws Exception {
172: Context context = null;
173: try {
174: /*
175: * first, get a context
176: */
177:
178: context = createVelocityContext(request, response);
179:
180: /*
181: * set the content type
182: */
183:
184: setContentType(request, response);
185:
186: /*
187: * let someone handle the request
188: */
189:
190: Template template = createTemplate(request, response,
191: context);
192: /*
193: * bail if we can't find the template
194: */
195:
196: if (template == null) {
197: return;
198: }
199:
200: /*
201: * now merge it
202: */
203:
204: mergeTemplate(template, context, response);
205: } catch (Exception e) {
206: /*
207: * call the error handler to let the derived class
208: * do something useful with this failure.
209: */
210:
211: error(request, response, e);
212: } finally {
213: /*
214: * call cleanup routine to let a derived class do some cleanup
215: */
216:
217: afterRender(request, response, context);
218: }
219:
220: }
221:
222: /**
223: * create a valid Template for velocity
224: * </p>
225: *
226: * @param request http request
227: * @param response http response
228: * @param velocityContext a Velocity Context object to be filled with data. Will be used
229: * for rendering this template
230: * @return Template to be used for request
231: * @throws Exception exception
232: */
233: protected Template createTemplate(HttpServletRequest request,
234: HttpServletResponse response, Context velocityContext)
235: throws Exception {
236: String servletPath = request.getServletPath();
237: return getTemplate(servletPath, inputEncoding);
238: }
239:
240: /**
241: * 返回该 url 创建的 Template
242: *
243: * @param servletPath servlet url
244: * @param encoding encoding
245: * @throws Exception exception
246: */
247: protected Template getTemplate(String servletPath, String encoding)
248: throws Exception {
249: // 首先è¦?æ ¹æ?® servletPath 获得所访问的 Module,进而得到 Module çš„ VelocityEngine,用 startsWith 判æ–?
250: String templateName = null;
251: VelocityEngine engine = null;
252: for (Map.Entry<String, VelocityEngine> entry : baseDir2VelocityEngineMap
253: .entrySet()) {
254: String baseDir = entry.getKey();
255: VelocityEngine ve = entry.getValue();
256: int index = servletPath.indexOf(baseDir);
257: if (index >= 0) {
258: engine = ve;
259: templateName = servletPath.substring(index
260: + baseDir.length());
261: }
262: }
263: if (engine == null) {
264: throw new ParseErrorException(
265: "Can not found velocity engine for servlet path: "
266: + servletPath);
267: }
268:
269: return engine.getTemplate(templateName, encoding);
270: }
271:
272: /**
273: * merges the template with the context. Only override this if you really, really
274: * really need to. (And don't call us with questions if it breaks :)
275: *
276: * @param template template object returned by the handleRequest() method
277: * @param context context created by the createContext() method
278: * @param response servlet reponse (use this to get the output stream or Writer
279: * @throws Exception exception
280: */
281: protected void mergeTemplate(Template template, Context context,
282: HttpServletResponse response) throws Exception {
283: ServletOutputStream output = response.getOutputStream();
284: // ASSUMPTION: response.setContentType() has been called.
285: String encoding = response.getCharacterEncoding();
286:
287: VelocityWriter vw = null;
288: try {
289: vw = (VelocityWriter) writerPool.get();
290:
291: if (vw == null) {
292: vw = new VelocityWriter(new OutputStreamWriter(output,
293: encoding), 4 * 1024, true);
294: } else {
295: vw.recycle(new OutputStreamWriter(output, encoding));
296: }
297:
298: template.merge(context, vw);
299: } finally {
300: try {
301: if (vw != null) {
302: /*
303: * flush and put back into the pool
304: * don't close to allow us to play
305: * nicely with others.
306: */
307: vw.flush();
308:
309: /*
310: * Clear the VelocityWriter's reference to its
311: * internal OutputStreamWriter to allow the latter
312: * to be GC'd while vw is pooled.
313: */
314: vw.recycle(null);
315:
316: writerPool.put(vw);
317: }
318: } catch (Exception e) {
319: // do nothing
320: }
321: }
322: }
323:
324: /**
325: * Sets the content type of the response, defaulting to {@link
326: * #defaultContentType} if not overriden.
327: *
328: * @param request The servlet request from the client.
329: * @param response The servlet reponse to the client.
330: */
331: protected void setContentType(HttpServletRequest request,
332: HttpServletResponse response) {
333: String contentType = defaultContentType;
334: int index = contentType.lastIndexOf(';') + 1;
335: if (index <= 0
336: || (index < contentType.length() && contentType
337: .indexOf("charset", index) == -1)) {
338: // Append the character encoding which we'd like to use.
339: String encoding = outputEncoding;
340: //System.out.println("Chose output encoding of '" +
341: // encoding + '\'');
342: if (!DEFAULT_OUTPUT_ENCODING.equalsIgnoreCase(encoding)) {
343: contentType += "; charset=" + encoding;
344: }
345: }
346: response.setContentType(contentType);
347: //System.out.println("Response Content-Type set to '" +
348: // contentType + '\'');
349: }
350:
351: /**
352: * Invoked when there is an error thrown in any part of doRequest() processing.
353: * <br><br>
354: * Default will send a simple HTML response indicating there was a problem.
355: *
356: * @param request original HttpServletRequest from servlet container.
357: * @param response HttpServletResponse object from servlet container.
358: * @param cause Exception that was thrown by some other part of process.
359: * @throws IOException io exception
360: * @throws ServletException servlet exception
361: */
362: protected void error(HttpServletRequest request,
363: HttpServletResponse response, Exception cause)
364: throws ServletException, IOException {
365: StringBuffer html = new StringBuffer();
366: html.append("<html>");
367: html.append("<title>Error</title>");
368: html.append("<body bgcolor=\"#ffffff\">");
369: html
370: .append("<h2>VelocityServlet: Error processing the template</h2>");
371: html.append("<pre>");
372: String why = cause.getMessage();
373: if (why != null && why.trim().length() > 0) {
374: html.append(why);
375: html.append("<br>");
376: }
377:
378: StringWriter sw = new StringWriter();
379: cause.printStackTrace(new PrintWriter(sw));
380:
381: html.append(sw.toString());
382: html.append("</pre>");
383: html.append("</body>");
384: html.append("</html>");
385: response.getOutputStream().print(html.toString());
386: }
387:
388: /**
389: * create VelocityContext and set key/value
390: *
391: * @param request http request
392: * @param response http response
393: */
394: protected Context createVelocityContext(HttpServletRequest request,
395: HttpServletResponse response) {
396: ActionContext actionContext = (ActionContext) request
397: .getAttribute(ControllerServlet.INVOCATION_CONTEXT);
398: PageContext pageContext = actionContext.getPageContext();
399: VelocityContext velocityContext = new VelocityContext(
400: pageContext.getResultMap());
401: // 使用相对路径
402: ec.attachToContext(velocityContext);
403: return velocityContext;
404: }
405:
406: protected void afterRender(HttpServletRequest request,
407: HttpServletResponse response, Context velocityContext) {
408:
409: }
410:
411: public static void main(String[] args) {
412:
413: }
414: }
|