001 /*
002 /*
003 * Copyright 2006 Stephen J. McConnell.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
014 * implied.
015 *
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019
020 package net.dpml.util;
021
022 import java.net.URI;
023 import java.net.URL;
024 import java.net.URISyntaxException;
025 import java.util.Hashtable;
026 import java.util.Map;
027 import java.io.InputStream;
028 import java.io.InputStreamReader;
029 import java.io.IOException;
030 import java.io.OutputStream;
031 import java.io.FileNotFoundException;
032
033 import javax.xml.XMLConstants;
034
035 import net.dpml.transit.Artifact;
036
037 import org.w3c.dom.DOMError;
038 import org.w3c.dom.DOMLocator;
039 import org.w3c.dom.bootstrap.DOMImplementationRegistry;
040 import org.w3c.dom.DOMErrorHandler;
041 import org.w3c.dom.DOMConfiguration;
042 import org.w3c.dom.Document;
043 import org.w3c.dom.ls.DOMImplementationLS;
044 import org.w3c.dom.ls.LSOutput;
045 import org.w3c.dom.ls.LSParser;
046 import org.w3c.dom.ls.LSSerializer;
047 import org.w3c.dom.ls.LSResourceResolver;
048 import org.w3c.dom.ls.LSInput;
049
050 /**
051 * Utility class that creates a schema validating DOM3 parser.
052 */
053 public class DOM3DocumentBuilder
054 {
055 private final Map m_map;
056 private final Logger m_logger;
057
058 /**
059 * Creation of a new DOM3 document builder.
060 */
061 public DOM3DocumentBuilder()
062 {
063 this( new DefaultLogger( "dom" ) );
064 }
065
066 /**
067 * Creation of a new DOM3 document builder.
068 * @param logger the assigned logging channel
069 */
070 public DOM3DocumentBuilder( Logger logger )
071 {
072 this( logger, new Hashtable() );
073 }
074
075 /**
076 * Creation of a new DOM3 document builder.
077 * @param logger the assigned logging channel
078 * @param map namespace to builder uri map
079 */
080 public DOM3DocumentBuilder( Logger logger, Map map )
081 {
082 m_map = map;
083 m_logger = logger;
084 }
085
086 /**
087 * Parse an xml schema document.
088 * @param uri the document uri
089 * @return the validated document
090 * @exception IOException if an IO error occurs
091 */
092 public Document parse( URI uri ) throws IOException
093 {
094 if( null == uri )
095 {
096 throw new NullPointerException( "uri" );
097 }
098 try
099 {
100 DOMImplementationRegistry registry =
101 DOMImplementationRegistry.newInstance();
102 DOMImplementationLS impl =
103 (DOMImplementationLS) registry.getDOMImplementation( "LS" );
104 if( null == impl )
105 {
106 final String error =
107 "Unable to locate a DOM3 parser.";
108 throw new IllegalStateException( error );
109 }
110 LSParser builder = impl.createLSParser( DOMImplementationLS.MODE_SYNCHRONOUS, null );
111 DOMConfiguration config = builder.getDomConfig();
112 config.setParameter( "error-handler", new InternalErrorHandler( m_logger, uri ) );
113 config.setParameter( "resource-resolver", new InternalResourceResolver( m_map ) );
114 config.setParameter( "validate", Boolean.TRUE );
115
116 DOMInput input = new DOMInput();
117 URL url = Artifact.toURL( uri );
118 InputStream stream = url.openStream();
119 InputStreamReader reader = new InputStreamReader( stream );
120 input.setCharacterStream( reader );
121
122 return builder.parse( input );
123 }
124 catch( FileNotFoundException e )
125 {
126 throw e;
127 }
128 catch( Throwable e )
129 {
130 final String error =
131 "DOM3 error while attempting to parse document."
132 + "\nSource: " + uri;
133 //String message = ExceptionHelper.packException( error, e, true );
134 IOException ioe = new IOException( error );
135 ioe.initCause( e );
136 throw ioe;
137 }
138 }
139
140 /**
141 * Write a document to an output stream.
142 * @param doc the document to write
143 * @param output the output stream
144 * @exception Exception if an error occurs
145 */
146 public void write( Document doc, OutputStream output ) throws Exception
147 {
148 DOMImplementationRegistry registry =
149 DOMImplementationRegistry.newInstance();
150 DOMImplementationLS impl =
151 (DOMImplementationLS) registry.getDOMImplementation( "LS" );
152 if( null == impl )
153 {
154 final String error =
155 "Unable to locate a DOM3 implementation.";
156 throw new IllegalStateException( error );
157 }
158 LSSerializer domWriter = impl.createLSSerializer();
159 LSOutput lsOut = impl.createLSOutput();
160 lsOut.setByteStream( output );
161 domWriter.write( doc, lsOut );
162 }
163
164 /**
165 * Utility class to handle namespace uri resolves.
166 */
167 private static class InternalResourceResolver implements LSResourceResolver
168 {
169 private final Map m_map;
170
171 /**
172 * Creation of a new InternalResourceResolver.
173 * @param map the namespace to builder map
174 */
175 InternalResourceResolver( Map map )
176 {
177 m_map = map;
178 }
179
180 /**
181 * Resolve an LS input.
182 * @param type the node type
183 * @param namespace the node namespace
184 * @param publicId the public id
185 * @param systemId the system id
186 * @param base the base value
187 * @return the LS input instance
188 */
189 public LSInput resolveResource(
190 String type, String namespace, String publicId, String systemId, String base )
191 {
192 if( XMLConstants.W3C_XML_SCHEMA_NS_URI.equals( type ) )
193 {
194 DOMInput input = new DOMInput();
195 input.setPublicId( publicId );
196 input.setSystemId( systemId );
197 input.setBaseURI( base );
198
199 if( null == namespace )
200 {
201 return input;
202 }
203
204 try
205 {
206 URI uri = resolveURI( namespace );
207 URL url = Artifact.toURL( uri );
208 InputStream stream = url.openStream();
209 InputStreamReader reader = new InputStreamReader( stream );
210 input.setCharacterStream( reader );
211 }
212 catch( URISyntaxException e )
213 {
214 // ignore
215 }
216 catch( IOException e )
217 {
218 // ignore
219 }
220 return input;
221 }
222 else
223 {
224 return null;
225 }
226 }
227
228 private URI resolveURI( String namespace ) throws URISyntaxException
229 {
230 if( null == namespace )
231 {
232 throw new NullPointerException( "namespace" );
233 }
234 String value = System.getProperty( namespace );
235 if( null != value )
236 {
237 return new URI( value );
238 }
239 if( m_map.containsKey( namespace ) )
240 {
241 return (URI) m_map.get( namespace );
242 }
243 else
244 {
245 return new URI( namespace );
246 }
247 }
248 }
249
250 /**
251 * Internal error handler with lots of room for improvement.
252 */
253 private static final class InternalErrorHandler implements DOMErrorHandler
254 {
255 private final URI m_uri;
256 private final Logger m_logger;
257
258 InternalErrorHandler( Logger logger, URI uri )
259 {
260 m_uri = uri;
261 m_logger = logger;
262 }
263
264 /**
265 * Handle the supplied error.
266 * @param error the error
267 * @return a boolean value
268 */
269 public boolean handleError( DOMError error )
270 {
271 DOMLocator locator = error.getLocation();
272 int line = locator.getLineNumber();
273 int column = locator.getColumnNumber();
274 String uri = locator.getUri();
275 if( null == uri )
276 {
277 uri = m_uri.toString();
278 }
279 String position = "[" + line + ":" + column + "]";
280 String message = error.getMessage();
281 if( null != message )
282 {
283 short severity = error.getSeverity();
284 final String notice =
285 "DOM3 Validation Error"
286 + "\nSource: "
287 + uri + ":" + position
288 + "\nCause: "
289 + message;
290 if( severity == DOMError.SEVERITY_ERROR )
291 {
292 m_logger.error( notice );
293 }
294 else if( severity == DOMError.SEVERITY_WARNING )
295 {
296 m_logger.warn( notice );
297 }
298 else
299 {
300 m_logger.info( notice );
301 }
302 }
303 return true;
304 }
305
306 private String getLabel( DOMError error )
307 {
308 short severity = error.getSeverity();
309 if( severity == DOMError.SEVERITY_ERROR )
310 {
311 return "ERROR";
312 }
313 else if( severity == DOMError.SEVERITY_WARNING )
314 {
315 return "WARNING";
316 }
317 else
318 {
319 return "FATAL";
320 }
321 }
322 }
323 }