001: /*
002: * Copyright (c) 2002-2003 by OpenSymphony
003: * All rights reserved.
004: */
005: /*
006: * Created on 15/04/2004
007: */
008: package com.opensymphony.webwork.views.freemarker;
009:
010: import com.opensymphony.webwork.ServletActionContext;
011: import com.opensymphony.webwork.dispatcher.WebWorkResultSupport;
012: import com.opensymphony.webwork.views.util.ResourceUtil;
013: import com.opensymphony.xwork.ActionContext;
014: import com.opensymphony.xwork.ActionInvocation;
015: import com.opensymphony.xwork.LocaleProvider;
016: import com.opensymphony.xwork.util.OgnlValueStack;
017: import freemarker.template.*;
018:
019: import javax.servlet.ServletContext;
020: import javax.servlet.http.HttpServletRequest;
021: import javax.servlet.http.HttpServletResponse;
022:
023: import java.io.CharArrayWriter;
024: import java.io.IOException;
025: import java.io.Writer;
026: import java.util.Locale;
027:
028: /**
029: * <!-- START SNIPPET: description -->
030: *
031: * Renders a view using the Freemarker template engine. Alternatively, the
032: * {@link com.opensymphony.webwork.dispatcher.ServletDispatcherResult
033: * dispatcher} result type can be used in conjunction Webwork's {@link
034: * FreemarkerServlet}.
035: * <p>
036: * The FreemarkarManager class configures the template loaders so that the
037: * template location can be either
038: * </p>
039: *
040: * <ul>
041: *
042: * <li>relative to the web root folder. eg <code>/WEB-INF/views/home.ftl</code>
043: * </li>
044: *
045: * <li>a classpath resuorce. eg <code>com/company/web/views/home.ftl</code></li>
046: *
047: * </ul>
048: *
049: * <p>
050: * <b>NOTE (bufferOutput attribute):</b><br/>
051: * Allow customization of either (when true) to write result to response
052: * stream/writer only when everything is ok (without exception) or otherwise.
053: * This is usefull when using Freemarker's "rethrow" exception handler, where we
054: * don't want partial of the page to be writen and then exception occurred and
055: * we have freemarker's "rethrow" exception handler to take over but its too late
056: * since part of the response has already been 'commited' to the stream/writer.
057: * </p>
058: *
059: * <!-- END SNIPPET: description -->
060: *
061: * <b>This result type takes the following parameters:</b>
062: *
063: * <!-- START SNIPPET: params -->
064: *
065: * <ul>
066: *
067: * <li><b>location (default)</b> - the location of the template to process.</li>
068: *
069: * <li><b>parse</b> - true by default. If set to false, the location param
070: * will not be parsed for Ognl expressions.</li>
071: *
072: * <li><b>contentType</b> - defaults to "text/html" unless specified.</li>
073: *
074: * <li><b>bufferOutput</b> - default to false. If true, will only write to
075: * the response if the whole freemarker page could be rendered ok. </li>
076: *
077: * </ul>
078: *
079: * <!-- END SNIPPET: params -->
080: *
081: * <b>Example:</b>
082: *
083: * <pre>
084: * <!-- START SNIPPET: example -->
085: * <result name="success" type="freemarker">foo.ftl</result>
086: * <!-- END SNIPPET: example -->
087: * </pre>
088: *
089: * @author CameronBraid
090: */
091: public class FreemarkerResult extends WebWorkResultSupport {
092:
093: private static final long serialVersionUID = -3778230771704661631L;
094:
095: protected ActionInvocation invocation;
096: protected Configuration configuration;
097: protected ObjectWrapper wrapper;
098:
099: protected boolean bufferOutput = false;
100:
101: /*
102: * webwork results are constructed for each result execeution
103: *
104: * the current context is availible to subclasses via these protected fields
105: */
106: protected String location;
107: private String pContentType = "text/html";
108:
109: public void setContentType(String aContentType) {
110: pContentType = aContentType;
111: }
112:
113: /**
114: * allow parameterization of the contentType
115: * the default being text/html
116: */
117: public String getContentType() {
118: return pContentType;
119: }
120:
121: public void setBufferOutput(boolean bufferedOutput) {
122: this .bufferOutput = bufferedOutput;
123: }
124:
125: /**
126: * Allow customization of either (when true) to write result to response stream/writer
127: * only when everything is ok (without exception) or otherwise. This is usefull
128: * when using Freemarker's "rethrow" exception handler, where we don't want
129: * partial of the page to be writen and then exception occurred and we have
130: * freemarker's "rethrow" exception handler to take over but its too late since
131: * part of the response has already been 'commited' to the stream/writer.
132: *
133: * @return boolean
134: */
135: public boolean getBufferOutput() {
136: return bufferOutput;
137: }
138:
139: /**
140: * Execute this result, using the specified template location.
141: * <p/>
142: * The template location has already been interoplated for any variable substitutions
143: * <p/>
144: * this method obtains the freemarker configuration and the object wrapper from the provided hooks.
145: * It them implements the template processing workflow by calling the hooks for
146: * preTemplateProcess and postTemplateProcess
147: */
148: public void doExecute(String location, ActionInvocation invocation)
149: throws IOException, TemplateException {
150: this .location = location;
151: this .invocation = invocation;
152: this .configuration = getConfiguration();
153: this .wrapper = getObjectWrapper();
154:
155: if (!location.startsWith("/")) {
156: ActionContext ctx = invocation.getInvocationContext();
157: HttpServletRequest req = (HttpServletRequest) ctx
158: .get(ServletActionContext.HTTP_REQUEST);
159: String base = ResourceUtil.getResourceBase(req);
160: location = base + "/" + location;
161: }
162:
163: Template template = configuration.getTemplate(location,
164: deduceLocale());
165: TemplateModel model = createModel();
166:
167: // Give subclasses a chance to hook into preprocessing
168: if (preTemplateProcess(template, model)) {
169: try {
170: // Process the template
171: // First, get the writer
172: Writer writer = null;
173: boolean useOutputStream = false;
174: try {
175: writer = getWriter();
176: } catch (IllegalStateException ise) {
177: // Getting the writer failed, try using getOutputStream()
178: // This can happen on some application servers such as WebLogic 8.1
179: useOutputStream = true;
180: }
181: if (useOutputStream) {
182: // If we are here, we don't have the issue of WW-1458, since
183: // we are already writing through a temporary buffer.
184:
185: // Use a StringWriter as a buffer to write the template output to
186: writer = new java.io.StringWriter();
187: template.process(model, writer);
188: writer.flush();
189:
190: // Then write the contents of the writer to the OutputStream
191: java.io.OutputStream os = ServletActionContext
192: .getResponse().getOutputStream();
193: os.write(writer.toString().getBytes());
194: } else {
195: // Process the template with the normal writer since it was available
196:
197: // WW-1458
198: // Allow customization of either (when true) to write result to response stream/writer
199: // only when everything is ok (without exception) or otherwise. This is usefull
200: // when using Freemarker's "rethrow" exception handler, where we don't want
201: // partial of the page to be writen and then exception occurred and we have
202: // freemarker's "rethrow" exception handler to take over but its too late since
203: // part of the response has already been 'commited' to the stream/writer.
204: if (configuration.getTemplateExceptionHandler() == TemplateExceptionHandler.RETHROW_HANDLER
205: || getBufferOutput()) {
206: CharArrayWriter tempBuffer = new CharArrayWriter();
207: template.process(model, tempBuffer);
208: tempBuffer.flush();
209:
210: tempBuffer.writeTo(writer);
211: } else {
212: template.process(model, writer);
213: }
214: }
215: } finally {
216: // Give subclasses a chance to hook into postprocessing
217: postTemplateProcess(template, model);
218: }
219: }
220: }
221:
222: /**
223: * This method is called from {@link #doExecute(String, ActionInvocation)} to obtain the
224: * FreeMarker configuration object that this result will use for template loading. This is a
225: * hook that allows you to custom-configure the configuration object in a subclass, or to fetch
226: * it from an IoC container.
227: * <p/>
228: * <b>
229: * The default implementation obtains the configuration from the ConfigurationManager instance.
230: * </b>
231: */
232: protected Configuration getConfiguration() throws TemplateException {
233: return FreemarkerManager.getInstance().getConfiguration(
234: ServletActionContext.getServletContext());
235: }
236:
237: /**
238: * This method is called from {@link #doExecute(String, ActionInvocation)} to obtain the
239: * FreeMarker object wrapper object that this result will use for adapting objects into template
240: * models. This is a hook that allows you to custom-configure the wrapper object in a subclass.
241: * <p/>
242: * <b>
243: * The default implementation returns {@link Configuration#getObjectWrapper()}
244: * </b>
245: */
246: protected ObjectWrapper getObjectWrapper() {
247: return configuration.getObjectWrapper();
248: }
249:
250: /**
251: * The default writer writes directly to the response writer.
252: */
253: protected Writer getWriter() throws IOException {
254: return ServletActionContext.getResponse().getWriter();
255: }
256:
257: /**
258: * Build the instance of the ScopesHashModel, including JspTagLib support
259: * <p/>
260: * Objects added to the model are
261: * <p/>
262: * <ul>
263: * <li>Application - servlet context attributes hash model
264: * <li>JspTaglibs - jsp tag lib factory model
265: * <li>Request - request attributes hash model
266: * <li>Session - session attributes hash model
267: * <li>req - the HttpServletRequst object for direct access
268: * <li>res - the HttpServletResponse object for direct access
269: * <li>stack - the OgnLValueStack instance for direct access
270: * <li>ognl - the instance of the OgnlTool
271: * <li>action - the action itself
272: * <li>exception - optional : the JSP or Servlet exception as per the servlet spec (for JSP Exception pages)
273: * <li>webwork - instance of the WebWorkUtil class
274: * </ul>
275: */
276: protected TemplateModel createModel() throws TemplateModelException {
277: ServletContext servletContext = ServletActionContext
278: .getServletContext();
279: HttpServletRequest request = ServletActionContext.getRequest();
280: HttpServletResponse response = ServletActionContext
281: .getResponse();
282: OgnlValueStack stack = ServletActionContext.getContext()
283: .getValueStack();
284:
285: Object action = null;
286: if (invocation != null)
287: action = invocation.getAction(); //Added for NullPointException
288: return FreemarkerManager.getInstance().buildTemplateModel(
289: stack, action, servletContext, request, response,
290: wrapper);
291: }
292:
293: /**
294: * Returns the locale used for the {@link Configuration#getTemplate(String, Locale)} call. The base implementation
295: * simply returns the locale setting of the action (assuming the action implements {@link LocaleProvider}) or, if
296: * the action does not the configuration's locale is returned. Override this method to provide different behaviour,
297: */
298: protected Locale deduceLocale() {
299: if (invocation.getAction() instanceof LocaleProvider) {
300: return ((LocaleProvider) invocation.getAction())
301: .getLocale();
302: } else {
303: return configuration.getLocale();
304: }
305: }
306:
307: /**
308: * the default implementation of postTemplateProcess applies the contentType parameter
309: */
310: protected void postTemplateProcess(Template template,
311: TemplateModel data) throws IOException {
312: }
313:
314: /**
315: * Called before the execution is passed to template.process().
316: * This is a generic hook you might use in subclasses to perform a specific
317: * action before the template is processed. By default does nothing.
318: * A typical action to perform here is to inject application-specific
319: * objects into the model root
320: *
321: * @return true to process the template, false to suppress template processing.
322: */
323: protected boolean preTemplateProcess(Template template,
324: TemplateModel model) throws IOException, TemplateException {
325: Object attrContentType = template
326: .getCustomAttribute("content_type");
327:
328: if (attrContentType != null) {
329: ServletActionContext.getResponse().setContentType(
330: attrContentType.toString());
331: } else {
332: String contentType = getContentType();
333:
334: if (contentType == null) {
335: contentType = "text/html";
336: }
337:
338: String encoding = template.getEncoding();
339:
340: if (encoding != null) {
341: contentType = contentType + "; charset=" + encoding;
342: }
343:
344: ServletActionContext.getResponse().setContentType(
345: contentType);
346: }
347:
348: return true;
349: }
350: }
|