KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > jelly > impl > TagScript


1 /*
2  * Copyright 2002,2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.commons.jelly.impl;
17
18 import java.io.IOException JavaDoc;
19 import java.lang.reflect.InvocationTargetException JavaDoc;
20 import java.net.MalformedURLException JavaDoc;
21 import java.net.URL JavaDoc;
22 import java.util.Collections JavaDoc;
23 import java.util.Hashtable JavaDoc;
24 import java.util.Iterator JavaDoc;
25 import java.util.Map JavaDoc;
26 import java.util.WeakHashMap JavaDoc;
27
28 import org.apache.commons.beanutils.ConvertingWrapDynaBean;
29 import org.apache.commons.beanutils.ConvertUtils;
30 import org.apache.commons.beanutils.DynaBean;
31 import org.apache.commons.beanutils.DynaProperty;
32
33 import org.apache.commons.jelly.CompilableTag;
34 import org.apache.commons.jelly.JellyContext;
35 import org.apache.commons.jelly.JellyException;
36 import org.apache.commons.jelly.JellyTagException;
37 import org.apache.commons.jelly.DynaTag;
38 import org.apache.commons.jelly.LocationAware;
39 import org.apache.commons.jelly.NamespaceAwareTag;
40 import org.apache.commons.jelly.Script;
41 import org.apache.commons.jelly.Tag;
42 import org.apache.commons.jelly.XMLOutput;
43 import org.apache.commons.jelly.expression.Expression;
44
45 import org.apache.commons.logging.Log;
46 import org.apache.commons.logging.LogFactory;
47
48 import org.xml.sax.Attributes JavaDoc;
49 import org.xml.sax.Locator JavaDoc;
50 import org.xml.sax.SAXException JavaDoc;
51
52 /**
53  * <p><code>TagScript</code> is a Script that evaluates a custom tag.</p>
54  *
55  * <b>Note</b> that this class should be re-entrant and used
56  * concurrently by multiple threads.
57  *
58  * @author <a HREF="mailto:jstrachan@apache.org">James Strachan</a>
59  * @version $Revision: 227285 $
60  */

61 public class TagScript implements Script {
62
63     /** The Log to which logging calls will be made. */
64     private static final Log log = LogFactory.getLog(TagScript.class);
65
66
67     /** The attribute expressions that are created */
68     protected Map JavaDoc attributes = new Hashtable JavaDoc();
69
70     /** the optional namespaces Map of prefix -> URI of this single Tag */
71     private Map JavaDoc tagNamespacesMap;
72
73     /**
74      * The optional namespace context mapping all prefixes -> URIs in scope
75      * at the point this tag is used.
76      * This Map is only created lazily if it is required by the NamespaceAwareTag.
77      */

78     private Map JavaDoc namespaceContext;
79
80     /** the Jelly file which caused the problem */
81     private String JavaDoc fileName;
82
83     /** the qualified element name which caused the problem */
84     private String JavaDoc elementName;
85
86     /** the local (non-namespaced) tag name */
87     private String JavaDoc localName;
88
89     /** the line number of the tag */
90     private int lineNumber = -1;
91
92     /** the column number of the tag */
93     private int columnNumber = -1;
94
95     /** the factory of Tag instances */
96     private TagFactory tagFactory;
97
98     /** the body script used for this tag */
99     private Script tagBody;
100
101     /** the parent TagScript */
102     private TagScript parent;
103
104     /** the SAX attributes */
105     private Attributes JavaDoc saxAttributes;
106     
107     /** the url of the script when parsed */
108     private URL JavaDoc scriptURL = null;
109     
110     /** A synchronized WeakHashMap from the current Thread (key) to a Tag object (value).
111      */

112     private Map JavaDoc threadLocalTagCache = Collections.synchronizedMap(new WeakHashMap JavaDoc());
113
114     /**
115      * @return a new TagScript based on whether
116      * the given Tag class is a bean tag or DynaTag
117      */

118     public static TagScript newInstance(Class JavaDoc tagClass) {
119         TagFactory factory = new DefaultTagFactory(tagClass);
120         return new TagScript(factory);
121     }
122
123     public TagScript() {
124     }
125
126     public TagScript(TagFactory tagFactory) {
127         this.tagFactory = tagFactory;
128     }
129
130     public String JavaDoc toString() {
131         return super.toString() + "[tag=" + elementName + ";at=" + lineNumber + ":" + columnNumber + "]";
132     }
133
134     /**
135      * Compiles the tags body
136      */

137     public Script compile() throws JellyException {
138         if (tagBody != null) {
139             tagBody = tagBody.compile();
140         }
141         return this;
142     }
143
144     /**
145      * Sets the optional namespaces prefix -> URI map of
146      * the namespaces attached to this Tag
147      */

148     public void setTagNamespacesMap(Map JavaDoc tagNamespacesMap) {
149         // lets check that this is a thread-safe map
150
if ( ! (tagNamespacesMap instanceof Hashtable JavaDoc) ) {
151             tagNamespacesMap = new Hashtable JavaDoc( tagNamespacesMap );
152         }
153         this.tagNamespacesMap = tagNamespacesMap;
154     }
155
156     /**
157      * Configures this TagScript from the SAX Locator, setting the column
158      * and line numbers
159      */

160     public void setLocator(Locator JavaDoc locator) {
161         setLineNumber( locator.getLineNumber() );
162         setColumnNumber( locator.getColumnNumber() );
163     }
164
165
166     /** Add an initialization attribute for the tag.
167      * This method must be called after the setTag() method
168      */

169     public void addAttribute(String JavaDoc name, Expression expression) {
170         if (log.isDebugEnabled()) {
171             log.debug("adding attribute name: " + name + " expression: " + expression);
172         }
173         attributes.put(name, new ExpressionAttribute(name,expression));
174     }
175
176     /** Add an initialization attribute for the tag.
177      * This method must be called after the setTag() method
178      */

179     public void addAttribute(String JavaDoc name, String JavaDoc prefix, String JavaDoc nsURI, Expression expression) {
180         if (log.isDebugEnabled()) {
181             log.debug("adding attribute name: " + name + " expression: " + expression);
182         }
183         if(name.indexOf(':')==-1)
184             name = prefix + ':' + name;
185         attributes.put(name, new ExpressionAttribute(name,prefix,nsURI,expression));
186     }
187
188     /**
189      * Strips off the name of a script to create a new context URL
190      * FIXME: Copied from JellyContext
191      */

192     private URL JavaDoc getJellyContextURL(URL JavaDoc url) throws MalformedURLException JavaDoc {
193         String JavaDoc text = url.toString();
194         int idx = text.lastIndexOf('/');
195         text = text.substring(0, idx + 1);
196         return new URL JavaDoc(text);
197     }
198
199     // Script interface
200
//-------------------------------------------------------------------------
201

202     /** Evaluates the body of a tag */
203     public void run(JellyContext context, XMLOutput output) throws JellyTagException {
204         URL JavaDoc rootURL = context.getRootURL();
205         URL JavaDoc currentURL = context.getCurrentURL();
206         if ( ! context.isCacheTags() ) {
207             clearTag();
208         }
209         try {
210             Tag tag = getTag(context);
211             if ( tag == null ) {
212                 return;
213             }
214             tag.setContext(context);
215             setContextURLs(context);
216
217             if ( tag instanceof DynaTag ) {
218                 DynaTag dynaTag = (DynaTag) tag;
219
220                 // ### probably compiling this to 2 arrays might be quicker and smaller
221
for (Iterator JavaDoc iter = attributes.entrySet().iterator(); iter.hasNext();) {
222                     Map.Entry JavaDoc entry = (Map.Entry JavaDoc) iter.next();
223                     String JavaDoc name = (String JavaDoc) entry.getKey();
224                     Expression expression = ((ExpressionAttribute) entry.getValue()).exp;
225
226                     Class JavaDoc type = dynaTag.getAttributeType(name);
227                     Object JavaDoc value = null;
228                     if (type != null && type.isAssignableFrom(Expression.class) && !type.isAssignableFrom(Object JavaDoc.class)) {
229                         value = expression;
230                     }
231                     else {
232                         value = expression.evaluateRecurse(context);
233                     }
234                     dynaTag.setAttribute(name, value);
235                 }
236             }
237             else {
238                 // treat the tag as a bean
239
DynaBean dynaBean = new ConvertingWrapDynaBean( tag );
240                 for (Iterator JavaDoc iter = attributes.entrySet().iterator(); iter.hasNext();) {
241                     Map.Entry JavaDoc entry = (Map.Entry JavaDoc) iter.next();
242                     String JavaDoc name = (String JavaDoc) entry.getKey();
243                     Expression expression = ((ExpressionAttribute) entry.getValue()).exp;
244
245                     DynaProperty property = dynaBean.getDynaClass().getDynaProperty(name);
246                     if (property == null) {
247                         throw new JellyException("This tag does not understand the '" + name + "' attribute" );
248                     }
249                     Class JavaDoc type = property.getType();
250
251                     Object JavaDoc value = null;
252                     if (type.isAssignableFrom(Expression.class) && !type.isAssignableFrom(Object JavaDoc.class)) {
253                         value = expression;
254                     }
255                     else {
256                         value = expression.evaluateRecurse(context);
257                     }
258                     dynaBean.set(name, value);
259                 }
260             }
261
262             tag.doTag(output);
263             if (output != null) {
264                 output.flush();
265             }
266         }
267         catch (JellyTagException e) {
268             handleException(e);
269         } catch (JellyException e) {
270             handleException(e);
271         } catch (IOException JavaDoc e) {
272             handleException(e);
273         } catch (RuntimeException JavaDoc e) {
274             handleException(e);
275         }
276         catch (Error JavaDoc e) {
277            /*
278             * Not sure if we should be converting errors to exceptions,
279             * but not trivial to remove because JUnit tags throw
280             * Errors in the normal course of operation. Hmm...
281             */

282             handleException(e);
283         } finally {
284             context.setRootURL(rootURL);
285             context.setCurrentURL(currentURL);
286         }
287
288     }
289
290     /**
291      * Set the context's root and current URL if not present
292      * @param context
293      * @throws JellyTagException
294      */

295     protected void setContextURLs(JellyContext context) throws JellyTagException {
296         if ((context.getCurrentURL() == null || context.getRootURL() == null) && scriptURL != null)
297         {
298             if (context.getRootURL() == null) context.setRootURL(scriptURL);
299             if (context.getCurrentURL() == null) context.setCurrentURL(scriptURL);
300         }
301     }
302
303     // Properties
304
//-------------------------------------------------------------------------
305

306     /**
307      * @return the tag to be evaluated, creating it lazily if required.
308      */

309     public Tag getTag(JellyContext context) throws JellyException {
310         Thread JavaDoc t = Thread.currentThread();
311         Tag tag = (Tag) threadLocalTagCache.get(t);
312         if ( tag == null ) {
313             tag = createTag();
314             if ( tag != null ) {
315                 threadLocalTagCache.put(t,tag);
316                 configureTag(tag,context);
317             }
318         }
319         return tag;
320     }
321
322     /**
323      * Returns the Factory of Tag instances.
324      * @return the factory
325      */

326     public TagFactory getTagFactory() {
327         return tagFactory;
328     }
329
330     /**
331      * Sets the Factory of Tag instances.
332      * @param tagFactory The factory to set
333      */

334     public void setTagFactory(TagFactory tagFactory) {
335         this.tagFactory = tagFactory;
336     }
337
338     /**
339      * Returns the parent.
340      * @return TagScript
341      */

342     public TagScript getParent() {
343         return parent;
344     }
345
346     /**
347      * Returns the tagBody.
348      * @return Script
349      */

350     public Script getTagBody() {
351         return tagBody;
352     }
353
354     /**
355      * Sets the parent.
356      * @param parent The parent to set
357      */

358     public void setParent(TagScript parent) {
359         this.parent = parent;
360     }
361
362     /**
363      * Sets the tagBody.
364      * @param tagBody The tagBody to set
365      */

366     public void setTagBody(Script tagBody) {
367         this.tagBody = tagBody;
368     }
369
370     /**
371      * @return the Jelly file which caused the problem
372      */

373     public String JavaDoc getFileName() {
374         return fileName;
375     }
376
377     /**
378      * Sets the Jelly file which caused the problem
379      */

380     public void setFileName(String JavaDoc fileName) {
381         this.fileName = fileName;
382         try
383         {
384             this.scriptURL = getJellyContextURL(new URL JavaDoc(fileName));
385         } catch (MalformedURLException JavaDoc e) {
386             log.debug("error setting script url", e);
387         }
388     }
389
390
391     /**
392      * @return the element name which caused the problem
393      */

394     public String JavaDoc getElementName() {
395         return elementName;
396     }
397
398     /**
399      * Sets the element name which caused the problem
400      */

401     public void setElementName(String JavaDoc elementName) {
402         this.elementName = elementName;
403     }
404     /**
405      * @return the line number of the tag
406      */

407     public int getLineNumber() {
408         return lineNumber;
409     }
410
411     /**
412      * Sets the line number of the tag
413      */

414     public void setLineNumber(int lineNumber) {
415         this.lineNumber = lineNumber;
416     }
417
418     /**
419      * @return the column number of the tag
420      */

421     public int getColumnNumber() {
422         return columnNumber;
423     }
424
425     /**
426      * Sets the column number of the tag
427      */

428     public void setColumnNumber(int columnNumber) {
429         this.columnNumber = columnNumber;
430     }
431
432     /**
433      * Returns the SAX attributes of this tag
434      * @return Attributes
435      */

436     public Attributes JavaDoc getSaxAttributes() {
437         return saxAttributes;
438     }
439
440     /**
441      * Sets the SAX attributes of this tag
442      * @param saxAttributes The saxAttributes to set
443      */

444     public void setSaxAttributes(Attributes JavaDoc saxAttributes) {
445         this.saxAttributes = saxAttributes;
446     }
447
448     /**
449      * Returns the local, non namespaced XML name of this tag
450      * @return String
451      */

452     public String JavaDoc getLocalName() {
453         return localName;
454     }
455
456     /**
457      * Sets the local, non namespaced name of this tag.
458      * @param localName The localName to set
459      */

460     public void setLocalName(String JavaDoc localName) {
461         this.localName = localName;
462     }
463
464
465     /**
466      * Returns the namespace context of this tag. This is all the prefixes
467      * in scope in the document where this tag is used which are mapped to
468      * their namespace URIs.
469      *
470      * @return a Map with the keys are namespace prefixes and the values are
471      * namespace URIs.
472      */

473     public synchronized Map JavaDoc getNamespaceContext() {
474         if (namespaceContext == null) {
475             if (parent != null) {
476                 namespaceContext = getParent().getNamespaceContext();
477                 if (tagNamespacesMap != null && !tagNamespacesMap.isEmpty()) {
478                     // create a new child context
479
Hashtable JavaDoc newContext = new Hashtable JavaDoc(namespaceContext.size()+1);
480                     newContext.putAll(namespaceContext);
481                     newContext.putAll(tagNamespacesMap);
482                     namespaceContext = newContext;
483                 }
484             }
485             else {
486                 namespaceContext = tagNamespacesMap;
487                 if (namespaceContext == null) {
488                     namespaceContext = new Hashtable JavaDoc();
489                 }
490             }
491         }
492         return namespaceContext;
493     }
494
495     // Implementation methods
496
//-------------------------------------------------------------------------
497

498     /**
499      * Factory method to create a new Tag instance.
500      * The default implementation is to delegate to the TagFactory
501      */

502     protected Tag createTag() throws JellyException {
503         if ( tagFactory != null) {
504             return tagFactory.createTag(localName, getSaxAttributes());
505         }
506         return null;
507     }
508
509     
510     /**
511      * Compiles a newly created tag if required, sets its parent and body.
512      */

513     protected void configureTag(Tag tag, JellyContext context) throws JellyException {
514         if (tag instanceof CompilableTag) {
515             ((CompilableTag) tag).compile();
516         }
517         Tag parentTag = null;
518         if ( parent != null ) {
519             parentTag = parent.getTag(context);
520         }
521         tag.setParent( parentTag );
522         tag.setBody( tagBody );
523
524         if (tag instanceof NamespaceAwareTag) {
525             NamespaceAwareTag naTag = (NamespaceAwareTag) tag;
526             naTag.setNamespaceContext(getNamespaceContext());
527         }
528         if (tag instanceof LocationAware) {
529             applyLocation((LocationAware) tag);
530         }
531     }
532
533     /**
534      * Flushes the current cached tag so that it will be created, lazily, next invocation
535      */

536     protected void clearTag() {
537         Thread JavaDoc t = Thread.currentThread();
538         threadLocalTagCache.put(t,null);
539     }
540
541     /**
542      * Allows the script to set the tag instance to be used, such as in a StaticTagScript
543      * when a StaticTag is switched with a DynamicTag
544      */

545     protected void setTag(Tag tag, JellyContext context) {
546         Thread JavaDoc t = Thread.currentThread();
547         threadLocalTagCache.put(t,tag);
548     }
549
550     /**
551      * Output the new namespace prefixes used for this element
552      */

553     protected void startNamespacePrefixes(XMLOutput output) throws SAXException JavaDoc {
554         if ( tagNamespacesMap != null ) {
555             for ( Iterator JavaDoc iter = tagNamespacesMap.entrySet().iterator(); iter.hasNext(); ) {
556                 Map.Entry JavaDoc entry = (Map.Entry JavaDoc) iter.next();
557                 String JavaDoc prefix = (String JavaDoc) entry.getKey();
558                 String JavaDoc uri = (String JavaDoc) entry.getValue();
559                 output.startPrefixMapping(prefix, uri);
560             }
561         }
562     }
563
564     /**
565      * End the new namespace prefixes mapped for the current element
566      */

567     protected void endNamespacePrefixes(XMLOutput output) throws SAXException JavaDoc {
568         if ( tagNamespacesMap != null ) {
569             for ( Iterator JavaDoc iter = tagNamespacesMap.keySet().iterator(); iter.hasNext(); ) {
570                 String JavaDoc prefix = (String JavaDoc) iter.next();
571                 output.endPrefixMapping(prefix);
572             }
573         }
574     }
575
576     /**
577      * Converts the given value to the required type.
578      *
579      * @param value is the value to be converted. This will not be null
580      * @param requiredType the type that the value should be converted to
581      */

582     protected Object JavaDoc convertType(Object JavaDoc value, Class JavaDoc requiredType)
583         throws JellyException {
584         if (requiredType.isInstance(value)) {
585             return value;
586         }
587         if (value instanceof String JavaDoc) {
588             return ConvertUtils.convert((String JavaDoc) value, requiredType);
589         }
590         return value;
591     }
592
593     /**
594      * Creates a new Jelly exception, adorning it with location information
595      */

596     protected JellyException createJellyException(String JavaDoc reason) {
597         return new JellyException(
598             reason, fileName, elementName, columnNumber, lineNumber
599         );
600     }
601
602     /**
603      * Creates a new Jelly exception, adorning it with location information
604      */

605     protected JellyException createJellyException(String JavaDoc reason, Exception JavaDoc cause) {
606         if (cause instanceof JellyException) {
607             return (JellyException) cause;
608         }
609
610         if (cause instanceof InvocationTargetException JavaDoc) {
611             return new JellyException(
612                 reason,
613                 ((InvocationTargetException JavaDoc) cause).getTargetException(),
614                 fileName,
615                 elementName,
616                 columnNumber,
617                 lineNumber);
618         }
619         return new JellyException(
620             reason, cause, fileName, elementName, columnNumber, lineNumber
621         );
622     }
623
624     /**
625      * A helper method to handle this Jelly exception.
626      * This method adorns the JellyException with location information
627      * such as adding line number information etc.
628      */

629     protected void handleException(JellyTagException e) throws JellyTagException {
630         if (log.isTraceEnabled()) {
631             log.trace( "Caught exception: " + e, e );
632         }
633
634         applyLocation(e);
635
636         throw e;
637     }
638
639     /**
640      * A helper method to handle this Jelly exception.
641      * This method adorns the JellyException with location information
642      * such as adding line number information etc.
643      */

644     protected void handleException(JellyException e) throws JellyTagException {
645         if (log.isTraceEnabled()) {
646             log.trace( "Caught exception: " + e, e );
647         }
648
649         applyLocation(e);
650
651         throw new JellyTagException(e);
652     }
653
654     protected void applyLocation(LocationAware locationAware) {
655         if (locationAware.getLineNumber() == -1) {
656             locationAware.setColumnNumber(columnNumber);
657             locationAware.setLineNumber(lineNumber);
658         }
659         if ( locationAware.getFileName() == null ) {
660             locationAware.setFileName( fileName );
661         }
662         if ( locationAware.getElementName() == null ) {
663             locationAware.setElementName( elementName );
664         }
665     }
666
667     /**
668      * A helper method to handle this non-Jelly exception.
669      * This method will rethrow the exception, wrapped in a JellyException
670      * while adding line number information etc.
671      */

672     protected void handleException(Exception JavaDoc e) throws JellyTagException {
673         if (log.isTraceEnabled()) {
674             log.trace( "Caught exception: " + e, e );
675         }
676
677         if (e instanceof LocationAware) {
678             applyLocation((LocationAware) e);
679         }
680
681         if ( e instanceof JellyException ) {
682             e.fillInStackTrace();
683         }
684
685         if ( e instanceof InvocationTargetException JavaDoc) {
686             throw new JellyTagException( ((InvocationTargetException JavaDoc)e).getTargetException(),
687                                       fileName,
688                                       elementName,
689                                       columnNumber,
690                                       lineNumber );
691         }
692
693         throw new JellyTagException(e, fileName, elementName, columnNumber, lineNumber);
694     }
695
696     /**
697      * A helper method to handle this non-Jelly exception.
698      * This method will rethrow the exception, wrapped in a JellyException
699      * while adding line number information etc.
700      *
701      * Is this method wise?
702      */

703     protected void handleException(Error JavaDoc e) throws Error JavaDoc, JellyTagException {
704         if (log.isTraceEnabled()) {
705             log.trace( "Caught exception: " + e, e );
706         }
707
708         if (e instanceof LocationAware) {
709             applyLocation((LocationAware) e);
710         }
711
712         throw new JellyTagException(e, fileName, elementName, columnNumber, lineNumber);
713     }
714 }
715
716
717 class ExpressionAttribute {
718     public ExpressionAttribute(String JavaDoc name, Expression exp) {
719         this(name,"","",exp);
720     }
721     public ExpressionAttribute(String JavaDoc name, String JavaDoc prefix, String JavaDoc nsURI, Expression exp) {
722         this.name = name;
723         this.prefix = prefix;
724         this.nsURI = nsURI;
725         this.exp = exp;
726     }
727
728     String JavaDoc name;
729     String JavaDoc prefix;
730     String JavaDoc nsURI;
731     Expression exp;
732 }
Popular Tags