View Javadoc
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