001: package org.apache.velocity.tools.struts;
002:
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:
022: import java.util.Iterator;
023: import java.util.Map;
024: import java.util.Stack;
025:
026: import javax.servlet.http.HttpSession;
027:
028: import org.apache.commons.logging.Log;
029: import org.apache.commons.logging.LogFactory;
030: import org.apache.struts.tiles.AttributeDefinition;
031: import org.apache.struts.tiles.ComponentContext;
032: import org.apache.struts.tiles.ComponentDefinition;
033: import org.apache.struts.tiles.Controller;
034: import org.apache.struts.tiles.DefinitionAttribute;
035: import org.apache.struts.tiles.DefinitionNameAttribute;
036: import org.apache.struts.tiles.DefinitionsFactoryException;
037: import org.apache.struts.tiles.DirectStringAttribute;
038: import org.apache.struts.tiles.TilesUtil;
039: import org.apache.velocity.context.Context;
040: import org.apache.velocity.tools.view.ImportSupport;
041: import org.apache.velocity.tools.view.context.ViewContext;
042:
043: /**
044: * View tool to use struts-tiles with Velocity.
045: * <p><pre>
046: * Template example(s):
047: * <!-- insert a tile -->
048: * $tiles.myTileDefinition
049: *
050: * <!-- get named attribute value from the current tiles-context -->
051: * $tiles.getAttribute("myTileAttribute")
052: *
053: * <!-- import all attributes of the current tiles-context into the velocity-context. -->
054: * $tiles.importAttributes()
055: *
056: * Toolbox configuration:
057: * <tool>
058: * <key>tiles</key>
059: * <scope>request</scope>
060: * <class>org.apache.velocity.tools.struts.TilesTool</class>
061: * </tool>
062: * </pre></p>
063: *
064: * <p>This tool should only be used in the request scope.</p>
065: *
066: * @author <a href="mailto:marinoj@centrum.is">Marino A. Jonsson</a>
067: * @since VelocityTools 1.1
068: * @version $Revision: 477914 $ $Date: 2006-11-21 13:52:11 -0800 (Tue, 21 Nov 2006) $
069: */
070: public class TilesTool extends ImportSupport {
071: protected static final Log LOG = LogFactory.getLog(TilesTool.class);
072:
073: static final String PAGE_SCOPE = "page";
074: static final String REQUEST_SCOPE = "request";
075: static final String SESSION_SCOPE = "session";
076: static final String APPLICATION_SCOPE = "application";
077:
078: protected Context velocityContext;
079:
080: /**
081: * A stack to hold ComponentContexts while nested tile-definitions
082: * are rendered.
083: */
084: protected Stack contextStack;
085:
086: /******************************* Constructors ****************************/
087:
088: /**
089: * Default constructor. Tool must be initialized before use.
090: */
091: public TilesTool() {
092: }
093:
094: /**
095: * Initializes this tool.
096: *
097: * @param obj the current ViewContext
098: * @throws IllegalArgumentException if the param is not a ViewContext
099: */
100: public void init(Object obj) {
101: if (!(obj instanceof ViewContext)) {
102: throw new IllegalArgumentException(
103: "Tool can only be initialized with a ViewContext");
104: }
105:
106: ViewContext viewContext = (ViewContext) obj;
107: this .velocityContext = viewContext.getVelocityContext();
108: this .request = viewContext.getRequest();
109: this .response = viewContext.getResponse();
110: this .application = viewContext.getServletContext();
111: }
112:
113: /***************************** View Helpers ******************************/
114:
115: /**
116: * A generic tiles insert function.
117: *
118: * <p>This is functionally equivalent to
119: * <code><tiles:insert attribute="foo" /></code>.</p>
120: *
121: * @param obj Can be any of the following:
122: * AttributeDefinition,
123: * tile-definition name,
124: * tile-attribute name,
125: * regular uri.
126: * (checked in that order)
127: * @return the rendered template or value as a String
128: * @throws Exception on failure
129: */
130: public String get(Object obj) {
131: try {
132: Object value = getCurrentContext().getAttribute(
133: obj.toString());
134: if (value != null) {
135: return processObjectValue(value);
136: }
137: return processAsDefinitionOrURL(obj.toString());
138: } catch (Exception e) {
139: LOG.error("Exeption while rendering Tile " + obj + ": ", e);
140: return null;
141: }
142: }
143:
144: /**
145: * Fetches a named attribute-value from the current tiles-context.
146: *
147: * <p>This is functionally equivalent to
148: * <code><tiles:getAsString name="foo" /></code>.</p>
149: *
150: * @param name the name of the tiles-attribute to fetch
151: * @return attribute value for the named attribute
152: */
153: public Object getAttribute(String name) {
154: Object value = getCurrentContext().getAttribute(name);
155: if (value == null) {
156: LOG.warn("Tile attribute '" + name
157: + "' wasn't found in context.");
158: }
159: return value;
160: }
161:
162: /**
163: * Imports the named attribute-value from the current tiles-context into the
164: * current Velocity context.
165: *
166: * <p>This is functionally equivalent to
167: * <code><tiles:importAttribute name="foo" /></code>
168: *
169: * @param name the name of the tiles-attribute to import
170: */
171: public void importAttribute(String name) {
172: this .importAttribute(name, PAGE_SCOPE);
173: }
174:
175: /**
176: * Imports the named attribute-value from the current tiles-context into the
177: * named context ("page", "request", "session", or "application").
178: *
179: * <p>This is functionally equivalent to
180: * <code><tiles:importAttribute name="foo" scope="scopeValue" /></code>
181: *
182: * @param name the name of the tiles-attribute to import
183: * @param scope the named context scope to put the attribute into.
184: */
185: public void importAttribute(String name, String scope) {
186: Object value = getCurrentContext().getAttribute(name);
187: if (value == null) {
188: LOG.warn("Tile attribute '" + name
189: + "' wasn't found in context.");
190: }
191:
192: if (scope.equals(PAGE_SCOPE)) {
193: velocityContext.put(name, value);
194: } else if (scope.equals(REQUEST_SCOPE)) {
195: request.setAttribute(name, value);
196: } else if (scope.equals(SESSION_SCOPE)) {
197: request.getSession().setAttribute(name, value);
198: } else if (scope.equals(APPLICATION_SCOPE)) {
199: application.setAttribute(name, value);
200: }
201: }
202:
203: /**
204: * Imports all attributes in the current tiles-context into the
205: * current velocity-context.
206: *
207: * <p>This is functionally equivalent to
208: * <code><tiles:importAttribute /></code>.</p>
209: */
210: public void importAttributes() {
211: this .importAttributes(PAGE_SCOPE);
212: }
213:
214: /**
215: * Imports all attributes in the current tiles-context into the named
216: * context ("page", "request", "session", or "application").
217: *
218: * <p>This is functionally equivalent to
219: * <code><tiles:importAttribute scope="scopeValue" /></code>.</p>
220: *
221: * @param scope the named context scope to put the attributes into.
222: */
223: public void importAttributes(String scope) {
224: ComponentContext context = getCurrentContext();
225: Iterator names = context.getAttributeNames();
226:
227: if (scope.equals(PAGE_SCOPE)) {
228: while (names.hasNext()) {
229: String name = (String) names.next();
230: velocityContext.put(name, context.getAttribute(name));
231: }
232: } else if (scope.equals(REQUEST_SCOPE)) {
233: while (names.hasNext()) {
234: String name = (String) names.next();
235: request.setAttribute(name, context.getAttribute(name));
236: }
237: } else if (scope.equals(SESSION_SCOPE)) {
238: HttpSession session = request.getSession();
239: while (names.hasNext()) {
240: String name = (String) names.next();
241: session.setAttribute(name, context.getAttribute(name));
242: }
243: } else if (scope.equals(APPLICATION_SCOPE)) {
244: while (names.hasNext()) {
245: String name = (String) names.next();
246: application.setAttribute(name, context
247: .getAttribute(name));
248: }
249: }
250: }
251:
252: /************************** Protected Methods ****************************/
253:
254: /**
255: * Process an object retrieved as a bean or attribute.
256: *
257: * @param value - Object can be a typed attribute, a String, or anything
258: * else. If typed attribute, use associated type. Otherwise, apply
259: * toString() on object, and use returned string as a name.
260: * @throws Exception - Throws by underlying nested call to
261: * processDefinitionName()
262: * @return the fully processed value as String
263: */
264: protected String processObjectValue(Object value) throws Exception {
265: /* First, check if value is one of the Typed Attribute */
266: if (value instanceof AttributeDefinition) {
267: /* We have a type => return appropriate IncludeType */
268: return processTypedAttribute((AttributeDefinition) value);
269: } else if (value instanceof ComponentDefinition) {
270: return processDefinition((ComponentDefinition) value);
271: }
272: /* Value must denote a valid String */
273: return processAsDefinitionOrURL(value.toString());
274: }
275:
276: /**
277: * Process typed attribute according to its type.
278: *
279: * @param value Typed attribute to process.
280: * @return the fully processed attribute value as String.
281: * @throws Exception - Throws by underlying nested call to processDefinitionName()
282: */
283: protected String processTypedAttribute(AttributeDefinition value)
284: throws Exception {
285: if (value instanceof DirectStringAttribute) {
286: return (String) value.getValue();
287: } else if (value instanceof DefinitionAttribute) {
288: return processDefinition((ComponentDefinition) value
289: .getValue());
290: } else if (value instanceof DefinitionNameAttribute) {
291: return processAsDefinitionOrURL((String) value.getValue());
292: }
293: /* else if( value instanceof PathAttribute ) */
294: return doInsert((String) value.getValue(), null, null);
295: }
296:
297: /**
298: * Try to process name as a definition, or as an URL if not found.
299: *
300: * @param name Name to process.
301: * @return the fully processed definition or URL
302: * @throws Exception
303: */
304: protected String processAsDefinitionOrURL(String name)
305: throws Exception {
306: try {
307: ComponentDefinition definition = TilesUtil.getDefinition(
308: name, this .request, this .application);
309: if (definition != null) {
310: return processDefinition(definition);
311: }
312: } catch (DefinitionsFactoryException ex) {
313: /* silently failed, because we can choose to not define a factory. */
314: }
315: /* no definition found, try as url */
316: return processUrl(name);
317: }
318:
319: /**
320: * End of Process for definition.
321: *
322: * @param definition Definition to process.
323: * @return the fully processed definition.
324: * @throws Exception from InstantiationException Can't create requested controller
325: */
326: protected String processDefinition(ComponentDefinition definition)
327: throws Exception {
328: Controller controller = null;
329: try {
330: controller = definition.getOrCreateController();
331:
332: String role = definition.getRole();
333: String page = definition.getTemplate();
334:
335: return doInsert(definition.getAttributes(), page, role,
336: controller);
337: } catch (InstantiationException ex) {
338: throw new Exception(ex.getMessage());
339: }
340: }
341:
342: /**
343: * Processes an url
344: *
345: * @param url the URI to process.
346: * @return the rendered template as String.
347: * @throws Exception
348: */
349: protected String processUrl(String url) throws Exception {
350: return doInsert(url, null, null);
351: }
352:
353: /**
354: * Use this if there is no nested tile.
355: *
356: * @param page the page to process.
357: * @param role possible user-role
358: * @param controller possible tiles-controller
359: * @return the rendered template as String.
360: * @throws Exception
361: */
362: protected String doInsert(String page, String role,
363: Controller controller) throws Exception {
364: if (role != null && !this .request.isUserInRole(role)) {
365: return null;
366: }
367:
368: ComponentContext subCompContext = new ComponentContext();
369: return doInsert(subCompContext, page, role, controller);
370: }
371:
372: /**
373: * Use this if there is a nested tile.
374: *
375: * @param attributes attributes for the sub-context
376: * @param page the page to process.
377: * @param role possible user-role
378: * @param controller possible tiles-controller
379: * @return the rendered template as String.
380: * @throws Exception
381: */
382: protected String doInsert(Map attributes, String page, String role,
383: Controller controller) throws Exception {
384: if (role != null && !this .request.isUserInRole(role)) {
385: return null;
386: }
387:
388: ComponentContext subCompContext = new ComponentContext(
389: attributes);
390: return doInsert(subCompContext, page, role, controller);
391: }
392:
393: /**
394: * An extension of the other two doInsert functions
395: *
396: * @param subCompContext the sub-context to set in scope when the
397: * template is rendered.
398: * @param page the page to process.
399: * @param role possible user-role
400: * @param controller possible tiles-controller
401: * @return the rendered template as String.
402: * @throws Exception
403: */
404: protected String doInsert(ComponentContext subCompContext,
405: String page, String role, Controller controller)
406: throws Exception {
407: pushTilesContext();
408: try {
409: ComponentContext.setContext(subCompContext, this .request);
410:
411: /* Call controller if any */
412: if (controller != null) {
413: controller.execute(subCompContext, this .request,
414: this .response, this .application);
415: }
416: /* pass things off to ImportSupport */
417: return this .acquireString(page);
418: } finally {
419: popTilesContext();
420: }
421: }
422:
423: /**
424: * Retrieve the current tiles component context.
425: * This is pretty much just a convenience method.
426: */
427: protected ComponentContext getCurrentContext() {
428: return ComponentContext.getContext(this .request);
429: }
430:
431: /**
432: * <p>pushes the current tiles context onto the context-stack.
433: * preserving the context is necessary so that a sub-context can be
434: * put into request scope and lower level tiles can be rendered</p>
435: */
436: protected void pushTilesContext() {
437: if (this .contextStack == null) {
438: this .contextStack = new Stack();
439: }
440: contextStack.push(getCurrentContext());
441: }
442:
443: /**
444: * Pops the tiles sub-context off the context-stack after the lower level
445: * tiles have been rendered.
446: */
447: protected void popTilesContext() {
448: ComponentContext context = (ComponentContext) this.contextStack
449: .pop();
450: ComponentContext.setContext(context, this.request);
451: }
452:
453: }
|