KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > opensymphony > webwork > views > velocity > AbstractTagDirective


1 /*
2  * Copyright (c) 2002-2003 by OpenSymphony
3  * All rights reserved.
4  */

5 package com.opensymphony.webwork.views.velocity;
6
7 import com.opensymphony.webwork.ServletActionContext;
8 import com.opensymphony.webwork.config.Configuration;
9 import com.opensymphony.webwork.views.jsp.ParamTag;
10 import com.opensymphony.xwork.ActionContext;
11 import com.opensymphony.xwork.util.OgnlUtil;
12 import com.opensymphony.xwork.util.OgnlValueStack;
13 import ognl.Ognl;
14 import org.apache.commons.logging.Log;
15 import org.apache.commons.logging.LogFactory;
16 import org.apache.velocity.app.event.EventCartridge;
17 import org.apache.velocity.context.Context;
18 import org.apache.velocity.context.InternalContextAdapter;
19 import org.apache.velocity.exception.MethodInvocationException;
20 import org.apache.velocity.exception.ParseErrorException;
21 import org.apache.velocity.exception.ResourceNotFoundException;
22 import org.apache.velocity.runtime.directive.Directive;
23 import org.apache.velocity.runtime.parser.node.Node;
24 import org.apache.velocity.runtime.resource.Resource;
25 import org.apache.velocity.util.introspection.IntrospectionCacheData;
26
27 import javax.servlet.jsp.JspException JavaDoc;
28 import javax.servlet.jsp.PageContext JavaDoc;
29 import javax.servlet.jsp.tagext.BodyContent JavaDoc;
30 import javax.servlet.jsp.tagext.BodyTag JavaDoc;
31 import javax.servlet.jsp.tagext.IterationTag JavaDoc;
32 import javax.servlet.jsp.tagext.Tag JavaDoc;
33 import java.io.IOException JavaDoc;
34 import java.io.Writer JavaDoc;
35 import java.util.*;
36
37
38 /**
39  * Custom user Directive that enables the WebWork2 UI tags to be easily accessed from Velocity pages
40  *
41  * @deprecated Automatic JSP tag support doesn't work well and is likely to break. Please use the native Velocity
42  * tags introduced in WebWork 2.2
43  */

44 public abstract class AbstractTagDirective extends Directive {
45     protected static Log log = LogFactory.getLog(AbstractTagDirective.class);
46
47     public static final String JavaDoc VELOCITY_WRITER = "com.opensymphony.webwork.views.velocity.AbstractTagDirective.VELOCITY_WRITER";
48
49     /**
50      * a params of tagname to tagclass that provides faster lookup that searching through the tagpath. for example,
51      * <pre>#tag( TextField )</pre>
52      * would result in "TextField" and com.opensymphony.webwork.views.jsp.ui.TextFieldTag.class being stored in the
53      * tagclassMap
54      * todo enable this params to be reloaded or reset
55      */

56     protected static Map tagclassMap = new HashMap();
57
58     /**
59      * the guts of this directive that indicates how this directive should be rendered. Conceptually, this method is
60      * a controller that delegates the work to other methods. by convention, i'm using process* for the delegated
61      * methods. processRenderer and processTag respectively.
62      */

63     public boolean render(InternalContextAdapter contextAdapter, Writer JavaDoc writer, Node node) throws IOException JavaDoc, ResourceNotFoundException, ParseErrorException, MethodInvocationException {
64         if (node.jjtGetNumChildren() < 1) {
65             throw new ParseErrorException("no tag specified! to use the #tag directive, you must specify at least the name of the tag to use");
66         }
67
68         // instantiate our tag
69
Object JavaDoc object = this.createObject(node.jjtGetChild(0));
70
71         /**
72          * if this directive allows for a body, the last child Node will be the body. we'll want to grab a handle to
73          * this Node to allow the processTag method to use it
74          */

75         Node bodyNode = null;
76
77         /**
78          * if this Directive is a BLOCK directive, then we <b>know</b> we must have a body. store the reference to
79          * the directive's body in bodyNode
80          */

81         if (this.getType() == BLOCK) {
82             bodyNode = node.jjtGetChild(node.jjtGetNumChildren() - 1);
83         }
84
85         /**
86          * save the previous parent and tag if there are any
87          */

88         Object JavaDoc currentParent = contextAdapter.get(VelocityManager.PARENT);
89         Object JavaDoc currentTag = contextAdapter.get(VelocityManager.TAG);
90
91         try {
92             // if we're already inside a tag, then make this tag the new parent
93
contextAdapter.put(VelocityManager.PARENT, currentTag);
94             contextAdapter.put(VelocityManager.TAG, object);
95
96             InternalContextAdapter subContextAdapter = new WrappedInternalContextAdapter(contextAdapter);
97
98             // populate our tag with all the user specified properties
99
if (object instanceof ParamTag.Parametric) {
100                 Map params = ((ParamTag.Parametric) object).getParameters();
101
102                 if (params != null) {
103                     params.clear();
104                 }
105             }
106
107             applyAttributes(contextAdapter, node, object);
108
109             if (object instanceof Tag) {
110                 PageContext JavaDoc pageContext = ServletActionContext.getPageContext();
111
112                 if (currentTag instanceof Tag) {
113                     ((Tag) object).setParent((Tag) currentTag);
114                 }
115
116                 try {
117                     ActionContext.getContext().put(VELOCITY_WRITER, writer);
118                     return this.processTag(pageContext, (Tag) object, subContextAdapter, writer, node, bodyNode);
119                 } catch (Exception JavaDoc e) {
120                     log.error("Error processing tag: " + e, e);
121
122                     return false;
123                 }
124             } else {
125                 return true;
126             }
127         } finally {
128             /**
129              * replace the parent and/or child if there were any
130              */

131             if (currentParent != null) {
132                 contextAdapter.put(VelocityManager.PARENT, currentParent);
133             } else {
134                 contextAdapter.remove(VelocityManager.PARENT);
135             }
136
137             if (currentTag != null) {
138                 contextAdapter.put(VelocityManager.TAG, currentTag);
139             } else {
140                 contextAdapter.remove(VelocityManager.TAG);
141             }
142         }
143     }
144
145     /**
146      * todo it would be nice for the Configuration object to allow listeners to be registered so that they can be
147      * notified of changes to the Configuration files
148      *
149      * @return an array of paths to search for our tag library
150      */

151     protected String JavaDoc[] getTagPath() throws ResourceNotFoundException {
152         List pathList = new ArrayList();
153
154         // let's add the webwork tags first
155
pathList.add("com.opensymphony.webwork.views.jsp.ui");
156         pathList.add("com.opensymphony.webwork.views.jsp");
157
158         // now, if the user has defined a custom path, let's add that too
159
if (Configuration.isSet("webwork.velocity.tag.path")) {
160             StringTokenizer st = new StringTokenizer(Configuration.getString("webwork.velocity.tag.path"), ",");
161
162             while (st.hasMoreTokens()) {
163                 String JavaDoc token = st.nextToken().trim();
164                 pathList.add(token);
165             }
166         }
167
168         // allow fully qualified class names to be specified
169
pathList.add("");
170
171         String JavaDoc[] path = new String JavaDoc[pathList.size()];
172         pathList.toArray(path);
173
174         return path;
175     }
176
177     /**
178      * create a new instance of our rendering object. this will usually be a Tag, but I've left it as an Object just
179      * in case we want to define more abitrary rendering mechanisms that are not JSP tags
180      *
181      * @param node the node that contains the label for our rendering object. this will usually be something like
182      * TextField, Password, or Component
183      * @return a new instance of the object specified by the Node
184      * @throws org.apache.velocity.exception.ResourceNotFoundException
185      *
186      */

187     protected Object JavaDoc createObject(Node node) throws ResourceNotFoundException {
188         String JavaDoc tagname = node.getFirstToken().toString();
189
190         /*
191          * velocity 1.3 support of directives in 1.4. remove quotes around tagname.
192          * directives have to be quoted to in 1.4 e.g.
193          * #tag( "Label" "label='label test'" "name='label name'" "value=scalar" )
194          * vs. #tag( Label "label='label test'" "name='label name'" "value=scalar" ) in 1.3
195          */

196         if (tagname.startsWith("\"") && tagname.endsWith("\"")) {
197             tagname = tagname.substring(1, tagname.length() - 1);
198         }
199         
200         
201         Class JavaDoc clazz = (Class JavaDoc) tagclassMap.get(tagname);
202
203         if (clazz == null) {
204             clazz = this.findTagInPath(tagname);
205             tagclassMap.put(tagname, clazz);
206         }
207
208         if (clazz == null) {
209             throw new ResourceNotFoundException("No tag, '" + tagname + "', found in tag path");
210         }
211
212         try {
213             return clazz.newInstance();
214         } catch (Exception JavaDoc e) {
215             throw new ResourceNotFoundException("unable to instantiate tag class, '" + clazz.getName() + "'");
216         }
217     }
218
219     /**
220      * create a Map of properties that the user has passed in. for example,
221      * <pre>
222      * #tag( TextField "name=hello" "value=world" "template=foo" )
223      * </pre>
224      * would yield a params that contains {["name", "hello"], ["value", "world"], ["template", "foo"]}
225      *
226      * @param node the Node passed in to the render method
227      * @return a Map of the user specified properties
228      * @throws org.apache.velocity.exception.ParseErrorException
229      * if the was an error in the format of the property
230      * @see #render
231      */

232     protected Map createPropertyMap(InternalContextAdapter contextAdapter, Node node) throws ParseErrorException, MethodInvocationException {
233         Map propertyMap = new HashMap();
234
235         for (int index = 1, length = node.jjtGetNumChildren(); index < length;
236              index++) {
237             this.putProperty(propertyMap, contextAdapter, node.jjtGetChild(index));
238         }
239
240         return propertyMap;
241     }
242
243     /**
244      * Searches for tags (class that are instances of Renderers or Tags) in the webwork.velocity.tag.path using the
245      * following rules:
246      * <ul>
247      * <li>append the tagname + 'Tag' to the path and see if a class exists and is a Renderer or Tag</li>
248      * <li>append the tagname to the path and see if a class exists and is a Renderer or Tag</li>
249      * </ul>
250      * For example, let us say that we're search for a custom tag, Foobar. Assuming our webwork.velocity.tag.path is
251      * the default ("com.opensymphony.webwork.views.jsp.ui", "com.opensymphony.webwork.views.jsp", ""), then we will search
252      * for our tag in the following locations:
253      * <ul>
254      * <li>com.opensymphony.webwork.views.jsp.ui.FoobarTag</li>
255      * <li>com.opensymphony.webwork.views.jsp.ui.Foobar</li>
256      * <li>com.opensymphony.webwork.views.jsp.FoobarTag</li>
257      * <li>com.opensymphony.webwork.views.jsp.Foobar</li>
258      * <li>FoobarTag</li>
259      * <li>Foobar</li>
260      * </ul>
261      *
262      * @param tagname
263      * @see #getTagPath
264      */

265     protected Class JavaDoc findTagInPath(String JavaDoc tagname) throws ResourceNotFoundException {
266         String JavaDoc[] tagpath = this.getTagPath();
267
268         Class JavaDoc clazz = null;
269
270         for (int index = 0; (clazz == null) && (index < tagpath.length);
271              index++) {
272             try {
273                 clazz = Class.forName(tagpath[index] + "." + tagname + "Tag");
274             } catch (ClassNotFoundException JavaDoc e) {
275             }
276
277             if (clazz == null) {
278                 try {
279                     clazz = Class.forName(tagpath[index] + "." + tagname);
280                 } catch (ClassNotFoundException JavaDoc e) {
281                 }
282             }
283         }
284
285         return clazz;
286     }
287
288     /**
289      *
290      */

291     protected boolean processTag(PageContext JavaDoc pageContext, Tag tag, InternalContextAdapter context, Writer JavaDoc writer, Node node, Node bodyNode) throws ParseErrorException, IOException JavaDoc, MethodInvocationException, ResourceNotFoundException {
292         tag.setPageContext(pageContext);
293         writer = pageContext.getOut();
294
295         try {
296             Map paramMap = null;
297             ParamTag.Parametric parameterizedTag = null;
298
299             if (tag instanceof ParamTag.Parametric) {
300                 parameterizedTag = (ParamTag.Parametric) tag;
301                 paramMap = parameterizedTag.getParameters();
302             }
303
304             int result = tag.doStartTag();
305
306             if (paramMap != null) {
307                 parameterizedTag.getParameters().putAll(paramMap);
308             }
309
310             if (result != Tag.SKIP_BODY) {
311                 if (tag instanceof BodyTag JavaDoc) {
312                     BodyTag JavaDoc bodyTag = (BodyTag JavaDoc) tag;
313
314                     if (result == BodyTag.EVAL_BODY_BUFFERED) {
315                         BodyContent JavaDoc bodyContent = pageContext.pushBody();
316                         writer = bodyContent.getEnclosingWriter();
317                         bodyTag.setBodyContent(bodyContent);
318                     }
319
320                     bodyTag.doInitBody();
321                 }
322
323                 for (boolean done = false; !done;) {
324                     // if body content exists, render it first!
325
if (bodyNode != null) {
326                         bodyNode.render(context, writer);
327                     }
328
329                     if (tag instanceof IterationTag JavaDoc) {
330                         IterationTag JavaDoc iterationTag = (IterationTag JavaDoc) tag;
331                         done = (iterationTag.doAfterBody() == BodyTag.EVAL_BODY_AGAIN) ? false : true;
332                     } else {
333                         done = true;
334                     }
335                 }
336
337                 if (tag instanceof BodyTag JavaDoc) {
338                     if (result == BodyTag.EVAL_BODY_BUFFERED) {
339                         writer = pageContext.popBody();
340                     } else {
341                         ((BodyTag JavaDoc) tag).setBodyContent(null);
342                     }
343                 }
344             }
345
346             tag.doEndTag();
347         } catch (JspException JavaDoc e) {
348             String JavaDoc gripe = "Fatal exception caught while processing tag, " + tag.getClass().getName();
349             log.warn(gripe, e);
350
351             String JavaDoc methodName = "-";
352             throw new MethodInvocationException(gripe, e, methodName);
353         }
354
355         return true;
356     }
357
358     /**
359      * adds a given Node's key/value pair to the propertyMap. For example, if this Node contained the value "rows=20",
360      * then the key, rows, would be added to the propertyMap with the String value, 20.
361      *
362      * @param propertyMap a params containing all the properties that we wish to set
363      * @param node the parameter to set expressed in "name=value" format
364      */

365     protected void putProperty(Map propertyMap, InternalContextAdapter contextAdapter, Node node) throws ParseErrorException, MethodInvocationException {
366         // node.value uses the WebWorkValueStack to evaluate the directive's value parameter
367
String JavaDoc param = node.value(contextAdapter).toString();
368
369         int idx = param.indexOf("=");
370
371         if (idx != -1) {
372             String JavaDoc property = param.substring(0, idx);
373
374             String JavaDoc value = param.substring(idx + 1);
375             propertyMap.put(property, value);
376         } else {
377             throw new ParseErrorException("#" + this.getName() + " arguments must include an assignment operator! For example #tag( Component \"template=mytemplate\" ). #tag( TextField \"mytemplate\" ) is illegal!");
378         }
379     }
380
381     /**
382      * apply the attributes requested to the specified object
383      *
384      * @param context
385      * @param node
386      * @param object the object the tags should be applied to
387      * @throws ParseErrorException
388      * @throws MethodInvocationException
389      */

390     private void applyAttributes(InternalContextAdapter context, Node node, Object JavaDoc object) throws ParseErrorException, MethodInvocationException {
391         Map propertyMap = this.createPropertyMap(context, node);
392
393         // if there's nothing to do, don't bother creating an OgnlContext and the Iterator
394
if ((propertyMap == null) || (propertyMap.size() == 0)) {
395             return;
396         }
397
398         OgnlValueStack stack = ActionContext.getContext().getValueStack();
399         Map ognlContext = Ognl.createDefaultContext(object);
400
401         for (Iterator iterator = propertyMap.entrySet().iterator();
402              iterator.hasNext();) {
403             Map.Entry entry = (Map.Entry) iterator.next();
404             String JavaDoc key = entry.getKey().toString();
405             Object JavaDoc value = entry.getValue();
406
407             if (object instanceof ParamTag.Parametric && key.startsWith("params.")) {
408                 value = stack.findValue(value.toString());
409             }
410
411             OgnlUtil.setProperty(key, value, object, ognlContext);
412         }
413     }
414
415     /**
416      * the WrappedInternalContextAdapter is a simple wrapper around the InternalContextAdapter that allows us to
417      * effectively create local variables within each custom directive that don't bleed into the main context.
418      */

419     class WrappedInternalContextAdapter implements InternalContextAdapter {
420         private HashMap params = new HashMap();
421         private InternalContextAdapter contextAdapter;
422
423         public WrappedInternalContextAdapter(InternalContextAdapter contextAdapter) {
424             this.contextAdapter = contextAdapter;
425         }
426
427         public InternalContextAdapter getBaseContext() {
428             return contextAdapter.getBaseContext();
429         }
430
431         public void setCurrentResource(Resource resource) {
432             contextAdapter.setCurrentResource(resource);
433         }
434
435         public Resource getCurrentResource() {
436             return contextAdapter.getCurrentResource();
437         }
438
439         public String JavaDoc getCurrentTemplateName() {
440             return contextAdapter.getCurrentTemplateName();
441         }
442
443         public EventCartridge getEventCartridge() {
444             return contextAdapter.getEventCartridge();
445         }
446
447         public Context getInternalUserContext() {
448             return contextAdapter.getInternalUserContext();
449         }
450
451         public Object JavaDoc[] getKeys() {
452             Set keySet = params.keySet();
453
454             if (keySet == null) {
455                 return contextAdapter.getKeys();
456             }
457
458             Object JavaDoc[] objects = new Object JavaDoc[keySet.size()];
459             keySet.toArray(objects);
460
461             return objects;
462         }
463
464         public Object JavaDoc[] getTemplateNameStack() {
465             return contextAdapter.getTemplateNameStack();
466         }
467
468         public EventCartridge attachEventCartridge(EventCartridge eventCartridge) {
469             return contextAdapter.attachEventCartridge(eventCartridge);
470         }
471
472         public boolean containsKey(Object JavaDoc o) {
473             if (params.containsKey(o)) {
474                 return true;
475             }
476
477             return contextAdapter.containsKey(o);
478         }
479
480         public Object JavaDoc get(String JavaDoc s) {
481             Object JavaDoc obj = params.get(s);
482
483             if (obj == null) {
484                 obj = contextAdapter.get(s);
485             }
486
487             return obj;
488         }
489
490         public IntrospectionCacheData icacheGet(Object JavaDoc o) {
491             return contextAdapter.icacheGet(o);
492         }
493
494         public void icachePut(Object JavaDoc o, IntrospectionCacheData introspectionCacheData) {
495             contextAdapter.icachePut(o, introspectionCacheData);
496         }
497
498         public void popCurrentTemplateName() {
499             contextAdapter.popCurrentTemplateName();
500         }
501
502         public void pushCurrentTemplateName(String JavaDoc s) {
503             contextAdapter.pushCurrentTemplateName(s);
504         }
505
506         public Object JavaDoc put(String JavaDoc s, Object JavaDoc o) {
507             return params.put(s, o);
508         }
509
510         public Object JavaDoc remove(Object JavaDoc o) {
511             Object JavaDoc obj = params.remove(o);
512
513             if (obj == null) {
514                 obj = contextAdapter.remove(o);
515             }
516
517             return obj;
518         }
519     }
520 }
521
Popular Tags