Use JAXP 1.3 for all XML services. Create Schema objects.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / xml / Parser.java
1 /*
2  * Parser.java
3  * 
4  * Validating and non-validating XML parsing using JAXP 1.3.
5  * 
6  * Previous versions of the code directly used the Xerces DOMParser
7  * class. This class has been hidden in the Sun XML stack, and the
8  * public interface is to use DocumentBuilderFactory. This module 
9  * requires the DOM 3 and JAXP 1.3 support built into J2SE 5.0 and
10  * distributed separately for earlier releases of Java from
11  * https://jaxp.dev.java.net/. It should also work with Xerces 2.7.0
12  * when that release becomes available.
13  * 
14  * The org.opensaml.XML class already has most of the parsing code,
15  * but it uses a subset of the required Schemas. Here we build a
16  * wider Schema object, set it as the default SAML schema (because
17  * some Shibboleth namespace fields appear in SAML statements), and
18  * demand that Schema for every parser (DocumentBuilder) we request.
19  * 
20  * Currently, this class exposes static methods. Should a real 
21  * framework be installed, it would become a singleton object.
22  */
23 package edu.internet2.middleware.shibboleth.xml;
24
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.StringWriter;
28
29 import javax.xml.transform.OutputKeys;
30 import javax.xml.transform.Transformer;
31 import javax.xml.transform.TransformerConfigurationException;
32 import javax.xml.transform.TransformerException;
33 import javax.xml.transform.TransformerFactory;
34 import javax.xml.transform.dom.DOMSource;
35 import javax.xml.transform.stream.StreamResult;
36 import javax.xml.validation.Schema;
37
38 import org.apache.log4j.Logger;
39 import org.opensaml.SAMLException;
40 import org.w3c.dom.Document;
41 import org.w3c.dom.Node;
42 import org.xml.sax.InputSource;
43 import org.xml.sax.SAXException;
44
45 /**
46  * Obtain schema validating and non-validating XML parsers.
47  * 
48  * @author Howard Gilbert
49  */
50 public class Parser {
51     private static Logger log = Logger.getLogger(Parser.class);
52     
53     
54     /**
55      * All the namespaces used by any part of Shibboleth
56      * 
57      * Note: The current Schema compiler requires that dependencies
58      * (imports) be listed before the namespace of the schema
59      * that imports them.
60      */
61     private static String[] namespaces = new String[]{
62             "http://www.w3.org/2000/09/xmldsig#",  
63             "http://www.w3.org/2001/04/xmlenc#",
64             "urn:oasis:names:tc:SAML:1.0:assertion",
65             "urn:oasis:names:tc:SAML:2.0:assertion",
66             "http://www.w3.org/XML/1998/namespace",
67             "http://schemas.xmlsoap.org/soap/envelope/",
68             "urn:mace:shibboleth:credentials:1.0",
69             "urn:oasis:names:tc:SAML:1.0:protocol",
70             "urn:oasis:names:tc:SAML:2.0:protocol",
71             "urn:mace:shibboleth:namemapper:1.0",
72             "urn:mace:shibboleth:origin:1.0",
73             "urn:mace:shibboleth:arp:1.0",
74             "urn:mace:shibboleth:resolver:1.0",
75             "urn:mace:shibboleth:target:config:1.0",
76             "urn:mace:shibboleth:trust:1.0",
77             "urn:mace:shibboleth:1.0",
78             "http://schemas.xmlsoap.org/soap/envelope/",
79             "urn:oasis:names:tc:SAML:2.0:metadata",
80             "http://www.w3.org/2002/06/xmldsig-filter2"
81       };
82     
83     // If there were a real Framework here (like Spring) then
84     // the schemaBuilder would be inserted 
85     private static Schemas schemaBuilder = new SchemasDirectoryImpl();
86     private static Schema schema = schemaBuilder.compileSchema(namespaces);
87     
88     /**
89      * Load a DOM from a wrapped byte stream.
90      * 
91      * @param ins InputSource The XML document
92      * @param validate If true, use Schema. Otherwise, its raw XML.
93      * @return A DOM 3 tree
94      */
95     public static Document loadDom(InputSource ins, boolean validate) {
96         Document doc=null;
97         log.info("Loading XML from (" + ins.getSystemId() + ")"
98                 + (validate?" with Schema validation":"")
99                 );
100         try {
101             if (validate)
102                 doc = org.opensaml.XML.parserPool.parse(ins,schema);
103             else
104                 doc = org.opensaml.XML.parserPool.parse(ins,null);
105             
106         } catch (SAXException e) {
107             log.error("Error in XML configuration file"+e);
108             return null;
109         } catch (IOException e) {
110             log.error("Error accessing XML configuration file"+e);
111             return null;
112         } catch (SAMLException e) {
113             log.error("Error with XML parser "+e);
114             return null;
115        }
116         
117         return doc;
118     }
119     
120     
121     /**
122      * A dummy class that pretends to be an old Xerces DOMParser
123      * to simplify conversion of existing code.
124      */
125     public static class DOMParser {
126         Document doc = null;
127         boolean validate = false;
128         
129         public DOMParser(boolean validate) {
130             this.validate=validate;
131         }
132         
133         public Document parse(InputSource ins) throws SAXException, IOException {
134             doc = loadDom(ins,true);
135             return doc;
136         }
137         
138         public Document getDocument() {
139             return doc;
140         }
141     }
142     
143     /**
144      * Write a DOM out to a character stream (for debugging and logging)
145      * 
146      * @param dom The DOM tree to write
147      * @return A string containing the XML in character form.
148      */
149     public static String serialize(Node dom) {
150         String ret = null;
151         
152         TransformerFactory factory = TransformerFactory.newInstance();
153         Transformer transformer = null;
154         DOMSource source = new DOMSource(dom);
155         try {
156             transformer = factory.newTransformer();
157             transformer.setOutputProperty(OutputKeys.INDENT, "yes");
158         } catch (TransformerConfigurationException e) {
159             return null;
160         }
161         StringWriter stringWriter = new StringWriter();
162         StreamResult result = new StreamResult(stringWriter);
163         try {
164             transformer.transform(source, result);
165         } catch (TransformerException e1) {
166             return null;
167         }
168         return stringWriter.toString();
169     }
170     
171     /**
172      * Version of loadDom where the file is specified as a resource name
173      * 
174      * @param configFilePath input resource
175      * @param validate if true, use Schema
176      * @return DOM tree
177      */
178     public static Document loadDom(String configFilePath,boolean validate) 
179     {
180         InputSource insrc;
181         String schemaCannonicalFilePath;
182        try {
183             InputStream resourceAsStream = Parser.class.getResourceAsStream(configFilePath);
184             insrc = new InputSource(resourceAsStream);
185             insrc.setSystemId(configFilePath);
186         } catch (Exception e1) {
187             log.error("Configuration file "+configFilePath+" could not be located.");
188             return null;
189         }
190         
191         return loadDom(insrc,validate); // Now pass on to the main routine
192         
193     }
194     
195     /**
196      * Override the OpenSAML default schema from SAML 1.1 to 
197      * SAML 1.1 plus Shibboleth (and some SAML 2.0).
198      */
199     static {
200         org.opensaml.XML.parserPool.setDefaultSchema(schema);
201     }
202     
203 }