1 /*
2 * $Header: /cvsroot/xwing/projects/xwing/src/java/org/apache/commons/jelly/impl/TagScript.java,v 1.1 2003/09/17 00:00:20 jshowlett Exp $
3 * $Revision: 1.1 $
4 * $Date: 2003/09/17 00:00:20 $
5 *
6 * ====================================================================
7 *
8 * The Apache Software License, Version 1.1
9 *
10 * Copyright (c) 2002 The Apache Software Foundation. All rights
11 * reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 *
17 * 1. Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 *
20 * 2. Redistributions in binary form must reproduce the above copyright
21 * notice, this list of conditions and the following disclaimer in
22 * the documentation and/or other materials provided with the
23 * distribution.
24 *
25 * 3. The end-user documentation included with the redistribution, if
26 * any, must include the following acknowlegement:
27 * "This product includes software developed by the
28 * Apache Software Foundation (http://www.apache.org/)."
29 * Alternately, this acknowlegement may appear in the software itself,
30 * if and wherever such third-party acknowlegements normally appear.
31 *
32 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
33 * Foundation" must not be used to endorse or promote products derived
34 * from this software without prior written permission. For written
35 * permission, please contact apache@apache.org.
36 *
37 * 5. Products derived from this software may not be called "Apache"
38 * nor may "Apache" appear in their names without prior written
39 * permission of the Apache Group.
40 *
41 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
42 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
43 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
44 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
45 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
47 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
48 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
49 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
51 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
52 * SUCH DAMAGE.
53 * ====================================================================
54 *
55 * This software consists of voluntary contributions made by many
56 * individuals on behalf of the Apache Software Foundation. For more
57 * information on the Apache Software Foundation, please see
58 * <http://www.apache.org/>.
59 *
60 * $Id: TagScript.java,v 1.1 2003/09/17 00:00:20 jshowlett Exp $
61 */
62 package org.apache.commons.jelly.impl;
63
64 import java.lang.reflect.InvocationTargetException;
65 import java.util.Hashtable;
66 import java.util.Iterator;
67 import java.util.Map;
68
69 import org.apache.commons.beanutils.ConvertingWrapDynaBean;
70 import org.apache.commons.beanutils.ConvertUtils;
71 import org.apache.commons.beanutils.DynaBean;
72 import org.apache.commons.beanutils.DynaProperty;
73
74 import org.apache.commons.jelly.CompilableTag;
75 import org.apache.commons.jelly.JellyContext;
76 import org.apache.commons.jelly.JellyException;
77 import org.apache.commons.jelly.JellyTagException;
78 import org.apache.commons.jelly.DynaTag;
79 import org.apache.commons.jelly.LocationAware;
80 import org.apache.commons.jelly.NamespaceAwareTag;
81 import org.apache.commons.jelly.Script;
82 import org.apache.commons.jelly.Tag;
83 import org.apache.commons.jelly.XMLOutput;
84 import org.apache.commons.jelly.expression.Expression;
85
86 import org.apache.commons.logging.Log;
87 import org.apache.commons.logging.LogFactory;
88
89 import org.xml.sax.Attributes;
90 import org.xml.sax.Locator;
91 import org.xml.sax.SAXException;
92
93 /***
94 * <p><code>TagScript</code> is a Script that evaluates a custom tag.</p>
95 *
96 * <b>Note</b> that this class should be re-entrant and used
97 * concurrently by multiple threads.
98 *
99 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
100 * @version $Revision: 1.1 $
101 */
102 public class TagScript implements Script {
103
104 /*** The Log to which logging calls will be made. */
105 private static final Log log = LogFactory.getLog(TagScript.class);
106
107 /***
108 * Thread local storage for the tag used by the current thread.
109 * This allows us to pool tag instances, per thread to reduce object construction
110 * over head, if we need it.
111 *
112 * Note that we could use the stack and create a new tag for each invocation
113 * if we made a slight change to the Script API to pass in the parent tag.
114 */
115 private ThreadLocal tagHolder = new ThreadLocal();
116
117 /*** The attribute expressions that are created */
118 protected Map attributes = new Hashtable();
119
120 /*** the optional namespaces Map of prefix -> URI of this single Tag */
121 private Map tagNamespacesMap;
122
123 /***
124 * The optional namespace context mapping all prefixes -> URIs in scope
125 * at the point this tag is used.
126 * This Map is only created lazily if it is required by the NamespaceAwareTag.
127 */
128 private Map namespaceContext;
129
130 /*** the Jelly file which caused the problem */
131 private String fileName;
132
133 /*** the qualified element name which caused the problem */
134 private String elementName;
135
136 /*** the local (non-namespaced) tag name */
137 private String localName;
138
139 /*** the line number of the tag */
140 private int lineNumber = -1;
141
142 /*** the column number of the tag */
143 private int columnNumber = -1;
144
145 /*** the factory of Tag instances */
146 private TagFactory tagFactory;
147
148 /*** the body script used for this tag */
149 private Script tagBody;
150
151 /*** the parent TagScript */
152 private TagScript parent;
153
154 /*** the SAX attributes */
155 private Attributes saxAttributes;
156
157 /***
158 * @return a new TagScript based on whether
159 * the given Tag class is a bean tag or DynaTag
160 */
161 public static TagScript newInstance(Class tagClass) {
162 TagFactory factory = new DefaultTagFactory(tagClass);
163 return new TagScript(factory);
164 }
165
166 public TagScript() {
167 }
168
169 public TagScript(TagFactory tagFactory) {
170 this.tagFactory = tagFactory;
171 }
172
173 public String toString() {
174 return super.toString() + "[tag=" + elementName + ";at=" + lineNumber + ":" + columnNumber + "]";
175 }
176
177 /***
178 * Compiles the tags body
179 */
180 public Script compile() throws JellyException {
181 if (tagBody != null) {
182 tagBody = tagBody.compile();
183 }
184 return this;
185 }
186
187 /***
188 * Sets the optional namespaces prefix -> URI map of
189 * the namespaces attached to this Tag
190 */
191 public void setTagNamespacesMap(Map tagNamespacesMap) {
192 // lets check that this is a thread-safe map
193 if ( ! (tagNamespacesMap instanceof Hashtable) ) {
194 tagNamespacesMap = new Hashtable( tagNamespacesMap );
195 }
196 this.tagNamespacesMap = tagNamespacesMap;
197 }
198
199 /***
200 * Configures this TagScript from the SAX Locator, setting the column
201 * and line numbers
202 */
203 public void setLocator(Locator locator) {
204 setLineNumber( locator.getLineNumber() );
205 setColumnNumber( locator.getColumnNumber() );
206 }
207
208
209 /*** Add an initialization attribute for the tag.
210 * This method must be called after the setTag() method
211 */
212 public void addAttribute(String name, Expression expression) {
213 if (log.isDebugEnabled()) {
214 log.debug("adding attribute name: " + name + " expression: " + expression);
215 }
216 attributes.put(name, expression);
217 }
218
219 // Script interface
220 //-------------------------------------------------------------------------
221
222 /*** Evaluates the body of a tag */
223 public void run(JellyContext context, XMLOutput output) throws JellyTagException {
224 if ( ! context.isCacheTags() ) {
225 clearTag();
226 }
227 try {
228 Tag tag = getTag();
229 if ( tag == null ) {
230 return;
231 }
232 tag.setContext(context);
233
234 if ( tag instanceof DynaTag ) {
235 DynaTag dynaTag = (DynaTag) tag;
236
237 // ### probably compiling this to 2 arrays might be quicker and smaller
238 for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
239 Map.Entry entry = (Map.Entry) iter.next();
240 String name = (String) entry.getKey();
241 Expression expression = (Expression) entry.getValue();
242
243 Class type = dynaTag.getAttributeType(name);
244 Object value = null;
245 if (type != null && type.isAssignableFrom(Expression.class) && !type.isAssignableFrom(Object.class)) {
246 value = expression;
247 }
248 else {
249 value = expression.evaluateRecurse(context);
250 }
251 dynaTag.setAttribute(name, value);
252 }
253 }
254 else {
255 // treat the tag as a bean
256 DynaBean dynaBean = new ConvertingWrapDynaBean( tag );
257 for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
258 Map.Entry entry = (Map.Entry) iter.next();
259 String name = (String) entry.getKey();
260 Expression expression = (Expression) entry.getValue();
261
262 DynaProperty property = dynaBean.getDynaClass().getDynaProperty(name);
263 if (property == null) {
264 throw new JellyException("This tag does not understand the '" + name + "' attribute" );
265 }
266 Class type = property.getType();
267
268 Object value = null;
269 if (type.isAssignableFrom(Expression.class) && !type.isAssignableFrom(Object.class)) {
270 value = expression;
271 }
272 else {
273 value = expression.evaluateRecurse(context);
274 }
275 dynaBean.set(name, value);
276 }
277 }
278
279 tag.doTag(output);
280 }
281 catch (JellyTagException e) {
282 handleException(e);
283 }
284 catch (JellyException e) {
285 handleException(e);
286 }
287 catch (RuntimeException e) {
288 handleException(e);
289 }
290 catch (Error e) {
291 /*
292 * Not sure if we should be converting errors to exceptions,
293 * but not trivial to remove because JUnit tags throw
294 * Errors in the normal course of operation. Hmm...
295 */
296 handleException(e);
297 }
298 finally {
299 if ( ! context.isCacheTags() ) {
300 clearTag();
301 }
302 }
303
304 }
305
306
307 // Properties
308 //-------------------------------------------------------------------------
309
310 /***
311 * @return the tag to be evaluated, creating it lazily if required.
312 */
313 public Tag getTag() throws JellyException {
314 Tag tag = (Tag) tagHolder.get();
315 if ( tag == null ) {
316 tag = createTag();
317 if ( tag != null ) {
318 tagHolder.set(tag);
319 }
320 }
321 configureTag(tag);
322 return tag;
323 }
324
325 /***
326 * Returns the Factory of Tag instances.
327 * @return the factory
328 */
329 public TagFactory getTagFactory() {
330 return tagFactory;
331 }
332
333 /***
334 * Sets the Factory of Tag instances.
335 * @param tagFactory The factory to set
336 */
337 public void setTagFactory(TagFactory tagFactory) {
338 this.tagFactory = tagFactory;
339 }
340
341 /***
342 * Returns the parent.
343 * @return TagScript
344 */
345 public TagScript getParent() {
346 return parent;
347 }
348
349 /***
350 * Returns the tagBody.
351 * @return Script
352 */
353 public Script getTagBody() {
354 return tagBody;
355 }
356
357 /***
358 * Sets the parent.
359 * @param parent The parent to set
360 */
361 public void setParent(TagScript parent) {
362 this.parent = parent;
363 }
364
365 /***
366 * Sets the tagBody.
367 * @param tagBody The tagBody to set
368 */
369 public void setTagBody(Script tagBody) {
370 this.tagBody = tagBody;
371 }
372
373 /***
374 * @return the Jelly file which caused the problem
375 */
376 public String getFileName() {
377 return fileName;
378 }
379
380 /***
381 * Sets the Jelly file which caused the problem
382 */
383 public void setFileName(String fileName) {
384 this.fileName = fileName;
385 }
386
387
388 /***
389 * @return the element name which caused the problem
390 */
391 public String getElementName() {
392 return elementName;
393 }
394
395 /***
396 * Sets the element name which caused the problem
397 */
398 public void setElementName(String elementName) {
399 this.elementName = elementName;
400 }
401 /***
402 * @return the line number of the tag
403 */
404 public int getLineNumber() {
405 return lineNumber;
406 }
407
408 /***
409 * Sets the line number of the tag
410 */
411 public void setLineNumber(int lineNumber) {
412 this.lineNumber = lineNumber;
413 }
414
415 /***
416 * @return the column number of the tag
417 */
418 public int getColumnNumber() {
419 return columnNumber;
420 }
421
422 /***
423 * Sets the column number of the tag
424 */
425 public void setColumnNumber(int columnNumber) {
426 this.columnNumber = columnNumber;
427 }
428
429 /***
430 * Returns the SAX attributes of this tag
431 * @return Attributes
432 */
433 public Attributes getSaxAttributes() {
434 return saxAttributes;
435 }
436
437 /***
438 * Sets the SAX attributes of this tag
439 * @param saxAttributes The saxAttributes to set
440 */
441 public void setSaxAttributes(Attributes saxAttributes) {
442 this.saxAttributes = saxAttributes;
443 }
444
445 /***
446 * Returns the local, non namespaced XML name of this tag
447 * @return String
448 */
449 public String getLocalName() {
450 return localName;
451 }
452
453 /***
454 * Sets the local, non namespaced name of this tag.
455 * @param localName The localName to set
456 */
457 public void setLocalName(String localName) {
458 this.localName = localName;
459 }
460
461
462 /***
463 * Returns the namespace context of this tag. This is all the prefixes
464 * in scope in the document where this tag is used which are mapped to
465 * their namespace URIs.
466 *
467 * @return a Map with the keys are namespace prefixes and the values are
468 * namespace URIs.
469 */
470 public synchronized Map getNamespaceContext() {
471 if (namespaceContext == null) {
472 if (parent != null) {
473 namespaceContext = getParent().getNamespaceContext();
474 if (tagNamespacesMap != null && !tagNamespacesMap.isEmpty()) {
475 // create a new child context
476 Hashtable newContext = new Hashtable(namespaceContext.size()+1);
477 newContext.putAll(namespaceContext);
478 newContext.putAll(tagNamespacesMap);
479 namespaceContext = newContext;
480 }
481 }
482 else {
483 namespaceContext = tagNamespacesMap;
484 if (namespaceContext == null) {
485 namespaceContext = new Hashtable();
486 }
487 }
488 }
489 return namespaceContext;
490 }
491
492 // Implementation methods
493 //-------------------------------------------------------------------------
494
495 /***
496 * Factory method to create a new Tag instance.
497 * The default implementation is to delegate to the TagFactory
498 */
499 protected Tag createTag() throws JellyException {
500 if ( tagFactory != null) {
501 return tagFactory.createTag(localName, getSaxAttributes());
502 }
503 return null;
504 }
505
506
507 /***
508 * Compiles a newly created tag if required, sets its parent and body.
509 */
510 protected void configureTag(Tag tag) throws JellyException {
511 if (tag instanceof CompilableTag) {
512 ((CompilableTag) tag).compile();
513 }
514 Tag parentTag = null;
515 if ( parent != null ) {
516 parentTag = parent.getTag();
517 }
518 tag.setParent( parentTag );
519 tag.setBody( tagBody );
520
521 if (tag instanceof NamespaceAwareTag) {
522 NamespaceAwareTag naTag = (NamespaceAwareTag) tag;
523 naTag.setNamespaceContext(getNamespaceContext());
524 }
525 if (tag instanceof LocationAware) {
526 applyLocation((LocationAware) tag);
527 }
528 }
529
530 /***
531 * Flushes the current cached tag so that it will be created, lazily, next invocation
532 */
533 protected void clearTag() {
534 tagHolder.set(null);
535 }
536
537 /***
538 * Allows the script to set the tag instance to be used, such as in a StaticTagScript
539 * when a StaticTag is switched with a DynamicTag
540 */
541 protected void setTag(Tag tag) {
542 tagHolder.set(tag);
543 }
544
545 /***
546 * Output the new namespace prefixes used for this element
547 */
548 protected void startNamespacePrefixes(XMLOutput output) throws SAXException {
549 if ( tagNamespacesMap != null ) {
550 for ( Iterator iter = tagNamespacesMap.entrySet().iterator(); iter.hasNext(); ) {
551 Map.Entry entry = (Map.Entry) iter.next();
552 String prefix = (String) entry.getKey();
553 String uri = (String) entry.getValue();
554 output.startPrefixMapping(prefix, uri);
555 }
556 }
557 }
558
559 /***
560 * End the new namespace prefixes mapped for the current element
561 */
562 protected void endNamespacePrefixes(XMLOutput output) throws SAXException {
563 if ( tagNamespacesMap != null ) {
564 for ( Iterator iter = tagNamespacesMap.keySet().iterator(); iter.hasNext(); ) {
565 String prefix = (String) iter.next();
566 output.endPrefixMapping(prefix);
567 }
568 }
569 }
570
571 /***
572 * Converts the given value to the required type.
573 *
574 * @param value is the value to be converted. This will not be null
575 * @param requiredType the type that the value should be converted to
576 */
577 protected Object convertType(Object value, Class requiredType)
578 throws JellyException {
579 if (requiredType.isInstance(value)) {
580 return value;
581 }
582 if (value instanceof String) {
583 return ConvertUtils.convert((String) value, requiredType);
584 }
585 return value;
586 }
587
588 /***
589 * Creates a new Jelly exception, adorning it with location information
590 */
591 protected JellyException createJellyException(String reason) {
592 return new JellyException(
593 reason, fileName, elementName, columnNumber, lineNumber
594 );
595 }
596
597 /***
598 * Creates a new Jelly exception, adorning it with location information
599 */
600 protected JellyException createJellyException(String reason, Exception cause) {
601 if (cause instanceof JellyException) {
602 return (JellyException) cause;
603 }
604
605 if (cause instanceof InvocationTargetException) {
606 return new JellyException(
607 reason,
608 ((InvocationTargetException) cause).getTargetException(),
609 fileName,
610 elementName,
611 columnNumber,
612 lineNumber);
613 }
614 return new JellyException(
615 reason, cause, fileName, elementName, columnNumber, lineNumber
616 );
617 }
618
619 /***
620 * A helper method to handle this Jelly exception.
621 * This method adorns the JellyException with location information
622 * such as adding line number information etc.
623 */
624 protected void handleException(JellyTagException e) throws JellyTagException {
625 if (log.isTraceEnabled()) {
626 log.trace( "Caught exception: " + e, e );
627 }
628
629 applyLocation(e);
630
631 throw e;
632 }
633
634 /***
635 * A helper method to handle this Jelly exception.
636 * This method adorns the JellyException with location information
637 * such as adding line number information etc.
638 */
639 protected void handleException(JellyException e) throws JellyTagException {
640 if (log.isTraceEnabled()) {
641 log.trace( "Caught exception: " + e, e );
642 }
643
644 applyLocation(e);
645
646 throw new JellyTagException(e);
647 }
648
649 protected void applyLocation(LocationAware locationAware) {
650 if (locationAware.getLineNumber() == -1) {
651 locationAware.setColumnNumber(columnNumber);
652 locationAware.setLineNumber(lineNumber);
653 }
654 if ( locationAware.getFileName() == null ) {
655 locationAware.setFileName( fileName );
656 }
657 if ( locationAware.getElementName() == null ) {
658 locationAware.setElementName( elementName );
659 }
660 }
661
662 /***
663 * A helper method to handle this non-Jelly exception.
664 * This method will rethrow the exception, wrapped in a JellyException
665 * while adding line number information etc.
666 */
667 protected void handleException(Exception e) throws JellyTagException {
668 if (log.isTraceEnabled()) {
669 log.trace( "Caught exception: " + e, e );
670 }
671
672 if (e instanceof LocationAware) {
673 applyLocation((LocationAware) e);
674 }
675
676 if ( e instanceof JellyException ) {
677 e.fillInStackTrace();
678 }
679
680 if ( e instanceof InvocationTargetException) {
681 throw new JellyTagException( ((InvocationTargetException)e).getTargetException(),
682 fileName,
683 elementName,
684 columnNumber,
685 lineNumber );
686 }
687
688 throw new JellyTagException(e, fileName, elementName, columnNumber, lineNumber);
689 }
690
691 /***
692 * A helper method to handle this non-Jelly exception.
693 * This method will rethrow the exception, wrapped in a JellyException
694 * while adding line number information etc.
695 *
696 * Is this method wise?
697 */
698 protected void handleException(Error e) throws Error, JellyTagException {
699 if (log.isTraceEnabled()) {
700 log.trace( "Caught exception: " + e, e );
701 }
702
703 if (e instanceof LocationAware) {
704 applyLocation((LocationAware) e);
705 }
706
707 throw new JellyTagException(e, fileName, elementName, columnNumber, lineNumber);
708 }
709 }
This page was automatically generated by Maven