blob: a02012d7d54e2f4e9ff1a29425e4e1c2b85b30b0 [file] [log] [blame]
/*
* Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.xml.internal.bind.v2.runtime.unmarshaller;
import java.io.IOException;
import java.io.InputStream;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.PropertyException;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.UnmarshallerHandler;
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.attachment.AttachmentUnmarshaller;
import javax.xml.bind.helpers.AbstractUnmarshallerImpl;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.events.XMLEvent;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import com.sun.xml.internal.bind.IDResolver;
import com.sun.xml.internal.bind.api.ClassResolver;
import com.sun.xml.internal.bind.unmarshaller.DOMScanner;
import com.sun.xml.internal.bind.unmarshaller.InfosetScanner;
import com.sun.xml.internal.bind.unmarshaller.Messages;
import com.sun.xml.internal.bind.v2.ClassFactory;
import com.sun.xml.internal.bind.v2.runtime.AssociationMap;
import com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl;
import com.sun.xml.internal.bind.v2.runtime.JaxBeanInfo;
import com.sun.xml.internal.bind.v2.util.XmlFactory;
import java.io.Closeable;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
/**
* Default Unmarshaller implementation.
*
* <p>
* This class can be extended by the generated code to provide
* type-safe unmarshall methods.
*
* @author
* <a href="mailto:kohsuke.kawaguchi@sun.com">Kohsuke KAWAGUCHI</a>
*/
public final class UnmarshallerImpl extends AbstractUnmarshallerImpl implements ValidationEventHandler, Closeable
{
/** Owning {@link JAXBContext} */
protected final JAXBContextImpl context;
/**
* schema which will be used to validate during calls to unmarshal
*/
private Schema schema;
public final UnmarshallingContext coordinator;
/** Unmarshaller.Listener */
private Listener externalListener;
/**
* The attachment unmarshaller used to support MTOM and swaRef.
*/
private AttachmentUnmarshaller attachmentUnmarshaller;
private IDResolver idResolver = new DefaultIDResolver();
public UnmarshallerImpl( JAXBContextImpl context, AssociationMap assoc ) {
this.context = context;
this.coordinator = new UnmarshallingContext( this, assoc );
try {
setEventHandler(this);
} catch (JAXBException e) {
throw new AssertionError(e); // impossible
}
}
public UnmarshallerHandler getUnmarshallerHandler() {
return getUnmarshallerHandler(true,null);
}
private XMLReader reader = null;
/**
* Obtains a configured XMLReader.
*
* This method is used when the client-specified
* {@link SAXSource} object doesn't have XMLReader.
*
* {@link Unmarshaller} is not re-entrant, so we will
* only use one instance of XMLReader.
*
* Overriden in order to fix potential security issue.
*/
@Override
protected XMLReader getXMLReader() throws JAXBException {
if (reader == null) {
try {
SAXParserFactory parserFactory = XmlFactory.createParserFactory(context.disableSecurityProcessing);
// there is no point in asking a validation because
// there is no guarantee that the document will come with
// a proper schemaLocation.
parserFactory.setValidating(false);
reader = parserFactory.newSAXParser().getXMLReader();
} catch (ParserConfigurationException e) {
throw new JAXBException(e);
} catch (SAXException e) {
throw new JAXBException(e);
}
}
return reader;
}
private SAXConnector getUnmarshallerHandler( boolean intern, JaxBeanInfo expectedType ) {
XmlVisitor h = createUnmarshallerHandler(null, false, expectedType);
if (intern) {
h = new InterningXmlVisitor(h);
}
return new SAXConnector(h,null);
}
/**
* Creates and configures a new unmarshalling pipe line.
* Depending on the setting, we put a validator as a filter.
*
* @return
* A component that implements both {@link UnmarshallerHandler}
* and {@link ValidationEventHandler}. All the parsing errors
* should be reported to this error handler for the unmarshalling
* process to work correctly.
*
* Also, returned handler expects all the XML names to be interned.
*
*/
public final XmlVisitor createUnmarshallerHandler(InfosetScanner scanner, boolean inplace, JaxBeanInfo expectedType ) {
coordinator.reset(scanner,inplace,expectedType,idResolver);
XmlVisitor unmarshaller = coordinator;
// delegate to JAXP 1.3 for validation if the client provided a schema
if (schema != null) {
unmarshaller = new ValidatingUnmarshaller(schema,unmarshaller);
}
if(attachmentUnmarshaller!=null && attachmentUnmarshaller.isXOPPackage()) {
unmarshaller = new MTOMDecorator(this,unmarshaller,attachmentUnmarshaller);
}
return unmarshaller;
}
private static final DefaultHandler dummyHandler = new DefaultHandler();
public static boolean needsInterning( XMLReader reader ) {
// attempt to set it to true, which could fail
try {
reader.setFeature("http://xml.org/sax/features/string-interning",true);
} catch (SAXException e) {
// if it fails that's fine. we'll work around on our side
}
try {
if (reader.getFeature("http://xml.org/sax/features/string-interning")) {
return false; // no need for intern
}
} catch (SAXException e) {
// unrecognized/unsupported
}
// otherwise we need intern
return true;
}
protected Object unmarshal( XMLReader reader, InputSource source ) throws JAXBException {
return unmarshal0(reader,source,null);
}
protected <T> JAXBElement<T> unmarshal( XMLReader reader, InputSource source, Class<T> expectedType ) throws JAXBException {
if(expectedType==null) {
throw new IllegalArgumentException();
}
return (JAXBElement)unmarshal0(reader,source,getBeanInfo(expectedType));
}
private Object unmarshal0( XMLReader reader, InputSource source, JaxBeanInfo expectedType ) throws JAXBException {
SAXConnector connector = getUnmarshallerHandler(needsInterning(reader),expectedType);
reader.setContentHandler(connector);
// saxErrorHandler will be set by the getUnmarshallerHandler method.
// configure XMLReader so that the error will be sent to it.
// This is essential for the UnmarshallerHandler to be able to abort
// unmarshalling when an error is found.
//
// Note that when this XMLReader is provided by the client code,
// it might be already configured to call a client error handler.
// This will clobber such handler, if any.
//
// Ryan noted that we might want to report errors to such a client
// error handler as well.
reader.setErrorHandler(coordinator);
try {
reader.parse(source);
} catch( IOException e ) {
coordinator.clearStates();
throw new UnmarshalException(e);
} catch( SAXException e ) {
coordinator.clearStates();
throw createUnmarshalException(e);
}
Object result = connector.getResult();
// avoid keeping unnecessary references too long to let the GC
// reclaim more memory.
// setting null upsets some parsers, so use a dummy instance instead.
reader.setContentHandler(dummyHandler);
reader.setErrorHandler(dummyHandler);
return result;
}
@Override
public <T> JAXBElement<T> unmarshal( Source source, Class<T> expectedType ) throws JAXBException {
if (source instanceof SAXSource) {
SAXSource ss = (SAXSource) source;
XMLReader locReader = ss.getXMLReader();
if (locReader == null) {
locReader = getXMLReader();
}
return unmarshal(locReader, ss.getInputSource(), expectedType);
}
if (source instanceof StreamSource) {
return unmarshal(getXMLReader(), streamSourceToInputSource((StreamSource) source), expectedType);
}
if (source instanceof DOMSource) {
return unmarshal(((DOMSource) source).getNode(), expectedType);
}
// we don't handle other types of Source
throw new IllegalArgumentException();
}
public Object unmarshal0( Source source, JaxBeanInfo expectedType ) throws JAXBException {
if (source instanceof SAXSource) {
SAXSource ss = (SAXSource) source;
XMLReader locReader = ss.getXMLReader();
if (locReader == null) {
locReader = getXMLReader();
}
return unmarshal0(locReader, ss.getInputSource(), expectedType);
}
if (source instanceof StreamSource) {
return unmarshal0(getXMLReader(), streamSourceToInputSource((StreamSource) source), expectedType);
}
if (source instanceof DOMSource) {
return unmarshal0(((DOMSource) source).getNode(), expectedType);
}
// we don't handle other types of Source
throw new IllegalArgumentException();
}
@Override
public final ValidationEventHandler getEventHandler() {
try {
return super.getEventHandler();
} catch (JAXBException e) {
// impossible
throw new AssertionError();
}
}
/**
* Returns true if an event handler is installed.
* <p>
* The default handler ignores any errors, and for that this method returns false.
*/
public final boolean hasEventHandler() {
return getEventHandler()!=this;
}
@Override
public <T> JAXBElement<T> unmarshal(Node node, Class<T> expectedType) throws JAXBException {
if (expectedType == null) {
throw new IllegalArgumentException();
}
return (JAXBElement)unmarshal0(node,getBeanInfo(expectedType));
}
public final Object unmarshal( Node node ) throws JAXBException {
return unmarshal0(node,null);
}
// just to make the the test harness happy by making this method accessible
@Deprecated
public final Object unmarshal( SAXSource source ) throws JAXBException {
return super.unmarshal(source);
}
public final Object unmarshal0( Node node, JaxBeanInfo expectedType ) throws JAXBException {
try {
final DOMScanner scanner = new DOMScanner();
InterningXmlVisitor handler = new InterningXmlVisitor(createUnmarshallerHandler(null,false,expectedType));
scanner.setContentHandler(new SAXConnector(handler,scanner));
if(node.getNodeType() == Node.ELEMENT_NODE) {
scanner.scan((Element)node);
} else if(node.getNodeType() == Node.DOCUMENT_NODE) {
scanner.scan((Document)node);
} else {
throw new IllegalArgumentException("Unexpected node type: "+node);
}
Object retVal = handler.getContext().getResult();
handler.getContext().clearResult();
return retVal;
} catch( SAXException e ) {
throw createUnmarshalException(e);
}
}
@Override
public Object unmarshal(XMLStreamReader reader) throws JAXBException {
return unmarshal0(reader,null);
}
@Override
public <T> JAXBElement<T> unmarshal(XMLStreamReader reader, Class<T> expectedType) throws JAXBException {
if (expectedType==null) {
throw new IllegalArgumentException();
}
return (JAXBElement)unmarshal0(reader,getBeanInfo(expectedType));
}
public Object unmarshal0(XMLStreamReader reader, JaxBeanInfo expectedType) throws JAXBException {
if (reader == null) {
throw new IllegalArgumentException(
Messages.format(Messages.NULL_READER));
}
int eventType = reader.getEventType();
if (eventType != XMLStreamConstants.START_ELEMENT
&& eventType != XMLStreamConstants.START_DOCUMENT) {
// TODO: convert eventType into event name
throw new IllegalStateException(
Messages.format(Messages.ILLEGAL_READER_STATE,eventType));
}
XmlVisitor h = createUnmarshallerHandler(null,false,expectedType);
StAXConnector connector=StAXStreamConnector.create(reader,h);
try {
connector.bridge();
} catch (XMLStreamException e) {
throw handleStreamException(e);
}
Object retVal = h.getContext().getResult();
h.getContext().clearResult();
return retVal;
}
@Override
public <T> JAXBElement<T> unmarshal(XMLEventReader reader, Class<T> expectedType) throws JAXBException {
if(expectedType==null) {
throw new IllegalArgumentException();
}
return (JAXBElement)unmarshal0(reader,getBeanInfo(expectedType));
}
@Override
public Object unmarshal(XMLEventReader reader) throws JAXBException {
return unmarshal0(reader,null);
}
private Object unmarshal0(XMLEventReader reader,JaxBeanInfo expectedType) throws JAXBException {
if (reader == null) {
throw new IllegalArgumentException(
Messages.format(Messages.NULL_READER));
}
try {
XMLEvent event = reader.peek();
if (!event.isStartElement() && !event.isStartDocument()) {
// TODO: convert event into event name
throw new IllegalStateException(
Messages.format(
Messages.ILLEGAL_READER_STATE,event.getEventType()));
}
// Quick hack until SJSXP fixes 6270116
boolean isZephyr = reader.getClass().getName().equals("com.sun.xml.internal.stream.XMLReaderImpl");
XmlVisitor h = createUnmarshallerHandler(null,false,expectedType);
if(!isZephyr) {
h = new InterningXmlVisitor(h);
}
new StAXEventConnector(reader,h).bridge();
return h.getContext().getResult();
} catch (XMLStreamException e) {
throw handleStreamException(e);
}
}
public Object unmarshal0( InputStream input, JaxBeanInfo expectedType ) throws JAXBException {
return unmarshal0(getXMLReader(),new InputSource(input),expectedType);
}
private static JAXBException handleStreamException(XMLStreamException e) {
// StAXStreamConnector wraps SAXException to XMLStreamException.
// XMLStreamException doesn't print its nested stack trace when it prints
// its stack trace, so if we wrap XMLStreamException in JAXBException,
// it becomes harder to find out the real problem.
// So we unwrap them here. But we don't want to unwrap too eagerly, because
// that could throw away some meaningful exception information.
Throwable ne = e.getNestedException();
if(ne instanceof JAXBException) {
return (JAXBException)ne;
}
if(ne instanceof SAXException) {
return new UnmarshalException(ne);
}
return new UnmarshalException(e);
}
@Override
public Object getProperty(String name) throws PropertyException {
if(name.equals(IDResolver.class.getName())) {
return idResolver;
}
return super.getProperty(name);
}
@Override
public void setProperty(String name, Object value) throws PropertyException {
if(name.equals(FACTORY)) {
coordinator.setFactories(value);
return;
}
if(name.equals(IDResolver.class.getName())) {
idResolver = (IDResolver)value;
return;
}
if(name.equals(ClassResolver.class.getName())) {
coordinator.classResolver = (ClassResolver)value;
return;
}
if(name.equals(ClassLoader.class.getName())) {
coordinator.classLoader = (ClassLoader)value;
return;
}
super.setProperty(name, value);
}
public static final String FACTORY = "com.sun.xml.internal.bind.ObjectFactory";
@Override
public void setSchema(Schema schema) {
this.schema = schema;
}
@Override
public Schema getSchema() {
return schema;
}
@Override
public AttachmentUnmarshaller getAttachmentUnmarshaller() {
return attachmentUnmarshaller;
}
@Override
public void setAttachmentUnmarshaller(AttachmentUnmarshaller au) {
this.attachmentUnmarshaller = au;
}
/**
* @deprecated since 2.0
*/
@Override
public boolean isValidating() {
throw new UnsupportedOperationException();
}
/**
* @deprecated since 2.0
*/
@Override
public void setValidating(boolean validating) {
throw new UnsupportedOperationException();
}
@Override
public <A extends XmlAdapter> void setAdapter(Class<A> type, A adapter) {
if (type==null) {
throw new IllegalArgumentException();
}
coordinator.putAdapter(type,adapter);
}
@Override
public <A extends XmlAdapter> A getAdapter(Class<A> type) {
if(type==null) {
throw new IllegalArgumentException();
}
if(coordinator.containsAdapter(type)) {
return coordinator.getAdapter(type);
} else {
return null;
}
}
// opening up for public use
@Override
public UnmarshalException createUnmarshalException( SAXException e ) {
return super.createUnmarshalException(e);
}
/**
* Default error handling behavior for {@link Unmarshaller}.
*/
public boolean handleEvent(ValidationEvent event) {
return event.getSeverity()!=ValidationEvent.FATAL_ERROR;
}
private static InputSource streamSourceToInputSource( StreamSource ss ) {
InputSource is = new InputSource();
is.setSystemId( ss.getSystemId() );
is.setByteStream( ss.getInputStream() );
is.setCharacterStream( ss.getReader() );
return is;
}
public <T> JaxBeanInfo<T> getBeanInfo(Class<T> clazz) throws JAXBException {
return context.getBeanInfo(clazz,true);
}
@Override
public Listener getListener() {
return externalListener;
}
@Override
public void setListener(Listener listener) {
externalListener = listener;
}
public UnmarshallingContext getContext() {
return coordinator;
}
@Override
@SuppressWarnings("FinalizeDeclaration")
protected void finalize() throws Throwable {
try {
ClassFactory.cleanCache();
} finally {
super.finalize();
}
}
/**
* Must be called from same thread which created the UnmarshallerImpl instance.
* @throws IOException
*/
public void close() throws IOException {
ClassFactory.cleanCache();
}
}