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    }