View Javadoc

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.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 -&gt; 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     // Properties
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         // Return the parser we already created (if any)
451         if (parser != null) {
452             return (parser);
453         }
454         // Create and return a new parser
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         //set up the parse
502         reader.setContentHandler(this);
503         reader.setDTDHandler(this);
504         //reader.setEntityResolver(this);
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     // ContentHandler interface
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             // add check to ensure namespace URI is "" for no namespace
587             if ( namespaceURI == null ) {
588                 namespaceURI = "";
589             }
590 
591             // if this is a tag then create a script to run it
592             // otherwise pass the text to the current body
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                 // set the line number details
601                 if ( locator != null ) {
602                     tagScript.setLocator(locator);
603                 }
604                 // sets the file name element names
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                 // start a new body
615                 scriptStack.push(script);
616                 script = new ScriptBlock();
617                 tagScript.setTagBody(script);
618             }
619             else {
620                 // XXXX: might wanna handle empty elements later...
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             // now lets set the parent tag variable
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         // Register this prefix mapping
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         // Deregister this prefix mapping
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         ; // No processing required
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         ; // No processing is required
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         ; // No processing required
795     }
796 
797 
798     // DTDHandler interface
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     // ErrorHandler interface
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     // Implementation methods
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         // load the properties file of libraries available
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             // don't overload any Mock Tags already
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             // use the URI to load a taglib
979             TagLibrary taglib = context.getTagLibrary(namespaceURI);
980             if (taglib == null) {
981                 if (namespaceURI != null && namespaceURI.startsWith("jelly:")) {
982                     String uri = namespaceURI.substring(6);
983                     // try to find the class on the claspath
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                     // clone the attributes to keep them around after this parse
1009                     script.setSaxAttributes(new AttributesImpl(list));
1010 
1011                     // now iterate through through the expressions
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             // now iterate through through the expressions
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         // set parent relationship...
1097         aTagScript.setParent(this.tagScript);
1098 
1099         // set the namespace Map
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 }