View Javadoc

1   // ========================================================================
2   // Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.xml;
15  
16  import java.io.File;
17  import java.io.IOException;
18  import java.io.InputStream;
19  import java.net.URL;
20  import java.util.AbstractList;
21  import java.util.ArrayList;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.Map;
25  import java.util.NoSuchElementException;
26  import java.util.Stack;
27  import java.util.StringTokenizer;
28  
29  import javax.xml.parsers.SAXParser;
30  import javax.xml.parsers.SAXParserFactory;
31  
32  import org.eclipse.jetty.util.LazyList;
33  import org.eclipse.jetty.util.log.Log;
34  import org.xml.sax.Attributes;
35  import org.xml.sax.ContentHandler;
36  import org.xml.sax.InputSource;
37  import org.xml.sax.SAXException;
38  import org.xml.sax.SAXParseException;
39  import org.xml.sax.XMLReader;
40  import org.xml.sax.helpers.DefaultHandler;
41  
42  /*--------------------------------------------------------------*/
43  /**
44   * XML Parser wrapper. This class wraps any standard JAXP1.1 parser with convieniant error and
45   * entity handlers and a mini dom-like document tree.
46   * <P>
47   * By default, the parser is created as a validating parser only if xerces is present. This can be 
48   * configured by setting the "org.eclipse.jetty.xml.XmlParser.Validating" system property.
49   * 
50   * 
51   */
52  public class XmlParser
53  {
54      private Map _redirectMap = new HashMap();
55      private SAXParser _parser;
56      private Map _observerMap;
57      private Stack _observers = new Stack();
58      private String _xpath;
59      private Object _xpaths;
60      private String _dtd;
61  
62      /* ------------------------------------------------------------ */
63      /**
64       * Construct
65       */
66      public XmlParser()
67      {
68          SAXParserFactory factory = SAXParserFactory.newInstance();
69          boolean validating_dft = factory.getClass().toString().startsWith("org.apache.xerces.");
70          String validating_prop = System.getProperty("org.eclipse.jetty.xml.XmlParser.Validating", validating_dft ? "true" : "false");
71          boolean validating = Boolean.valueOf(validating_prop).booleanValue();
72  
73          setValidating(validating);
74      }
75  
76      /* ------------------------------------------------------------ */
77      /**
78       * Constructor.
79       */
80      public XmlParser(boolean validating)
81      {
82          setValidating(validating);
83      }
84      
85      /* ------------------------------------------------------------ */
86      public void setValidating(boolean validating)
87      {
88          try
89          {
90              SAXParserFactory factory = SAXParserFactory.newInstance();
91              factory.setValidating(validating);
92              _parser = factory.newSAXParser();
93              
94              try
95              {
96                  if (validating)
97                      _parser.getXMLReader().setFeature("http://apache.org/xml/features/validation/schema", validating);
98              }
99              catch (Exception e)
100             {
101                 if (validating)
102                     Log.warn("Schema validation may not be supported: ", e);
103                 else
104                     Log.ignore(e);
105             }
106 
107             _parser.getXMLReader().setFeature("http://xml.org/sax/features/validation", validating);
108             _parser.getXMLReader().setFeature("http://xml.org/sax/features/namespaces", true);
109             _parser.getXMLReader().setFeature("http://xml.org/sax/features/namespace-prefixes", false);  
110         }
111         catch (Exception e)
112         {
113             Log.warn(Log.EXCEPTION, e);
114             throw new Error(e.toString());
115         }
116     }
117     
118     /* ------------------------------------------------------------ */
119     /**
120      * @param name
121      * @param entity
122      */
123     public synchronized void redirectEntity(String name, URL entity)
124     {
125         if (entity != null)
126             _redirectMap.put(name, entity);
127     }
128 
129     /* ------------------------------------------------------------ */
130     /**
131      * 
132      * @return Returns the xpath.
133      */
134     public String getXpath()
135     {
136         return _xpath;
137     }
138 
139     /* ------------------------------------------------------------ */
140     /**
141      * Set an XPath A very simple subset of xpath is supported to select a partial tree. Currently
142      * only path like "/node1/nodeA | /node1/nodeB" are supported.
143      * 
144      * @param xpath The xpath to set.
145      */
146     public void setXpath(String xpath)
147     {
148         _xpath = xpath;
149         StringTokenizer tok = new StringTokenizer(xpath, "| ");
150         while (tok.hasMoreTokens())
151             _xpaths = LazyList.add(_xpaths, tok.nextToken());
152     }
153 
154     /* ------------------------------------------------------------ */
155     public String getDTD()
156     {
157         return _dtd;
158     }
159 
160     /* ------------------------------------------------------------ */
161     /**
162      * Add a ContentHandler. Add an additional _content handler that is triggered on a tag name. SAX
163      * events are passed to the ContentHandler provided from a matching start element to the
164      * corresponding end element. Only a single _content handler can be registered against each tag.
165      * 
166      * @param trigger Tag local or q name.
167      * @param observer SAX ContentHandler
168      */
169     public synchronized void addContentHandler(String trigger, ContentHandler observer)
170     {
171         if (_observerMap == null)
172             _observerMap = new HashMap();
173         _observerMap.put(trigger, observer);
174     }
175 
176     /* ------------------------------------------------------------ */
177     public synchronized Node parse(InputSource source) throws IOException, SAXException
178     {
179         _dtd=null;
180         Handler handler = new Handler();
181         XMLReader reader = _parser.getXMLReader();
182         reader.setContentHandler(handler);
183         reader.setErrorHandler(handler);
184         reader.setEntityResolver(handler);
185         if (Log.isDebugEnabled())
186             Log.debug("parsing: sid=" + source.getSystemId() + ",pid=" + source.getPublicId());
187         _parser.parse(source, handler);
188         if (handler._error != null)
189             throw handler._error;
190         Node doc = (Node) handler._top.get(0);
191         handler.clear();
192         return doc;
193     }
194 
195     /* ------------------------------------------------------------ */
196     /**
197      * Parse String URL.
198      */
199     public synchronized Node parse(String url) throws IOException, SAXException
200     {
201         if (Log.isDebugEnabled())
202             Log.debug("parse: " + url);
203         return parse(new InputSource(url));
204     }
205 
206     /* ------------------------------------------------------------ */
207     /**
208      * Parse File.
209      */
210     public synchronized Node parse(File file) throws IOException, SAXException
211     {
212         if (Log.isDebugEnabled())
213             Log.debug("parse: " + file);
214         return parse(new InputSource(file.toURL().toString()));
215     }
216 
217     /* ------------------------------------------------------------ */
218     /**
219      * Parse InputStream.
220      */
221     public synchronized Node parse(InputStream in) throws IOException, SAXException
222     {
223         _dtd=null;
224         Handler handler = new Handler();
225         XMLReader reader = _parser.getXMLReader();
226         reader.setContentHandler(handler);
227         reader.setErrorHandler(handler);
228         reader.setEntityResolver(handler);
229         _parser.parse(new InputSource(in), handler);
230         if (handler._error != null)
231             throw handler._error;
232         Node doc = (Node) handler._top.get(0);
233         handler.clear();
234         return doc;
235     }
236 
237     /* ------------------------------------------------------------ */
238     /* ------------------------------------------------------------ */
239     private class NoopHandler extends DefaultHandler
240     {
241         Handler _next;
242         int _depth;
243 
244         NoopHandler(Handler next)
245         {
246             this._next = next;
247         }
248 
249         /* ------------------------------------------------------------ */
250         public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException
251         {
252             _depth++;
253         }
254 
255         /* ------------------------------------------------------------ */
256         public void endElement(String uri, String localName, String qName) throws SAXException
257         {
258             if (_depth == 0)
259                 _parser.getXMLReader().setContentHandler(_next);
260             else
261                 _depth--;
262         }
263     }
264     
265     /* ------------------------------------------------------------ */
266     /* ------------------------------------------------------------ */
267     private class Handler extends DefaultHandler
268     {
269         Node _top = new Node(null, null, null);
270         SAXParseException _error;
271         private Node _context = _top;
272         private NoopHandler _noop;
273 
274         Handler()
275         {
276             _noop = new NoopHandler(this);
277         }
278 
279         /* ------------------------------------------------------------ */
280         void clear()
281         {
282             _top = null;
283             _error = null;
284             _context = null;
285         }
286 
287         /* ------------------------------------------------------------ */
288         public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException
289         {
290             String name = (uri == null || uri.equals("")) ? qName : localName;
291             Node node = new Node(_context, name, attrs);
292             
293 
294             // check if the node matches any xpaths set?
295             if (_xpaths != null)
296             {
297                 String path = node.getPath();
298                 boolean match = false;
299                 for (int i = LazyList.size(_xpaths); !match && i-- > 0;)
300                 {
301                     String xpath = (String) LazyList.get(_xpaths, i);
302 
303                     match = path.equals(xpath) || xpath.startsWith(path) && xpath.length() > path.length() && xpath.charAt(path.length()) == '/';
304                 }
305 
306                 if (match)
307                 {
308                     _context.add(node);
309                     _context = node;
310                 }
311                 else
312                 {
313                     _parser.getXMLReader().setContentHandler(_noop);
314                 }
315             }
316             else
317             {
318                 _context.add(node);
319                 _context = node;
320             }
321 
322             ContentHandler observer = null;
323             if (_observerMap != null)
324                 observer = (ContentHandler) _observerMap.get(name);
325             _observers.push(observer);
326 
327             for (int i = 0; i < _observers.size(); i++)
328                 if (_observers.get(i) != null)
329                     ((ContentHandler) _observers.get(i)).startElement(uri, localName, qName, attrs);
330         }
331 
332         /* ------------------------------------------------------------ */
333         public void endElement(String uri, String localName, String qName) throws SAXException
334         {
335             _context = _context._parent;
336             for (int i = 0; i < _observers.size(); i++)
337                 if (_observers.get(i) != null)
338                     ((ContentHandler) _observers.get(i)).endElement(uri, localName, qName);
339             _observers.pop();
340         }
341 
342         /* ------------------------------------------------------------ */
343         public void ignorableWhitespace(char buf[], int offset, int len) throws SAXException
344         {
345             for (int i = 0; i < _observers.size(); i++)
346                 if (_observers.get(i) != null)
347                     ((ContentHandler) _observers.get(i)).ignorableWhitespace(buf, offset, len);
348         }
349 
350         /* ------------------------------------------------------------ */
351         public void characters(char buf[], int offset, int len) throws SAXException
352         {
353             _context.add(new String(buf, offset, len));
354             for (int i = 0; i < _observers.size(); i++)
355                 if (_observers.get(i) != null)
356                     ((ContentHandler) _observers.get(i)).characters(buf, offset, len);
357         }
358 
359         /* ------------------------------------------------------------ */
360         public void warning(SAXParseException ex)
361         {
362             Log.debug(Log.EXCEPTION, ex);
363             Log.warn("WARNING@" + getLocationString(ex) + " : " + ex.toString());
364         }
365 
366         /* ------------------------------------------------------------ */
367         public void error(SAXParseException ex) throws SAXException
368         {
369             // Save error and continue to report other errors
370             if (_error == null)
371                 _error = ex;
372             Log.debug(Log.EXCEPTION, ex);
373             Log.warn("ERROR@" + getLocationString(ex) + " : " + ex.toString());
374         }
375 
376         /* ------------------------------------------------------------ */
377         public void fatalError(SAXParseException ex) throws SAXException
378         {
379             _error = ex;
380             Log.debug(Log.EXCEPTION, ex);
381             Log.warn("FATAL@" + getLocationString(ex) + " : " + ex.toString());
382             throw ex;
383         }
384 
385         /* ------------------------------------------------------------ */
386         private String getLocationString(SAXParseException ex)
387         {
388             return ex.getSystemId() + " line:" + ex.getLineNumber() + " col:" + ex.getColumnNumber();
389         }
390 
391         /* ------------------------------------------------------------ */
392         public InputSource resolveEntity(String pid, String sid)
393         {
394             if (Log.isDebugEnabled())
395                 Log.debug("resolveEntity(" + pid + ", " + sid + ")");
396 
397             if (sid!=null && sid.endsWith(".dtd"))
398                 _dtd=sid;
399             
400             URL entity = null;
401             if (pid != null)
402                 entity = (URL) _redirectMap.get(pid);
403             if (entity == null)
404                 entity = (URL) _redirectMap.get(sid);
405             if (entity == null)
406             {
407                 String dtd = sid;
408                 if (dtd.lastIndexOf('/') >= 0)
409                     dtd = dtd.substring(dtd.lastIndexOf('/') + 1);
410 
411                 if (Log.isDebugEnabled())
412                     Log.debug("Can't exact match entity in redirect map, trying " + dtd);
413                 entity = (URL) _redirectMap.get(dtd);
414             }
415 
416             if (entity != null)
417             {
418                 try
419                 {
420                     InputStream in = entity.openStream();
421                     if (Log.isDebugEnabled())
422                         Log.debug("Redirected entity " + sid + " --> " + entity);
423                     InputSource is = new InputSource(in);
424                     is.setSystemId(sid);
425                     return is;
426                 }
427                 catch (IOException e)
428                 {
429                     Log.ignore(e);
430                 }
431             }
432             return null;
433         }
434     }
435 
436     /* ------------------------------------------------------------ */
437     /* ------------------------------------------------------------ */
438     /**
439      * XML Attribute.
440      */
441     public static class Attribute
442     {
443         private String _name;
444         private String _value;
445 
446         Attribute(String n, String v)
447         {
448             _name = n;
449             _value = v;
450         }
451 
452         public String getName()
453         {
454             return _name;
455         }
456 
457         public String getValue()
458         {
459             return _value;
460         }
461     }
462 
463     /* ------------------------------------------------------------ */
464     /* ------------------------------------------------------------ */
465     /**
466      * XML Node. Represents an XML element with optional attributes and ordered content.
467      */
468     public static class Node extends AbstractList
469     {
470         Node _parent;
471         private ArrayList _list;
472         private String _tag;
473         private Attribute[] _attrs;
474         private boolean _lastString = false;
475         private String _path;
476 
477         /* ------------------------------------------------------------ */
478         Node(Node parent, String tag, Attributes attrs)
479         {
480             _parent = parent;
481             _tag = tag;
482 
483             if (attrs != null)
484             {
485                 _attrs = new Attribute[attrs.getLength()];
486                 for (int i = 0; i < attrs.getLength(); i++)
487                 {
488                     String name = attrs.getLocalName(i);
489                     if (name == null || name.equals(""))
490                         name = attrs.getQName(i);
491                     _attrs[i] = new Attribute(name, attrs.getValue(i));
492                 }
493             }
494         }
495 
496         /* ------------------------------------------------------------ */
497         public Node getParent()
498         {
499             return _parent;
500         }
501 
502         /* ------------------------------------------------------------ */
503         public String getTag()
504         {
505             return _tag;
506         }
507 
508         /* ------------------------------------------------------------ */
509         public String getPath()
510         {
511             if (_path == null)
512             {
513                 if (getParent() != null && getParent().getTag() != null)
514                     _path = getParent().getPath() + "/" + _tag;
515                 else
516                     _path = "/" + _tag;
517             }
518             return _path;
519         }
520 
521         /* ------------------------------------------------------------ */
522         /**
523          * Get an array of element attributes.
524          */
525         public Attribute[] getAttributes()
526         {
527             return _attrs;
528         }
529 
530         /* ------------------------------------------------------------ */
531         /**
532          * Get an element attribute.
533          * 
534          * @return attribute or null.
535          */
536         public String getAttribute(String name)
537         {
538             return getAttribute(name, null);
539         }
540 
541         /* ------------------------------------------------------------ */
542         /**
543          * Get an element attribute.
544          * 
545          * @return attribute or null.
546          */
547         public String getAttribute(String name, String dft)
548         {
549             if (_attrs == null || name == null)
550                 return dft;
551             for (int i = 0; i < _attrs.length; i++)
552                 if (name.equals(_attrs[i].getName()))
553                     return _attrs[i].getValue();
554             return dft;
555         }
556 
557         /* ------------------------------------------------------------ */
558         /**
559          * Get the number of children nodes.
560          */
561         public int size()
562         {
563             if (_list != null)
564                 return _list.size();
565             return 0;
566         }
567 
568         /* ------------------------------------------------------------ */
569         /**
570          * Get the ith child node or content.
571          * 
572          * @return Node or String.
573          */
574         public Object get(int i)
575         {
576             if (_list != null)
577                 return _list.get(i);
578             return null;
579         }
580 
581         /* ------------------------------------------------------------ */
582         /**
583          * Get the first child node with the tag.
584          * 
585          * @param tag
586          * @return Node or null.
587          */
588         public Node get(String tag)
589         {
590             if (_list != null)
591             {
592                 for (int i = 0; i < _list.size(); i++)
593                 {
594                     Object o = _list.get(i);
595                     if (o instanceof Node)
596                     {
597                         Node n = (Node) o;
598                         if (tag.equals(n._tag))
599                             return n;
600                     }
601                 }
602             }
603             return null;
604         }
605 
606         /* ------------------------------------------------------------ */
607         @Override
608         public void add(int i, Object o)
609         {
610             if (_list == null)
611                 _list = new ArrayList();
612             if (o instanceof String)
613             {
614                 if (_lastString)
615                 {
616                     int last = _list.size() - 1;
617                     _list.set(last, (String) _list.get(last) + o);
618                 }
619                 else
620                     _list.add(i, o);
621                 _lastString = true;
622             }
623             else
624             {
625                 _lastString = false;
626                 _list.add(i, o);
627             }
628         }
629 
630         /* ------------------------------------------------------------ */
631         public void clear()
632         {
633             if (_list != null)
634                 _list.clear();
635             _list = null;
636         }
637 
638         /* ------------------------------------------------------------ */
639         /**
640          * Get a tag as a string.
641          * 
642          * @param tag The tag to get
643          * @param tags IF true, tags are included in the value.
644          * @param trim If true, trim the value.
645          * @return results of get(tag).toString(tags).
646          */
647         public String getString(String tag, boolean tags, boolean trim)
648         {
649             Node node = get(tag);
650             if (node == null)
651                 return null;
652             String s = node.toString(tags);
653             if (s != null && trim)
654                 s = s.trim();
655             return s;
656         }
657 
658         /* ------------------------------------------------------------ */
659         public synchronized String toString()
660         {
661             return toString(true);
662         }
663 
664         /* ------------------------------------------------------------ */
665         /**
666          * Convert to a string.
667          * 
668          * @param tag If false, only _content is shown.
669          */
670         public synchronized String toString(boolean tag)
671         {
672             StringBuilder buf = new StringBuilder();
673             toString(buf, tag);
674             return buf.toString();
675         }
676 
677         /* ------------------------------------------------------------ */
678         /**
679          * Convert to a string.
680          * 
681          * @param tag If false, only _content is shown.
682          */
683         public synchronized String toString(boolean tag, boolean trim)
684         {
685             String s = toString(tag);
686             if (s != null && trim)
687                 s = s.trim();
688             return s;
689         }
690 
691         /* ------------------------------------------------------------ */
692         private synchronized void toString(StringBuilder buf, boolean tag)
693         {
694             if (tag)
695             {
696                 buf.append("<");
697                 buf.append(_tag);
698 
699                 if (_attrs != null)
700                 {
701                     for (int i = 0; i < _attrs.length; i++)
702                     {
703                         buf.append(' ');
704                         buf.append(_attrs[i].getName());
705                         buf.append("=\"");
706                         buf.append(_attrs[i].getValue());
707                         buf.append("\"");
708                     }
709                 }
710             }
711 
712             if (_list != null)
713             {
714                 if (tag)
715                     buf.append(">");
716                 for (int i = 0; i < _list.size(); i++)
717                 {
718                     Object o = _list.get(i);
719                     if (o == null)
720                         continue;
721                     if (o instanceof Node)
722                         ((Node) o).toString(buf, tag);
723                     else
724                         buf.append(o.toString());
725                 }
726                 if (tag)
727                 {
728                     buf.append("</");
729                     buf.append(_tag);
730                     buf.append(">");
731                 }
732             }
733             else if (tag)
734                 buf.append("/>");
735         }
736 
737         /* ------------------------------------------------------------ */
738         /**
739          * Iterator over named child nodes.
740          * 
741          * @param tag The tag of the nodes.
742          * @return Iterator over all child nodes with the specified tag.
743          */
744         public Iterator<Node> iterator(final String tag)
745         {
746             return new Iterator<Node>()
747             {
748                 int c = 0;
749                 Node _node;
750 
751                 /* -------------------------------------------------- */
752                 public boolean hasNext()
753                 {
754                     if (_node != null)
755                         return true;
756                     while (_list != null && c < _list.size())
757                     {
758                         Object o = _list.get(c);
759                         if (o instanceof Node)
760                         {
761                             Node n = (Node) o;
762                             if (tag.equals(n._tag))
763                             {
764                                 _node = n;
765                                 return true;
766                             }
767                         }
768                         c++;
769                     }
770                     return false;
771                 }
772 
773                 /* -------------------------------------------------- */
774                 public Node next()
775                 {
776                     try
777                     {
778                         if (hasNext())
779                             return _node;
780                         throw new NoSuchElementException();
781                     }
782                     finally
783                     {
784                         _node = null;
785                         c++;
786                     }
787                 }
788 
789                 /* -------------------------------------------------- */
790                 public void remove()
791                 {
792                     throw new UnsupportedOperationException("Not supported");
793                 }
794             };
795         }
796     }
797 }