001: /*
002: * $Id: FreemarkerResult.java 477379 2006-11-20 22:41:56Z ddewolf $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts2.views.freemarker;
022:
023: import java.io.IOException;
024: import java.io.Writer;
025: import java.util.Locale;
026:
027: import javax.servlet.ServletContext;
028: import javax.servlet.http.HttpServletRequest;
029: import javax.servlet.http.HttpServletResponse;
030:
031: import org.apache.struts2.ServletActionContext;
032: import org.apache.struts2.dispatcher.StrutsResultSupport;
033: import org.apache.struts2.views.util.ResourceUtil;
034:
035: import com.opensymphony.xwork2.ActionContext;
036: import com.opensymphony.xwork2.ActionInvocation;
037: import com.opensymphony.xwork2.LocaleProvider;
038: import com.opensymphony.xwork2.inject.Inject;
039: import com.opensymphony.xwork2.util.ValueStack;
040:
041: import freemarker.template.Configuration;
042: import freemarker.template.ObjectWrapper;
043: import freemarker.template.Template;
044: import freemarker.template.TemplateException;
045: import freemarker.template.TemplateModel;
046: import freemarker.template.TemplateModelException;
047:
048: /**
049: * <!-- START SNIPPET: description -->
050: *
051: * Renders a view using the Freemarker template engine.
052: * <p>
053: * The FreemarkarManager class configures the template loaders so that the
054: * template location can be either
055: * </p>
056: *
057: * <ul>
058: *
059: * <li>relative to the web root folder. eg <code>/WEB-INF/views/home.ftl</code>
060: * </li>
061: *
062: * <li>a classpath resuorce. eg <code>com/company/web/views/home.ftl</code></li>
063: *
064: * </ul>
065: *
066: * <!-- END SNIPPET: description -->
067: *
068: * <b>This result type takes the following parameters:</b>
069: *
070: * <!-- START SNIPPET: params -->
071: *
072: * <ul>
073: *
074: * <li><b>location (default)</b> - the location of the template to process.</li>
075: *
076: * <li><b>parse</b> - true by default. If set to false, the location param will
077: * not be parsed for Ognl expressions.</li>
078: *
079: * <li><b>contentType</b> - defaults to "text/html" unless specified.</li>
080: *
081: * </ul>
082: *
083: * <!-- END SNIPPET: params -->
084: *
085: * <b>Example:</b>
086: *
087: * <pre>
088: * <!-- START SNIPPET: example -->
089: *
090: * <result name="success" type="freemarker">foo.ftl</result>
091: *
092: * <!-- END SNIPPET: example -->
093: * </pre>
094: */
095: public class FreemarkerResult extends StrutsResultSupport {
096:
097: private static final long serialVersionUID = -3778230771704661631L;
098:
099: protected ActionInvocation invocation;
100: protected Configuration configuration;
101: protected ObjectWrapper wrapper;
102: protected FreemarkerManager freemarkerManager;
103: private Writer writer;
104:
105: /*
106: * Struts results are constructed for each result execution
107: *
108: * the current context is availible to subclasses via these protected fields
109: */
110: protected String location;
111: private String pContentType = "text/html";
112:
113: public FreemarkerResult() {
114: super ();
115: }
116:
117: public FreemarkerResult(String location) {
118: super (location);
119: }
120:
121: @Inject
122: public void setFreemarkerManager(FreemarkerManager mgr) {
123: this .freemarkerManager = mgr;
124: }
125:
126: public void setContentType(String aContentType) {
127: pContentType = aContentType;
128: }
129:
130: /**
131: * allow parameterization of the contentType
132: * the default being text/html
133: */
134: public String getContentType() {
135: return pContentType;
136: }
137:
138: /**
139: * Execute this result, using the specified template location.
140: * <p/>
141: * The template location has already been interoplated for any variable substitutions
142: * <p/>
143: * this method obtains the freemarker configuration and the object wrapper from the provided hooks.
144: * It them implements the template processing workflow by calling the hooks for
145: * preTemplateProcess and postTemplateProcess
146: */
147: public void doExecute(String location, ActionInvocation invocation)
148: throws IOException, TemplateException {
149: this .location = location;
150: this .invocation = invocation;
151: this .configuration = getConfiguration();
152: this .wrapper = getObjectWrapper();
153:
154: if (!location.startsWith("/")) {
155: ActionContext ctx = invocation.getInvocationContext();
156: HttpServletRequest req = (HttpServletRequest) ctx
157: .get(ServletActionContext.HTTP_REQUEST);
158: String base = ResourceUtil.getResourceBase(req);
159: location = base + "/" + location;
160: }
161:
162: Template template = configuration.getTemplate(location,
163: deduceLocale());
164: TemplateModel model = createModel();
165:
166: // Give subclasses a chance to hook into preprocessing
167: if (preTemplateProcess(template, model)) {
168: try {
169: // Process the template
170: template.process(model, getWriter());
171: } finally {
172: // Give subclasses a chance to hook into postprocessing
173: postTemplateProcess(template, model);
174: }
175: }
176: }
177:
178: /**
179: * This method is called from {@link #doExecute(String, ActionInvocation)} to obtain the
180: * FreeMarker configuration object that this result will use for template loading. This is a
181: * hook that allows you to custom-configure the configuration object in a subclass, or to fetch
182: * it from an IoC container.
183: * <p/>
184: * <b>
185: * The default implementation obtains the configuration from the ConfigurationManager instance.
186: * </b>
187: */
188: protected Configuration getConfiguration() throws TemplateException {
189: return freemarkerManager.getConfiguration(ServletActionContext
190: .getServletContext());
191: }
192:
193: /**
194: * This method is called from {@link #doExecute(String, ActionInvocation)} to obtain the
195: * FreeMarker object wrapper object that this result will use for adapting objects into template
196: * models. This is a hook that allows you to custom-configure the wrapper object in a subclass.
197: * <p/>
198: * <b>
199: * The default implementation returns {@link Configuration#getObjectWrapper()}
200: * </b>
201: */
202: protected ObjectWrapper getObjectWrapper() {
203: return configuration.getObjectWrapper();
204: }
205:
206: public void setWriter(Writer writer) {
207: this .writer = writer;
208: }
209:
210: /**
211: * The default writer writes directly to the response writer.
212: */
213: protected Writer getWriter() throws IOException {
214: if (writer != null) {
215: return writer;
216: }
217: return ServletActionContext.getResponse().getWriter();
218: }
219:
220: /**
221: * Build the instance of the ScopesHashModel, including JspTagLib support
222: * <p/>
223: * Objects added to the model are
224: * <p/>
225: * <ul>
226: * <li>Application - servlet context attributes hash model
227: * <li>JspTaglibs - jsp tag lib factory model
228: * <li>Request - request attributes hash model
229: * <li>Session - session attributes hash model
230: * <li>request - the HttpServletRequst object for direct access
231: * <li>response - the HttpServletResponse object for direct access
232: * <li>stack - the OgnLValueStack instance for direct access
233: * <li>ognl - the instance of the OgnlTool
234: * <li>action - the action itself
235: * <li>exception - optional : the JSP or Servlet exception as per the servlet spec (for JSP Exception pages)
236: * <li>struts - instance of the StrutsUtil class
237: * </ul>
238: */
239: protected TemplateModel createModel() throws TemplateModelException {
240: ServletContext servletContext = ServletActionContext
241: .getServletContext();
242: HttpServletRequest request = ServletActionContext.getRequest();
243: HttpServletResponse response = ServletActionContext
244: .getResponse();
245: ValueStack stack = ServletActionContext.getContext()
246: .getValueStack();
247:
248: Object action = null;
249: if (invocation != null)
250: action = invocation.getAction(); //Added for NullPointException
251: return freemarkerManager.buildTemplateModel(stack, action,
252: servletContext, request, response, wrapper);
253: }
254:
255: /**
256: * Returns the locale used for the {@link Configuration#getTemplate(String, Locale)} call. The base implementation
257: * simply returns the locale setting of the action (assuming the action implements {@link LocaleProvider}) or, if
258: * the action does not the configuration's locale is returned. Override this method to provide different behaviour,
259: */
260: protected Locale deduceLocale() {
261: if (invocation.getAction() instanceof LocaleProvider) {
262: return ((LocaleProvider) invocation.getAction())
263: .getLocale();
264: } else {
265: return configuration.getLocale();
266: }
267: }
268:
269: /**
270: * the default implementation of postTemplateProcess applies the contentType parameter
271: */
272: protected void postTemplateProcess(Template template,
273: TemplateModel data) throws IOException {
274: }
275:
276: /**
277: * Called before the execution is passed to template.process().
278: * This is a generic hook you might use in subclasses to perform a specific
279: * action before the template is processed. By default does nothing.
280: * A typical action to perform here is to inject application-specific
281: * objects into the model root
282: *
283: * @return true to process the template, false to suppress template processing.
284: */
285: protected boolean preTemplateProcess(Template template,
286: TemplateModel model) throws IOException {
287: Object attrContentType = template
288: .getCustomAttribute("content_type");
289:
290: if (attrContentType != null) {
291: ServletActionContext.getResponse().setContentType(
292: attrContentType.toString());
293: } else {
294: String contentType = getContentType();
295:
296: if (contentType == null) {
297: contentType = "text/html";
298: }
299:
300: String encoding = template.getEncoding();
301:
302: if (encoding != null) {
303: contentType = contentType + "; charset=" + encoding;
304: }
305:
306: ServletActionContext.getResponse().setContentType(
307: contentType);
308: }
309:
310: return true;
311: }
312: }
|