001: /*
002: * Copyright (c) 2002-2006 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.webwork.views.sitemesh;
006:
007: import java.io.BufferedReader;
008: import java.io.IOException;
009: import java.io.InputStreamReader;
010: import java.io.Writer;
011: import java.net.URL;
012: import java.util.Locale;
013:
014: import javax.servlet.ServletConfig;
015: import javax.servlet.ServletContext;
016: import javax.servlet.ServletException;
017: import javax.servlet.http.HttpServletRequest;
018: import javax.servlet.http.HttpServletResponse;
019:
020: import com.opensymphony.module.sitemesh.Config;
021: import com.opensymphony.module.sitemesh.Decorator;
022: import com.opensymphony.module.sitemesh.Factory;
023: import com.opensymphony.module.sitemesh.HTMLPage;
024: import com.opensymphony.module.sitemesh.Page;
025: import com.opensymphony.module.sitemesh.PageParser;
026: import com.opensymphony.module.sitemesh.filter.Buffer;
027: import com.opensymphony.webwork.WebWorkException;
028: import com.opensymphony.webwork.components.Component;
029: import com.opensymphony.webwork.views.JspSupportServlet;
030: import com.opensymphony.webwork.views.freemarker.FreemarkerManager;
031: import com.opensymphony.xwork.ActionContext;
032: import com.opensymphony.xwork.ActionInvocation;
033: import com.opensymphony.xwork.LocaleProvider;
034: import com.opensymphony.xwork.util.OgnlValueStack;
035:
036: import freemarker.template.Configuration;
037: import freemarker.template.ObjectWrapper;
038: import freemarker.template.SimpleHash;
039: import freemarker.template.Template;
040: import freemarker.template.TemplateException;
041:
042: /**
043: * <!-- START SNIPPET: javadoc -->
044: *
045: * This is the WebWork component that implements Freemarker's ApplyDecorator
046: * Transform. To use this Freemarker Transform, it needs to be enabled in
047: * webwork.properties (which is enabled by default)
048: *
049: * <pre>
050: * webwork.freemarker.sitemesh.applyDecoratorTransform = true
051: * </pre>
052: *
053: * An example of usage would be as follows:- <p/>
054: *
055: * In Sitemesh's decorators.xml
056: * <pre>
057: * <decorators defaultdir="/WEB-INF/decorators">
058: * ....
059: * <decorator name="panel" page="/panelDecorator.ftl" />
060: * </decorators*gt;
061: * </pre>
062: *
063: * Decorator (panelDecorator.ftl)<p/>
064: * <pre>
065: * <table border="1">
066: * <tr>
067: * <td>${title}</td>
068: * </tr>
069: * <tr>
070: * <td>${body}</td>
071: * </tr>
072: * </table>
073: * </pre>
074: *
075: * Freemarker page that uses decorator <p/>
076: * <pre>
077: * <html>
078: * <head>
079: * <title>some title</title>
080: * </head>
081: * <body>
082: * <h1>some body title</h1>
083: * <@sitemesh.applydecorator name="panel" page="/pages/pageToBeDecorated.ftl" />
084: * </body>
085: * </html>
086: * </pre>
087: *
088: *
089: * An example of pageToBeDecorated.ftl<p/>
090: * <pre>
091: * <html>
092: * <head>
093: * <title>Panel Title</title>
094: * </head>
095: * <body>
096: * Panel Content
097: * </body>
098: * </html>
099: * </pre>
100: *
101: *
102: * The nett outcome would be:-
103: * <pre>
104: * <html>
105: * <title>some title&l/title>
106: * <body>
107: * <h1>some body title</h1>
108: * <table border="1">
109: * <tr>
110: * <td>Panel Title</td>
111: * </tr>
112: * <tr>
113: * <td>Panel Content</td>
114: * </tr>
115: * </table>
116: * </body>
117: * </html>
118: * </pre>
119: *
120: * The following are method hooks available to ApplyDecoratorBean and its subclass
121: * <ul>
122: * <li>getFreemarkerTemplate(String templatePath) - create a Freemarker Template based on the template path given </li>
123: * <li>parsePageFromContent(String content) - returns a Sitemesh Page object based on the content as the to-be-decorated-page</li>
124: * <li>parsePageFromAbsoluteUrl(String absoluteUrl) - returns a Sitemesh Page object using the absoluteUrl to get the content of the to-be-decorated-page</li>
125: * <li>parsePageFromRelativeUrlPath(String relativeUrl) - returns a Sitemesh Page object using the relativeUrl to get the content of the to-be-decorated-page.</li>
126: * <li>getSitemeshFactory() - returns a Sitemesh Factory object</li>
127: * <li>getPageParser(String contentType) - returns a Sitemesh PageParser object</li>
128: * <li>getDecorator(HttpServletRequest request, String decoratorName) - returns a Sitemesh Decorator object with the decoratorName supplied.</li>
129: * <li>deduceLocale(ActionInvocation invocation, Configuration freemarkerConfiguration) - deduce the Locale from invocation else use the Locale supplied by Freemarker</li>
130: * <li>createModel() - create a Freemarker model for the template</li>
131: * </ul>
132: *
133: * <!-- END SNIPPET: javadoc -->
134: *
135: * @author tmjee
136: * @version $Date$ $Id$
137: *
138: * @see SitemeshModel
139: * @see FreemarkerManager#buildTemplateModel(OgnlValueStack, Object,
140: * ServletContext, HttpServletRequest, HttpServletResponse, ObjectWrapper)
141: */
142: public class ApplyDecoratorBean extends Component {
143:
144: private HttpServletRequest request;
145: private HttpServletResponse response;
146:
147: private String name;
148: private String page;
149: private String contentType;
150: private String encoding;
151:
152: /**
153: * @param stack
154: */
155: public ApplyDecoratorBean(OgnlValueStack stack,
156: HttpServletRequest request, HttpServletResponse response) {
157: super (stack);
158: this .request = request;
159: this .response = response;
160: }
161:
162: /**
163: * Set the name of the Sitemesh decorator to be used.
164: * @return String
165: */
166: public String getName() {
167: return this .name;
168: }
169:
170: public void setName(String name) {
171: this .name = name;
172: }
173:
174: /**
175: * Set the page to be decorated. This could be either an
176: * <ul>
177: * <li>absolute url eg. www.google.com</li>
178: * <li>relative url eg /page/pageToBeDecorated.ftl</li>
179: * </ul>
180: * @return String
181: */
182: public String getPage() {
183: return this .page;
184: }
185:
186: public void setPage(String page) {
187: this .page = page;
188: }
189:
190: /**
191: * The content type, default to "text/html".
192: * @return String
193: */
194: public String getContentType() {
195: return this .contentType;
196: }
197:
198: public void setContentType(String contentType) {
199: this .contentType = contentType;
200: }
201:
202: /**
203: * Set the encoding eg. "UTF-8".
204: * @return String
205: */
206: public String getEncoding() {
207: return this .encoding;
208: }
209:
210: public void setEncoding(String encoding) {
211: this .encoding = encoding;
212: }
213:
214: /**
215: * @see com.opensymphony.webwork.components.Component#start(java.io.Writer)
216: */
217: public boolean start(Writer writer) {
218: return true;
219: }
220:
221: /**
222: * @see com.opensymphony.webwork.components.Component#end(java.io.Writer, java.lang.String, boolean)
223: */
224: protected boolean end(Writer writer, String body,
225: boolean popComponentStack) {
226: try {
227: endAllowingExceptionThrowing(writer, body);
228: } catch (Exception e) {
229: throw new WebWorkException(
230: "failed when trying to end the parsing of component ["
231: + this + "]", e);
232: }
233:
234: super .end(writer, body, popComponentStack);
235: return false;
236: }
237:
238: /**
239: * Render the <code>page</page> specified with the <code>decorator</page>
240: *
241: * @param writer
242: * @param body
243: * @throws IOException
244: * @throws ServletException
245: * @throws TemplateException
246: */
247: protected void endAllowingExceptionThrowing(Writer writer,
248: String body) throws IOException, ServletException,
249: TemplateException {
250:
251: if (JspSupportServlet.jspSupportServlet == null) {
252: throw new IllegalStateException(
253: "JspSupportServlet needs to be configured and loaded on start up");
254: }
255:
256: Page pageObject = null;
257: if (page == null) {
258: pageObject = parsePageObjectFromContent(body);
259: } else if (page.startsWith("http://")
260: || page.startsWith("https://")) {
261: pageObject = parsePageObjectFromAbsoluteUrl(page);
262: } else {
263: pageObject = parsePageObjectFromRelativeUrlPath(page);
264: }
265: Decorator decorator = getDecorator(request, name);
266:
267: try {
268: Template template = getFreemarkerTemplate(decorator
269: .getPage());
270: SimpleHash model = createModel();
271: model.put("page", pageObject);
272: if (pageObject instanceof HTMLPage)
273: model.put("head", ((HTMLPage) pageObject).getHead());
274: model.put("body", pageObject.getBody());
275: model.put("title", pageObject.getTitle());
276: model.put("page.properties", pageObject.getProperties());
277: template.process(model, writer);
278: } finally {
279: }
280: }
281:
282: /**
283: * Method Hook: <p/>
284: * Returns a Freemarker's Template bsaed on the <code>templatePath</code>
285: * supplied.
286: *
287: * @param templatePath
288: * @return
289: * @throws IOException
290: * @throws TemplateException
291: */
292: protected Template getFreemarkerTemplate(String templatePath)
293: throws IOException, TemplateException {
294: FreemarkerManager freemarkerManager = FreemarkerManager
295: .getInstance();
296: Configuration configuration = freemarkerManager
297: .getConfiguration(JspSupportServlet.jspSupportServlet
298: .getServletContext());
299:
300: return configuration.getTemplate(templatePath);
301: }
302:
303: /**
304: * Returns back a Sitemesh Page object assuming that the <code>content</code> is
305: * the content of the "to-be-decorated" page.
306: *
307: * @param content
308: * @return
309: * @throws IOException
310: */
311: protected Page parsePageObjectFromContent(String content)
312: throws IOException {
313: PageParser pageParser = getPageParser(contentType);
314: return pageParser.parse(content.toCharArray());
315: }
316:
317: /**
318: * Returns back a Sitemesh Page object assuming that the <code>absoluteUrl</code> is
319: * the content of the "to-be-decorated" page.
320: *
321: * @param absoluteUrl
322: * @return
323: * @throws IOException
324: */
325: protected Page parsePageObjectFromAbsoluteUrl(String absoluteUrl)
326: throws IOException {
327: StringBuffer content = new StringBuffer(1024);
328: URL url = new URL(page);
329: BufferedReader reader = new BufferedReader(
330: new InputStreamReader(url.openStream()));
331:
332: int read = -1;
333: do {
334: char[] buffer = new char[1024];
335: read = reader.read(buffer);
336: if (read > -1)
337: content.append(buffer, 0, read);
338: } while (read != -1);
339:
340: PageParser pageParser = getPageParser(contentType);
341: return pageParser.parse(content.toString().toCharArray());
342: }
343:
344: /**
345: * Returns a Sitemesh Page object assuming the <code>relativeUrl</code> is
346: * the freemarker page that is "to-be-decorated".
347: *
348: * @param relativeUrl
349: * @return
350: * @throws TemplateException
351: * @throws IOException
352: */
353: protected Page parsePageObjectFromRelativeUrlPath(String relativeUrl)
354: throws TemplateException, IOException {
355: String fullPath = page;
356: if (fullPath.length() > 0 && fullPath.charAt(0) != '/') {
357:
358: // find absolute path if relative supplied
359: String this Path = request.getServletPath();
360:
361: // check if it did not return null (could occur when the servlet container
362: // does not use a servlet to serve the requested resouce)
363: if (this Path == null) {
364: String requestURI = request.getRequestURI();
365: if (request.getPathInfo() != null) {
366: // strip the pathInfo from the requestURI
367: this Path = requestURI.substring(0, requestURI
368: .indexOf(request.getPathInfo()));
369: } else {
370: this Path = requestURI;
371: }
372: }
373:
374: fullPath = this Path.substring(0,
375: this Path.lastIndexOf('/') + 1)
376: + fullPath;
377: int dotdot;
378: while ((dotdot = fullPath.indexOf("..")) > -1) {
379: int prevSlash = fullPath.lastIndexOf('/', dotdot - 2);
380: fullPath = fullPath.substring(0, prevSlash)
381: + fullPath.substring(dotdot + 2);
382: }
383: }
384:
385: contentType = contentType == null ? "text/html" : contentType;
386: if (encoding != null)
387: contentType = contentType + ";charset=" + encoding;
388:
389: Template decoratorTemplate = getFreemarkerTemplate(fullPath);
390:
391: Factory factory = getSitemeshFactory();
392: Buffer buffer = new Buffer(factory, contentType, encoding);
393: SimpleHash model = createModel();
394: decoratorTemplate.process(model, buffer.getWriter());
395: Page pageObject = buffer.parse();
396: return pageObject;
397: }
398:
399: /**
400: * Returns a Sitemesh Factory object.
401: * @return
402: */
403: protected Factory getSitemeshFactory() {
404: ServletConfig servletConfig = JspSupportServlet.jspSupportServlet
405: .getServletConfig();
406: Config config = new Config(servletConfig);
407: Factory factory = Factory.getInstance(config);
408: return factory;
409: }
410:
411: /**
412: * Returns a Sitemesh PageParser object with the <code>contentType</code>
413: * specified, which by default is "text/html".
414: *
415: * @param contentType
416: * @return
417: */
418: protected PageParser getPageParser(String contentType) {
419: Factory factory = getSitemeshFactory();
420: return factory.getPageParser(contentType == null ? "text/html"
421: : contentType);
422: }
423:
424: /**
425: * Returns a Sitemesh Decorator object with the <code>decoratorName</code> specified.
426: *
427: * @param request
428: * @param decoratorName
429: * @return
430: */
431: protected Decorator getDecorator(HttpServletRequest request,
432: String decoratorName) {
433: return getSitemeshFactory().getDecoratorMapper()
434: .getNamedDecorator(request, decoratorName);
435: }
436:
437: /**
438: * Attempt to deduce the Locale, based on <code>invocation</code> and
439: * Freemarker's <code>configuration</code>.
440: *
441: * @param invocation
442: * @param configuration
443: * @return
444: */
445: protected Locale deduceLocale(ActionInvocation invocation,
446: Configuration configuration) {
447: if (invocation.getAction() instanceof LocaleProvider) {
448: return ((LocaleProvider) invocation.getAction())
449: .getLocale();
450: } else {
451: return configuration.getLocale();
452: }
453: }
454:
455: /**
456: * Create a Freemarker's Model through {@link FreemarkerManager#buildTemplateModel(OgnlValueStack, Object, ServletContext, HttpServletRequest, HttpServletResponse, ObjectWrapper)}.
457: *
458: * @return
459: * @throws TemplateException
460: */
461: protected SimpleHash createModel() throws TemplateException {
462: FreemarkerManager freemarkerManager = FreemarkerManager
463: .getInstance();
464: ActionInvocation invocation = ActionContext.getContext()
465: .getActionInvocation();
466: ServletContext servletContext = JspSupportServlet.jspSupportServlet
467: .getServletContext();
468: OgnlValueStack stack = ActionContext.getContext()
469: .getValueStack();
470: ObjectWrapper wrapper = freemarkerManager.getConfiguration(
471: servletContext).getObjectWrapper();
472:
473: Object action = null;
474: if (invocation != null)
475: action = invocation.getAction(); //Added for NullPointException
476:
477: return freemarkerManager.buildTemplateModel(stack, action,
478: servletContext, request, response, wrapper);
479: }
480: }
|