1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.commons.jelly.parser;
17
18 import java.io.File;
19 import java.io.InputStream;
20 import java.io.IOException;
21 import java.io.Reader;
22 import java.net.URL;
23 import java.util.ArrayList;
24 import java.util.EmptyStackException;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.Map;
28 import java.util.Properties;
29
30 import javax.xml.parsers.SAXParser;
31 import javax.xml.parsers.SAXParserFactory;
32
33 import org.apache.commons.collections.ArrayStack;
34
35 import org.apache.commons.jelly.JellyContext;
36 import org.apache.commons.jelly.JellyException;
37 import org.apache.commons.jelly.Script;
38 import org.apache.commons.jelly.Tag;
39 import org.apache.commons.jelly.TagLibrary;
40 import org.apache.commons.jelly.impl.CompositeTextScriptBlock;
41 import org.apache.commons.jelly.impl.ExpressionScript;
42 import org.apache.commons.jelly.impl.StaticTag;
43 import org.apache.commons.jelly.impl.ScriptBlock;
44 import org.apache.commons.jelly.impl.StaticTagScript;
45 import org.apache.commons.jelly.impl.TagFactory;
46 import org.apache.commons.jelly.impl.TagScript;
47 import org.apache.commons.jelly.impl.TextScript;
48 import org.apache.commons.jelly.util.ClassLoaderUtils;
49 import org.apache.commons.jelly.expression.CompositeExpression;
50 import org.apache.commons.jelly.expression.ConstantExpression;
51 import org.apache.commons.jelly.expression.Expression;
52 import org.apache.commons.jelly.expression.ExpressionFactory;
53 import org.apache.commons.jelly.expression.jexl.JexlExpressionFactory;
54
55 import org.apache.commons.logging.Log;
56 import org.apache.commons.logging.LogFactory;
57
58 import org.xml.sax.Attributes;
59 import org.xml.sax.ErrorHandler;
60 import org.xml.sax.helpers.DefaultHandler;
61 import org.xml.sax.InputSource;
62 import org.xml.sax.Locator;
63 import org.xml.sax.SAXException;
64 import org.xml.sax.SAXParseException;
65 import org.xml.sax.XMLReader;
66 import org.xml.sax.helpers.AttributesImpl;
67
68 /*** <p><code>XMLParser</code> parses the XML Jelly format.
69 * The SAXParser and XMLReader portions of this code come from Digester.</p>
70 *
71 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
72 * @version $Revision: 219726 $
73 */
74 public class XMLParser extends DefaultHandler {
75
76 /***
77 * Share the Jelly properties across parsers
78 */
79 private static Properties jellyProperties;
80
81
82 /*** JellyContext which is used to locate tag libraries*/
83 private JellyContext context = new JellyContext();
84
85 /*** the expression factory used to evaluate tag attributes */
86 private ExpressionFactory expressionFactory;
87
88 /*** The current script block */
89 private ScriptBlock script;
90
91 /*** The current, parent tagScript */
92 private TagScript tagScript;
93
94 /*** The stack of body scripts. */
95 private ArrayStack scriptStack = new ArrayStack();
96
97 /*** The stack of tagScripts - use ArrayList as it allows null. */
98 private ArrayList tagScriptStack = new ArrayList();
99
100 /*** The current text buffer where non-custom tags get written */
101 private StringBuffer textBuffer;
102
103 /***
104 * The class loader to use for instantiating application objects.
105 * If not specified, the context class loader, or the class loader
106 * used to load XMLParser itself, is used, based on the value of the
107 * <code>useContextClassLoader</code> variable.
108 */
109 protected ClassLoader classLoader = null;
110
111 /***
112 * Do we want to use the Context ClassLoader when loading classes
113 * for instantiating new objects? Default is <code>false</code>.
114 */
115 protected boolean useContextClassLoader = false;
116
117 /***
118 * The application-supplied error handler that is notified when parsing
119 * warnings, errors, or fatal errors occur.
120 */
121 protected ErrorHandler errorHandler = null;
122
123 /***
124 * The SAXParserFactory that is created the first time we need it.
125 */
126 protected static SAXParserFactory factory = null;
127
128 /***
129 * The SAXParser we will use to parse the input stream.
130 */
131 protected SAXParser parser = null;
132
133 /***
134 * The XMLReader used to parse digester rules.
135 */
136 protected XMLReader reader = null;
137
138 /***
139 * The Locator associated with our parser.
140 */
141 protected Locator locator = null;
142
143 /***
144 * Registered namespaces we are currently processing. The key is the
145 * namespace prefix that was declared in the document. The value is an
146 * ArrayStack of the namespace URIs this prefix has been mapped to --
147 * the top Stack element is the most current one. (This architecture
148 * is required because documents can declare nested uses of the same
149 * prefix for different Namespace URIs).
150 */
151 protected Map namespaces = new HashMap();
152
153 /*** The Map of the namespace prefix -> URIs defined for the current element */
154 private Map elementNamespaces;
155
156 /***
157 * The name of the file being parsed that is passed to the TagScript objects
158 * for error reporting
159 */
160 private String fileName;
161
162 /***
163 * Do we want to use a validating parser?
164 */
165 protected boolean validating = false;
166
167 /*** Flag to indicate if this object has been configured */
168 private boolean configured;
169
170 /***
171 * when not null, set the default namespace for
172 * unprefixed elements via the DefaultNamespaceFilter
173 * class
174 */
175 private String defaultNamespaceURI = null;
176
177 /***
178 * The Log to which logging calls will be made.
179 */
180 private Log log = LogFactory.getLog(XMLParser.class);
181
182 /***
183 * Construct a new XMLParser with default properties.
184 */
185 public XMLParser() {
186 }
187
188 /***
189 * Construct a new XMLParser, allowing a SAXParser to be passed in. This
190 * allows XMLParser to be used in environments which are unfriendly to
191 * JAXP1.1 (such as WebLogic 6.0). Thanks for the request to change go to
192 * James House (james@interobjective.com). This may help in places where
193 * you are able to load JAXP 1.1 classes yourself.
194 */
195 public XMLParser(SAXParser parser) {
196 this.parser = parser;
197 }
198
199 /***
200 * Construct a new XMLParser, allowing an XMLReader to be passed in. This
201 * allows XMLParser to be used in environments which are unfriendly to
202 * JAXP1.1 (such as WebLogic 6.0). Note that if you use this option you
203 * have to configure namespace and validation support yourself, as these
204 * properties only affect the SAXParser and emtpy constructor.
205 */
206 public XMLParser(XMLReader reader) {
207 this.reader = reader;
208 }
209
210 /***
211 * Parse the content of the specified file using this XMLParser. Returns
212 * the root element from the object stack (if any).
213 *
214 * @param file File containing the XML data to be parsed
215 *
216 * @exception IOException if an input/output error occurs
217 * @exception SAXException if a parsing exception occurs
218 */
219 public Script parse(File file) throws IOException, SAXException {
220 return parse(file.toURL());
221 }
222
223 /***
224 * Parse the content of the specified file using this XMLParser. Returns
225 * the root element from the object stack (if any).
226 *
227 * @param url URL containing the XML data to be parsed
228 *
229 * @exception IOException if an input/output error occurs
230 * @exception SAXException if a parsing exception occurs
231 */
232 public Script parse(URL url) throws IOException, SAXException {
233 ensureConfigured();
234 this.fileName = url.toString();
235
236 InputSource source = new InputSource(url.toString());
237
238 getXMLReader().parse(source);
239 return script;
240 }
241
242 /***
243 * Parse the content of the specified input source using this XMLParser.
244 * Returns the root element from the object stack (if any).
245 *
246 * @param input Input source containing the XML data to be parsed
247 *
248 * @exception IOException if an input/output error occurs
249 * @exception SAXException if a parsing exception occurs
250 */
251 public Script parse(InputSource input) throws IOException, SAXException {
252 ensureConfigured();
253 this.fileName = input.getSystemId();
254 getXMLReader().parse(input);
255 return script;
256 }
257
258 /***
259 * Parse the content of the specified input stream using this XMLParser.
260 * Returns the root element from the object stack (if any).
261 * (Note: if reading a File or URL, use one of the URL-based
262 * parse methods instead. This method will not be able
263 * to resolve any relative paths inside a DTD.)
264 *
265 * @param input Input stream containing the XML data to be parsed
266 * @return
267 * @exception IOException
268 * if an input/output error occurs
269 * @exception SAXException
270 * if a parsing exception occurs
271 */
272 public Script parse(InputStream input) throws IOException, SAXException {
273 ensureConfigured();
274 this.fileName = getCurrentURI();
275 getXMLReader().parse(new InputSource(input));
276 return script;
277 }
278
279 /***
280 * Parse the content of the specified reader using this XMLParser.
281 * Returns the root element from the object stack (if any).
282 * (Note: if reading a File or URL, use one of the URL-based
283 * parse methods instead. This method will not be able
284 * to resolve any relative paths inside a DTD.)
285 *
286 * @param reader Reader containing the XML data to be parsed
287 * @return
288 * @exception IOException
289 * if an input/output error occurs
290 * @exception SAXException
291 * if a parsing exception occurs
292 */
293 public Script parse(Reader reader) throws IOException, SAXException {
294 ensureConfigured();
295 this.fileName = getCurrentURI();
296 getXMLReader().parse(new InputSource(reader));
297 return script;
298 }
299
300 /***
301 * Parse the content of the specified URI using this XMLParser.
302 * Returns the root element from the object stack (if any).
303 *
304 * @param uri URI containing the XML data to be parsed
305 *
306 * @exception IOException if an input/output error occurs
307 * @exception SAXException if a parsing exception occurs
308 */
309 public Script parse(String uri) throws IOException, SAXException {
310 ensureConfigured();
311 this.fileName = uri;
312 getXMLReader().parse(uri);
313 return script;
314 }
315
316 /***
317 * Return the currently mapped namespace URI for the specified prefix,
318 * if any; otherwise return <code>null</code>. These mappings come and
319 * go dynamically as the document is parsed.
320 *
321 * @param prefix Prefix to look up
322 */
323 public String findNamespaceURI(String prefix) {
324 ArrayStack stack = (ArrayStack) namespaces.get(prefix);
325 if (stack == null) {
326 return (null);
327 }
328 try {
329 return ((String) stack.peek());
330 }
331 catch (EmptyStackException e) {
332 return (null);
333 }
334 }
335
336
337
338 public JellyContext getContext() {
339 return context;
340 }
341
342 public void setContext(JellyContext context) {
343 this.context = context;
344 }
345
346 /***
347 * Set the jelly namespace to use for unprefixed elements.
348 * Will be overridden by an explicit namespace in the
349 * XML document.
350 *
351 * @param namespace jelly namespace to use (e.g. 'jelly:core')
352 */
353 public void setDefaultNamespaceURI(String namespace) {
354 this.defaultNamespaceURI = namespace;
355 }
356
357 /***
358 * Return the class loader to be used for instantiating application objects
359 * when required. This is determined based upon the following rules:
360 * <ul>
361 * <li>The class loader set by <code>setClassLoader()</code>, if any</li>
362 * <li>The thread context class loader, if it exists and the
363 * <code>useContextClassLoader</code> property is set to true</li>
364 * <li>The class loader used to load the XMLParser class itself.
365 * </ul>
366 */
367 public ClassLoader getClassLoader() {
368 return ClassLoaderUtils.getClassLoader(classLoader, useContextClassLoader, getClass());
369 }
370
371 /***
372 * Set the class loader to be used for instantiating application objects
373 * when required.
374 *
375 * @param classLoader The new class loader to use, or <code>null</code>
376 * to revert to the standard rules
377 */
378 public void setClassLoader(ClassLoader classLoader) {
379 this.classLoader = classLoader;
380 }
381
382 /***
383 * Return the boolean as to whether the context classloader should be used.
384 */
385 public boolean getUseContextClassLoader() {
386 return useContextClassLoader;
387 }
388
389 /***
390 * Determine whether to use the Context ClassLoader (the one found by
391 * calling <code>Thread.currentThread().getContextClassLoader()</code>)
392 * to resolve/load classes. If not
393 * using Context ClassLoader, then the class-loading defaults to
394 * using the calling-class' ClassLoader.
395 *
396 * @param use determines whether to use JellyContext ClassLoader.
397 */
398 public void setUseContextClassLoader(boolean use) {
399 useContextClassLoader = use;
400 }
401
402 /***
403 * Return the error handler for this XMLParser.
404 */
405 public ErrorHandler getErrorHandler() {
406 return (this.errorHandler);
407 }
408
409 /***
410 * Set the error handler for this XMLParser.
411 *
412 * @param errorHandler The new error handler
413 */
414 public void setErrorHandler(ErrorHandler errorHandler) {
415 this.errorHandler = errorHandler;
416 }
417
418 /***
419 * Return the current Logger associated with this instance of the XMLParser
420 */
421 public Log getLogger() {
422 return log;
423 }
424
425 /***
426 * Set the current logger for this XMLParser.
427 */
428 public void setLogger(Log log) {
429 this.log = log;
430 }
431
432 /*** @return the expression factory used to evaluate tag attributes */
433 public ExpressionFactory getExpressionFactory() {
434 if (expressionFactory == null) {
435 expressionFactory = createExpressionFactory();
436 }
437 return expressionFactory;
438 }
439
440 /*** Sets the expression factory used to evaluate tag attributes */
441 public void setExpressionFactory(ExpressionFactory expressionFactory) {
442 this.expressionFactory = expressionFactory;
443 }
444
445 /***
446 * Return the SAXParser we will use to parse the input stream. If there
447 * is a problem creating the parser, return <code>null</code>.
448 */
449 public SAXParser getParser() {
450
451 if (parser != null) {
452 return (parser);
453 }
454
455 synchronized (this) {
456 try {
457 if (factory == null) {
458 factory = SAXParserFactory.newInstance();
459 }
460 factory.setNamespaceAware(true);
461 factory.setValidating(validating);
462 parser = factory.newSAXParser();
463 return (parser);
464 }
465 catch (Exception e) {
466 log.error("XMLParser.getParser: ", e);
467 return (null);
468 }
469 }
470 }
471
472 /***
473 * By setting the reader in the constructor, you can bypass JAXP and
474 * be able to use digester in Weblogic 6.0.
475 *
476 * @deprecated Use getXMLReader() instead, which can throw a
477 * SAXException if the reader cannot be instantiated
478 */
479 public XMLReader getReader() {
480 try {
481 return (getXMLReader());
482 }
483 catch (SAXException e) {
484 log.error("Cannot get XMLReader", e);
485 return (null);
486 }
487 }
488
489 /***
490 * Return the XMLReader to be used for parsing the input document.
491 *
492 * @exception SAXException if no XMLReader can be instantiated
493 */
494 public synchronized XMLReader getXMLReader() throws SAXException {
495 if (reader == null) {
496 reader = getParser().getXMLReader();
497 if (this.defaultNamespaceURI != null) {
498 reader = new DefaultNamespaceFilter(this.defaultNamespaceURI,reader);
499 }
500 }
501
502 reader.setContentHandler(this);
503 reader.setDTDHandler(this);
504
505 reader.setErrorHandler(this);
506
507 return reader;
508 }
509
510 /***
511 * Return the validating parser flag.
512 */
513 public boolean getValidating() {
514 return (this.validating);
515 }
516
517 /***
518 * Set the validating parser flag. This must be called before
519 * <code>parse()</code> is called the first time.
520 *
521 * @param validating The new validating parser flag.
522 */
523 public void setValidating(boolean validating) {
524 this.validating = validating;
525 }
526
527
528 /***
529 * Returns the script that has just been created if this class is used
530 * as a SAX ContentHandler and passed into some XML processor or parser.
531 *
532 * @return the ScriptBlock created if SAX events are piped into this class,
533 * which must include a startDocument() and endDocument()
534 */
535 public ScriptBlock getScript() {
536 return script;
537 }
538
539
540
541
542 /***
543 * Process notification of the beginning of the document being reached.
544 *
545 * @exception SAXException if a parsing error is to be reported
546 */
547 public void startDocument() throws SAXException {
548 script = new ScriptBlock();
549 textBuffer = new StringBuffer();
550 tagScript = null;
551 scriptStack.clear();
552 tagScriptStack.clear();
553 }
554
555 /***
556 * Process notification of the end of the document being reached.
557 *
558 * @exception SAXException if a parsing error is to be reported
559 */
560 public void endDocument() throws SAXException {
561 textBuffer = null;
562 }
563
564 /***
565 * Process notification of the start of an XML element being reached.
566 *
567 * @param namespaceURI The Namespace URI, or the empty string if the
568 * element has no Namespace URI or if Namespace processing is not
569 * being performed.
570 * @param localName The local name (without prefix), or the empty
571 * string if Namespace processing is not being performed.
572 * @param qName The qualified name (with prefix), or the empty
573 * string if qualified names are not available.\
574 * @param list The attributes attached to the element. If there are
575 * no attributes, it shall be an empty Attributes object.
576 * @exception SAXException if a parsing error is to be reported
577 */
578 public void startElement(
579 String namespaceURI,
580 String localName,
581 String qName,
582 Attributes list)
583 throws SAXException {
584
585 try {
586
587 if ( namespaceURI == null ) {
588 namespaceURI = "";
589 }
590
591
592
593 TagScript newTagScript = createTag(namespaceURI, localName, list);
594 if (newTagScript == null) {
595 newTagScript = createStaticTag(namespaceURI, localName, qName, list);
596 }
597 tagScript = newTagScript;
598 tagScriptStack.add(tagScript);
599 if (tagScript != null) {
600
601 if ( locator != null ) {
602 tagScript.setLocator(locator);
603 }
604
605 tagScript.setFileName(fileName);
606 tagScript.setElementName(qName);
607 tagScript.setLocalName(localName);
608
609 if (textBuffer.length() > 0) {
610 addTextScript(textBuffer.toString());
611 textBuffer.setLength(0);
612 }
613 script.addScript(tagScript);
614
615 scriptStack.push(script);
616 script = new ScriptBlock();
617 tagScript.setTagBody(script);
618 }
619 else {
620
621 textBuffer.append("<");
622 textBuffer.append(qName);
623 int size = list.getLength();
624 for (int i = 0; i < size; i++) {
625 textBuffer.append(" ");
626 textBuffer.append(list.getQName(i));
627 textBuffer.append("=");
628 textBuffer.append("\"");
629 textBuffer.append(list.getValue(i));
630 textBuffer.append("\"");
631 }
632 textBuffer.append(">");
633 }
634 }
635 catch (SAXException e) {
636 throw e;
637 }
638 catch (Exception e) {
639 log.error( "Caught exception: " + e, e );
640 throw new SAXException( "Runtime Exception: " + e, e );
641 }
642 }
643
644 /***
645 * Process notification of character data received from the body of
646 * an XML element.
647 *
648 * @param buffer The characters from the XML document
649 * @param start Starting offset into the buffer
650 * @param length Number of characters from the buffer
651 *
652 * @exception SAXException if a parsing error is to be reported
653 */
654 public void characters(char buffer[], int start, int length)
655 throws SAXException {
656 textBuffer.append(buffer, start, length);
657 }
658
659 /***
660 * Process notification of the end of an XML element being reached.
661 *
662 * @param namespaceURI The Namespace URI, or the empty string if the
663 * element has no Namespace URI or if Namespace processing is not
664 * being performed.
665 * @param localName The local name (without prefix), or the empty
666 * string if Namespace processing is not being performed.
667 * @param qName The qualified XML 1.0 name (with prefix), or the
668 * empty string if qualified names are not available.
669 * @exception SAXException if a parsing error is to be reported
670 */
671 public void endElement(String namespaceURI, String localName, String qName)
672 throws SAXException {
673 try {
674 tagScript = (TagScript) tagScriptStack.remove(tagScriptStack.size() - 1);
675 if (tagScript != null) {
676 if (textBuffer.length() > 0) {
677 addTextScript(textBuffer.toString());
678 textBuffer.setLength(0);
679 }
680 script = (ScriptBlock) scriptStack.pop();
681 }
682 else {
683 textBuffer.append("</");
684 textBuffer.append(qName);
685 textBuffer.append(">");
686 }
687
688
689 if ( tagScriptStack.isEmpty() ) {
690 tagScript = null;
691 }
692 else {
693 tagScript = (TagScript) tagScriptStack.get(tagScriptStack.size() - 1);
694 }
695 } catch (Exception e) {
696 log.error( "Caught exception: " + e, e );
697 throw new SAXException( "Runtime Exception: " + e, e );
698 }
699 }
700
701 /***
702 * Process notification that a namespace prefix is coming in to scope.
703 *
704 * @param prefix Prefix that is being declared
705 * @param namespaceURI Corresponding namespace URI being mapped to
706 *
707 * @exception SAXException if a parsing error is to be reported
708 */
709 public void startPrefixMapping(String prefix, String namespaceURI)
710 throws SAXException {
711
712 ArrayStack stack = (ArrayStack) namespaces.get(prefix);
713 if (stack == null) {
714 stack = new ArrayStack();
715 namespaces.put(prefix, stack);
716 }
717 stack.push(namespaceURI);
718
719 if ( elementNamespaces == null ) {
720 elementNamespaces = new HashMap();
721 }
722 elementNamespaces.put(prefix, namespaceURI);
723 }
724
725 /***
726 * Process notification that a namespace prefix is going out of scope.
727 *
728 * @param prefix Prefix that is going out of scope
729 *
730 * @exception SAXException if a parsing error is to be reported
731 */
732 public void endPrefixMapping(String prefix) throws SAXException {
733
734 ArrayStack stack = (ArrayStack) namespaces.get(prefix);
735 if (stack == null) {
736 return;
737 }
738 try {
739 stack.pop();
740 if (stack.empty()) {
741 namespaces.remove(prefix);
742 }
743 }
744 catch (EmptyStackException e) {
745 throw createSAXException("endPrefixMapping popped too many times");
746 }
747 }
748
749 /***
750 * Process notification of ignorable whitespace received from the body of
751 * an XML element.
752 *
753 * @param buffer The characters from the XML document
754 * @param start Starting offset into the buffer
755 * @param len Number of characters from the buffer
756 *
757 * @exception SAXException if a parsing error is to be reported
758 */
759 public void ignorableWhitespace(char buffer[], int start, int len)
760 throws SAXException {
761 ;
762 }
763
764 /***
765 * Process notification of a processing instruction that was encountered.
766 *
767 * @param target The processing instruction target
768 * @param data The processing instruction data (if any)
769 *
770 * @exception SAXException if a parsing error is to be reported
771 */
772 public void processingInstruction(String target, String data)
773 throws SAXException {
774 ;
775 }
776
777 /***
778 * Set the document locator associated with our parser.
779 *
780 * @param locator The new locator
781 */
782 public void setDocumentLocator(Locator locator) {
783 this.locator = locator;
784 }
785
786 /***
787 * Process notification of a skipped entity.
788 *
789 * @param name Name of the skipped entity
790 *
791 * @exception SAXException if a parsing error is to be reported
792 */
793 public void skippedEntity(String name) throws SAXException {
794 ;
795 }
796
797
798
799
800
801 /***
802 * Receive notification of a notation declaration event.
803 *
804 * @param name The notation name
805 * @param publicId The public identifier (if any)
806 * @param systemId The system identifier (if any)
807 */
808 public void notationDecl(String name, String publicId, String systemId) {
809 }
810
811 /***
812 * Receive notification of an unparsed entity declaration event.
813 *
814 * @param name The unparsed entity name
815 * @param publicId The public identifier (if any)
816 * @param systemId The system identifier (if any)
817 * @param notation The name of the associated notation
818 */
819 public void unparsedEntityDecl(
820 String name,
821 String publicId,
822 String systemId,
823 String notation) {
824 }
825
826
827
828
829
830 /***
831 * Forward notification of a parsing error to the application supplied
832 * error handler, if any, otherwise throw a SAXException with the error.
833 *
834 * @param exception The error information
835 *
836 * @exception SAXException if a parsing exception occurs
837 */
838 public void error(SAXParseException exception) throws SAXException {
839 log.error(
840 "Parse Error at line "
841 + exception.getLineNumber()
842 + " column "
843 + exception.getColumnNumber()
844 + ": "
845 + exception.getMessage(),
846 exception);
847 if (errorHandler != null) {
848 errorHandler.error(exception);
849 } else {
850 throw exception;
851 }
852 }
853
854 /***
855 * Forward notification of a fatal parsing error to the application
856 * supplied error handler, if any, otherwise throw a SAXException with the error.
857 *
858 * @param exception The fatal error information
859 *
860 * @exception SAXException if a parsing exception occurs
861 */
862 public void fatalError(SAXParseException exception) throws SAXException {
863 log.error(
864 "Parse Fatal Error at line "
865 + exception.getLineNumber()
866 + " column "
867 + exception.getColumnNumber()
868 + ": "
869 + exception.getMessage(),
870 exception);
871 if (errorHandler != null) {
872 errorHandler.fatalError(exception);
873 } else {
874 throw exception;
875 }
876 }
877
878 /***
879 * Forward notification of a parse warning to the application supplied
880 * error handler (if any). Unlike XMLParser.error(SAXParseException) and
881 * XMLParser.fatalError(SAXParseException), this implementation will
882 * NOT throw a SAXException by default if no error handler is supplied.
883 *
884 * @param exception The warning information
885 *
886 * @exception SAXException if a parsing exception occurs
887 */
888 public void warning(SAXParseException exception) throws SAXException {
889 log.error(
890 "Parse Warning at line "
891 + exception.getLineNumber()
892 + " column "
893 + exception.getColumnNumber()
894 + ": "
895 + exception.getMessage(),
896 exception);
897 if (errorHandler != null) {
898 errorHandler.warning(exception);
899 }
900 }
901
902
903
904 /***
905 * If this object has not been configured then register the default
906 * namespaces
907 */
908 private void ensureConfigured() {
909 if (!configured) {
910 configure();
911 configured = true;
912 }
913 }
914
915 /***
916 * This method is called only once before parsing occurs
917 * which allows tag libraries to be registered and so forth
918 */
919 protected void configure() {
920
921 Properties properties = getJellyProperties();
922 for (Iterator iter = properties.entrySet().iterator(); iter.hasNext();) {
923 Map.Entry entry = (Map.Entry) iter.next();
924 String uri = (String) entry.getKey();
925 String className = (String) entry.getValue();
926 String libraryURI = "jelly:" + uri;
927
928
929 if ( ! context.isTagLibraryRegistered(libraryURI) ) {
930 context.registerTagLibrary(libraryURI, className);
931 }
932 }
933 }
934
935
936 /***
937 * A helper method which loads the static Jelly properties once on startup
938 */
939 protected synchronized Properties getJellyProperties() {
940 if (jellyProperties == null) {
941 jellyProperties = new Properties();
942
943 InputStream in = null;
944 URL url =
945 getClassLoader().getResource("org/apache/commons/jelly/jelly.properties");
946 if (url != null) {
947 log.debug("Loading Jelly default tag libraries from: " + url);
948 try {
949 in = url.openStream();
950 jellyProperties .load(in);
951 }
952 catch (IOException e) {
953 log.error("Could not load jelly properties from: " + url + ". Reason: " + e, e);
954 }
955 finally {
956 try {
957 in.close();
958 }
959 catch (Exception e) {
960 if (log.isDebugEnabled()) log.debug("error closing jelly.properties", e);
961 }
962 }
963 }
964 }
965 return jellyProperties;
966 }
967
968 /***
969 * Factory method to create new Tag script for the given namespaceURI and name or
970 * return null if this is not a custom Tag.
971 */
972 protected TagScript createTag(
973 String namespaceURI,
974 String localName,
975 Attributes list)
976 throws SAXException {
977 try {
978
979 TagLibrary taglib = context.getTagLibrary(namespaceURI);
980 if (taglib == null) {
981 if (namespaceURI != null && namespaceURI.startsWith("jelly:")) {
982 String uri = namespaceURI.substring(6);
983
984 try {
985 Class taglibClass = getClassLoader().loadClass(uri);
986 taglib = (TagLibrary) taglibClass.newInstance();
987 context.registerTagLibrary(namespaceURI, taglib);
988 }
989 catch (ClassNotFoundException e) {
990 throw createSAXException("Could not load class: " + uri + " so taglib instantiation failed", e);
991 }
992 catch (IllegalAccessException e) {
993 throw createSAXException("Constructor for class is not accessible: " + uri + " so taglib instantiation failed",e);
994 }
995 catch (InstantiationException e) {
996 throw createSAXException("Class could not be instantiated: " + uri + " so taglib instantiation failed",e);
997 }
998 catch (ClassCastException e) {
999 throw createSAXException("Class is not a TagLibrary: " + uri + " so taglib instantiation failed",e);
1000 }
1001 }
1002 }
1003 if (taglib != null) {
1004 TagScript script = taglib.createTagScript(localName, list);
1005 if ( script != null ) {
1006 configureTagScript(script);
1007
1008
1009 script.setSaxAttributes(new AttributesImpl(list));
1010
1011
1012 int size = list.getLength();
1013 for (int i = 0; i < size; i++) {
1014 String attributeName = list.getLocalName(i);
1015 String attributeValue = list.getValue(i);
1016 Expression expression =
1017 taglib.createExpression(
1018 getExpressionFactory(),
1019 script,
1020 attributeName,
1021 attributeValue);
1022 if (expression == null) {
1023 expression = createConstantExpression(localName, attributeName, attributeValue);
1024 }
1025 script.addAttribute(attributeName, expression);
1026 }
1027 }
1028 return script;
1029 }
1030 return null;
1031 }
1032 catch (Exception e) {
1033 log.warn(
1034 "Could not create taglib or URI: " + namespaceURI + " tag name: " + localName,
1035 e);
1036 throw createSAXException(e);
1037 }
1038 }
1039
1040
1041 /***
1042 * Factory method to create a static Tag that represents some static content.
1043 */
1044 protected TagScript createStaticTag(
1045 final String namespaceURI,
1046 final String localName,
1047 final String qName,
1048 Attributes list)
1049 throws SAXException {
1050 try {
1051 StaticTag tag = new StaticTag( namespaceURI, localName, qName );
1052 StaticTagScript script = new StaticTagScript(
1053 new TagFactory() {
1054 public Tag createTag(String name, Attributes attributes) {
1055 return new StaticTag( namespaceURI, localName, qName );
1056 }
1057 }
1058 );
1059 configureTagScript(script);
1060
1061
1062 int size = list.getLength();
1063 for (int i = 0; i < size; i++) {
1064 String attributeValue = list.getValue(i);
1065 Expression expression = CompositeExpression.parse(
1066 attributeValue, getExpressionFactory()
1067 );
1068 String attrQName = list.getQName(i);
1069 int p = attrQName.indexOf(':');
1070 String prefix = p>=0 ?
1071 attrQName.substring(0,p):
1072 "";
1073 script.addAttribute(list.getLocalName(i),
1074 prefix, list.getURI(i), expression);
1075 }
1076 return script;
1077 }
1078 catch (Exception e) {
1079 log.warn(
1080 "Could not create static tag for URI: "
1081 + namespaceURI
1082 + " tag name: "
1083 + localName,
1084 e);
1085 throw createSAXException(e);
1086 }
1087 }
1088
1089
1090 /***
1091 * Configure a newly created TagScript instance before any Expressions are created
1092 *
1093 * @param aTagScript
1094 */
1095 protected void configureTagScript(TagScript aTagScript) {
1096
1097 aTagScript.setParent(this.tagScript);
1098
1099
1100 if ( elementNamespaces != null ) {
1101 aTagScript.setTagNamespacesMap( elementNamespaces );
1102 elementNamespaces = null;
1103 }
1104 }
1105
1106 /***
1107 * Adds the text to the current script block parsing any embedded
1108 * expressions inot ExpressionScript objects.
1109 */
1110 protected void addTextScript(String text) throws JellyException {
1111 Expression expression =
1112 CompositeExpression.parse(text, getExpressionFactory());
1113
1114 addExpressionScript(script, expression);
1115 }
1116
1117
1118 /***
1119 * Adds the given Expression object to the current Script.
1120 */
1121 protected void addExpressionScript(ScriptBlock script, Expression expression) {
1122 if ( expression instanceof ConstantExpression ) {
1123 ConstantExpression constantExpression
1124 = (ConstantExpression) expression;
1125 Object value = constantExpression.getValue();
1126 if ( value != null ) {
1127 script.addScript(new TextScript( value.toString() ));
1128 }
1129 }
1130 else
1131 if ( expression instanceof CompositeExpression ) {
1132 CompositeTextScriptBlock newBlock = new CompositeTextScriptBlock();
1133 script.addScript(newBlock);
1134
1135 CompositeExpression compositeExpression
1136 = (CompositeExpression) expression;
1137 Iterator iter = compositeExpression.getExpressions().iterator();
1138 while (iter.hasNext()) {
1139 addExpressionScript( newBlock, (Expression) iter.next() );
1140 }
1141 }
1142 else {
1143 script.addScript(new ExpressionScript(expression));
1144 }
1145 }
1146
1147 protected Expression createConstantExpression(
1148 String tagName,
1149 String attributeName,
1150 String attributeValue) {
1151 return new ConstantExpression(attributeValue);
1152 }
1153
1154 protected ExpressionFactory createExpressionFactory() {
1155 return new JexlExpressionFactory();
1156 }
1157
1158 /***
1159 * @return the current context URI as a String or null if there is no
1160 * current context defined on the JellyContext
1161 */
1162 protected String getCurrentURI() {
1163 URL url = this.getContext().getCurrentURL();
1164 return (url != null) ? url.toString() : null;
1165 }
1166
1167 /***
1168 * Create a SAX exception which also understands about the location in
1169 * the file where the exception occurs
1170 *
1171 * @return the new exception
1172 */
1173 protected SAXException createSAXException(String message, Exception e) {
1174 log.warn("Underlying exception: " + e);
1175 e.printStackTrace();
1176 if (locator != null) {
1177 String error =
1178 "Error at ("
1179 + locator.getLineNumber()
1180 + ", "
1181 + locator.getColumnNumber()
1182 + "): "
1183 + message;
1184 if (e != null) {
1185 return new SAXParseException(error, locator, e);
1186 }
1187 else {
1188 return new SAXParseException(error, locator);
1189 }
1190 }
1191 log.error("No Locator!");
1192 if (e != null) {
1193 return new SAXException(message, e);
1194 }
1195 else {
1196 return new SAXException(message);
1197 }
1198 }
1199
1200 /***
1201 * Create a SAX exception which also understands about the location in
1202 * the digester file where the exception occurs
1203 *
1204 * @return the new exception
1205 */
1206 protected SAXException createSAXException(Exception e) {
1207 return createSAXException(e.getMessage(), e);
1208 }
1209 /***
1210 * Create a SAX exception which also understands about the location in
1211 * the digester file where the exception occurs
1212 *
1213 * @return the new exception
1214 */
1215 protected SAXException createSAXException(String message) {
1216 return createSAXException(message, null);
1217 }
1218 }