001    /*
002     * @(#)$Id: AbstractUnmarshallerImpl.java,v 1.7 2003/02/13 23:41:06 kk122374 Exp $
003     *
004     * Copyright 2001 Sun Microsystems, Inc. All Rights Reserved.
005     * 
006     * This software is the proprietary information of Sun Microsystems, Inc.  
007     * Use is subject to license terms.
008     * 
009     */
010    package javax.xml.bind.helpers;
011    
012    import java.io.File;
013    import java.net.URL;
014    import java.net.MalformedURLException;
015    import javax.xml.parsers.SAXParserFactory;
016    import javax.xml.parsers.ParserConfigurationException;
017    import javax.xml.transform.Source;
018    import javax.xml.transform.sax.SAXSource;
019    import javax.xml.transform.stream.StreamSource;
020    import javax.xml.transform.dom.DOMSource;
021    import javax.xml.bind.JAXBException;
022    import javax.xml.bind.PropertyException;
023    import javax.xml.bind.UnmarshalException;
024    import javax.xml.bind.Unmarshaller;
025    import javax.xml.bind.ValidationEventHandler;
026    import org.xml.sax.XMLReader;
027    import org.xml.sax.InputSource;
028    import org.xml.sax.SAXException;
029    
030    /**
031     * Partial default <tt>Unmarshaller</tt> implementation.
032     * 
033     * <p>
034     * This class provides a partial default implementation for the
035     * {@link javax.xml.bind.Unmarshaller} interface.
036     * 
037     * <p>
038     * A JAXB Provider has to implement three methods
039     * (getUnmarshallerHandler, unmarshal(Node), and
040     * unmarshal(XMLReader,InputSource)
041     *
042     * @author <ul><li>Kohsuke Kawaguchi, Sun Microsystems, Inc.</li></ul>
043     * @version $Revision: 1.7 $ $Date: 2003/02/13 23:41:06 $
044     * @see javax.xml.bind.Unmarshaller
045     * @since JAXB1.0
046     */
047    public abstract class AbstractUnmarshallerImpl implements Unmarshaller
048    {    
049        /** handler that will be used to process errors and warnings during unmarshal */
050        private ValidationEventHandler eventHandler = 
051            new DefaultValidationEventHandler();
052        
053        /** whether or not the unmarshaller will validate */
054        private boolean validating = false;
055    
056        /**
057         * XMLReader that will be used to parse a document.
058         */
059        private XMLReader reader = null;
060        
061        /**
062         * Obtains a configured XMLReader.
063         * 
064         * This method is used when the client-specified
065         * {@link SAXSource} object doesn't have XMLReader.
066         * 
067         * {@link Unmarshaller} is not re-entrant, so we will
068         * only use one instance of XMLReader.
069         */
070        protected XMLReader getXMLReader() throws JAXBException {
071            if(reader==null) {
072                try {
073                    SAXParserFactory parserFactory;
074                    parserFactory = SAXParserFactory.newInstance();
075                    parserFactory.setNamespaceAware(true);
076                    // there is no point in asking a validation because 
077                    // there is no guarantee that the document will come with
078                    // a proper schemaLocation.
079                    parserFactory.setValidating(false);
080                    reader = parserFactory.newSAXParser().getXMLReader();
081                } catch( ParserConfigurationException e ) {
082                    throw new JAXBException(e);
083                } catch( SAXException e ) {
084                    throw new JAXBException(e);
085                }
086            }
087            return reader;
088        }
089        
090        public Object unmarshal( Source source ) throws JAXBException {
091            if( source == null ) {
092                throw new IllegalArgumentException(
093                    Messages.format( Messages.MUST_NOT_BE_NULL, "source" ) );
094            }
095            
096            if(source instanceof SAXSource)
097                return unmarshal( (SAXSource)source );
098            if(source instanceof StreamSource)
099                return unmarshal( streamSourceToInputSource((StreamSource)source));
100            if(source instanceof DOMSource)
101                return unmarshal( ((DOMSource)source).getNode() );
102            
103            // we don't handle other types of Source
104            throw new IllegalArgumentException();
105        }
106    
107        // use the client specified XMLReader contained in the SAXSource.
108        private final Object unmarshal( SAXSource source ) throws JAXBException {
109            
110            XMLReader reader = source.getXMLReader();
111            if( reader == null )
112                reader = getXMLReader();
113            
114            return unmarshal( reader, source.getInputSource() );
115        }
116    
117        /**
118         * Unmarshals an object by using the specified XMLReader and the InputSource.
119         * 
120         * The callee should call the setErrorHandler method of the XMLReader
121         * so that errors are passed to the client-specified ValidationEventHandler.
122         */
123        protected abstract Object unmarshal( XMLReader reader, InputSource source ) throws JAXBException;
124        
125        public final Object unmarshal( InputSource source ) throws JAXBException {
126            if( source == null ) {
127                throw new IllegalArgumentException(
128                    Messages.format( Messages.MUST_NOT_BE_NULL, "source" ) );
129            }
130    
131            return unmarshal( getXMLReader(), source );
132        }
133            
134    
135        private Object unmarshal( String url ) throws JAXBException {
136            return unmarshal( new InputSource(url) );
137        }
138        
139        public final Object unmarshal( URL url ) throws JAXBException {
140            if( url == null ) {
141                throw new IllegalArgumentException(
142                    Messages.format( Messages.MUST_NOT_BE_NULL, "url" ) );
143            }
144    
145            return unmarshal( url.toExternalForm() );
146        }
147        
148        public final Object unmarshal( File f ) throws JAXBException {
149            if( f == null ) {
150                throw new IllegalArgumentException(
151                    Messages.format( Messages.MUST_NOT_BE_NULL, "file" ) );
152            }
153    
154            try {
155                // copied from JAXP
156                String path = f.getAbsolutePath();
157                if (File.separatorChar != '/')
158                    path = path.replace(File.separatorChar, '/');
159                if (!path.startsWith("/"))
160                    path = "/" + path;
161                if (!path.endsWith("/") && f.isDirectory())
162                    path = path + "/";
163                return unmarshal(new URL("file", "", path));
164            } catch( MalformedURLException e ) {
165                throw new IllegalArgumentException(e.getMessage());
166            }
167        }
168        
169        public final Object unmarshal( java.io.InputStream is ) 
170            throws JAXBException {
171                
172            if( is == null ) {
173                throw new IllegalArgumentException(
174                    Messages.format( Messages.MUST_NOT_BE_NULL, "is" ) );
175            }
176    
177            InputSource isrc = new InputSource( is );
178            return unmarshal( isrc );
179        }
180        
181        private static InputSource streamSourceToInputSource( StreamSource ss ) {
182            InputSource is = new InputSource();
183            is.setSystemId( ss.getSystemId() );
184            is.setByteStream( ss.getInputStream() );
185            is.setCharacterStream( ss.getReader() );
186            
187            return is;
188        }
189        
190        
191        /**
192         * Indicates whether or not the Unmarshaller is configured to validate
193         * during unmarshal operations.
194         * <p>
195         * <i><b>Note:</b> I named this method isValidating() to stay in-line
196         * with JAXP, as opposed to naming it getValidating(). </i>
197         *
198         * @return true if the Unmarshaller is configured to validate during
199         *        unmarshal operations, false otherwise
200         * @throws JAXBException if an error occurs while retrieving the validating
201         *        flag
202         */
203        public boolean isValidating() throws JAXBException {
204            return validating;
205        }
206        
207        /**
208         * Allow an application to register a validation event handler.
209         * <p>
210         * The validation event handler will be called by the JAXB Provider if any
211         * validation errors are encountered during calls to any of the
212         * <tt>unmarshal</tt> methods.  If the client application does not register
213         * a validation event handler before invoking the unmarshal methods, then
214         * all validation events will be silently ignored and may result in
215         * unexpected behaviour.
216         *
217         * @param handler the validation event handler
218         * @throws JAXBException if an error was encountered while setting the
219         *        event handler
220         */
221        public void setEventHandler(ValidationEventHandler handler) 
222            throws JAXBException {
223            
224            if( handler == null ) {
225                eventHandler = new DefaultValidationEventHandler();
226            } else {
227                eventHandler = handler;
228            }
229        }
230        
231        /**
232         * Specifies whether or not the Unmarshaller should validate during
233         * unmarshal operations.  By default, the <tt>Unmarshaller</tt> does
234         * not validate.
235         * <p>
236         * This method may only be invoked before or after calling one of the
237         * unmarshal methods.
238         *
239         * @param validating true if the Unmarshaller should validate during
240         *       unmarshal, false otherwise
241         * @throws JAXBException if an error occurred while enabling or disabling
242         * validation at unmarshal time
243         */
244        public void setValidating(boolean validating) throws JAXBException {
245            this.validating = validating;
246        }
247        
248        /**
249         * Return the current event handler or the default event handler if one
250         * hasn't been set.
251         *
252         * @return the current ValidationEventHandler or the default event handler
253         *        if it hasn't been set
254         * @throws JAXBException if an error was encountered while getting the
255         *        current event handler
256         */
257        public ValidationEventHandler getEventHandler() throws JAXBException {
258            return eventHandler;
259        }
260        
261        
262        /**
263         * Creates an UnmarshalException from a SAXException.
264         * 
265         * This is an utility method provided for the derived classes.
266         * 
267         * <p>
268         * When a provider-implemented ContentHandler wants to throw a
269         * JAXBException, it needs to wrap the exception by a SAXException.
270         * If the unmarshaller implementation blindly wrap SAXException
271         * by JAXBException, such an exception will be a JAXBException
272         * wrapped by a SAXException wrapped by another JAXBException.
273         * This is silly.
274         * 
275         * <p>
276         * This method checks the nested exception of SAXException
277         * and reduce those excessive wrapping.
278         * 
279         * @return the resulting UnmarshalException
280         */
281        protected UnmarshalException createUnmarshalException( SAXException e ) {
282            // check the nested exception to see if it's an UnmarshalException
283            Exception nested = e.getException();
284            if(nested instanceof UnmarshalException)
285                return (UnmarshalException)nested;
286            
287            if(nested instanceof RuntimeException)
288                // typically this is an unexpected exception,
289                // just throw it rather than wrap it, so that the full stack
290                // trace can be displayed.
291                throw (RuntimeException)nested;
292                    
293            
294            // otherwise simply wrap it
295            if(nested!=null)
296                return new UnmarshalException(nested);
297            else
298                return new UnmarshalException(e);
299        }
300        
301        /**
302         * Default implementation of the setProperty method always 
303         * throws PropertyException since there are no required
304         * properties. If a provider needs to handle additional 
305         * properties, it should override this method in a derived class.
306         */
307        public void setProperty( String name, Object value )
308            throws PropertyException {
309    
310            if( name == null ) {
311                throw new IllegalArgumentException(
312                    Messages.format( Messages.MUST_NOT_BE_NULL, "name" ) );
313            }
314    
315            throw new PropertyException(name, value);
316        }
317        
318        /**
319         * Default implementation of the getProperty method always 
320         * throws PropertyException since there are no required
321         * properties. If a provider needs to handle additional 
322         * properties, it should override this method in a derived class.
323         */
324        public Object getProperty( String name )
325            throws PropertyException {
326                
327            if( name == null ) {
328                throw new IllegalArgumentException(
329                    Messages.format( Messages.MUST_NOT_BE_NULL, "name" ) );
330            }
331    
332            throw new PropertyException(name);
333        }
334        
335    }