package org.jboss.resteasy.plugins.providers.jaxb;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.Providers;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlType;
import javax.xml.namespace.QName;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;

import org.jboss.resteasy.annotations.providers.jaxb.DoNotUseJAXBProvider;
import org.jboss.resteasy.annotations.providers.jaxb.Wrapped;
import org.jboss.resteasy.core.ResteasyContext;
import org.jboss.resteasy.core.messagebody.AsyncBufferedMessageBodyWriter;
import org.jboss.resteasy.plugins.providers.jaxb.i18n.LogMessages;
import org.jboss.resteasy.plugins.providers.jaxb.i18n.Messages;
import org.jboss.resteasy.spi.ResteasyConfiguration;
import org.jboss.resteasy.spi.util.FindAnnotation;
import org.jboss.resteasy.spi.util.Types;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;

Author:Bill Burke
Version:$Revision: 1 $
/** * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $ */
@Provider @Produces({"application/xml", "application/*+xml", "text/xml", "text/*+xml"}) @Consumes({"application/xml", "application/*+xml", "text/xml", "text/*+xml"}) public class CollectionProvider implements MessageBodyReader<Object>, AsyncBufferedMessageBodyWriter<Object> { @Context protected Providers providers; private boolean disableExternalEntities = true; private boolean enableSecureProcessingFeature = true; private boolean disableDTDs = true; public CollectionProvider() { LogMessages.LOGGER.debugf("Provider : %s, Method : CollectionProvider", getClass().getName()); ResteasyConfiguration context = ResteasyContext.getContextData(ResteasyConfiguration.class); if (context != null) { String s = context.getParameter("resteasy.document.expand.entity.references"); if (s != null) { setDisableExternalEntities(!Boolean.parseBoolean(s)); } s = context.getParameter("resteasy.document.secure.processing.feature"); if (s != null) { setEnableSecureProcessingFeature(Boolean.parseBoolean(s)); } s = context.getParameter("resteasy.document.secure.disableDTDs"); if (s != null) { setDisableDTDs(Boolean.parseBoolean(s)); } } } protected JAXBContextFinder getFinder(MediaType type) { ContextResolver<JAXBContextFinder> resolver = providers.getContextResolver(JAXBContextFinder.class, type); if (resolver == null) return null; return resolver.getContext(null); } public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return isWrapped(type, genericType, annotations, mediaType); } protected boolean isWrapped(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { if ((Collection.class.isAssignableFrom(type) || type.isArray()) && genericType != null) { Class<?> baseType = Types.getCollectionBaseType(type, genericType); if (baseType == null) return false; baseType = XmlAdapterWrapper.xmlAdapterValueType(baseType, annotations); return (baseType.isAnnotationPresent(XmlRootElement.class) || baseType.isAnnotationPresent(XmlType.class) || baseType.isAnnotationPresent(XmlSeeAlso.class) || JAXBElement.class.equals(baseType)) && (FindAnnotation.findAnnotation(baseType, annotations, DoNotUseJAXBProvider.class) == null) && !IgnoredMediaTypes.ignored(baseType, annotations, mediaType); } return false; } public Object getJAXBObject(JAXBContextFinder finder, MediaType mediaType, Class<?> clazz, Element element) throws JAXBException { JAXBContext ctx = finder.findCachedContext(clazz, mediaType, null); return ctx.createUnmarshaller().unmarshal(element); } @SuppressWarnings("unchecked") public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { LogMessages.LOGGER.debugf("Provider : %s, Method : readFrom", getClass().getName()); JAXBContextFinder finder = getFinder(mediaType); if (finder == null) { throw new JAXBUnmarshalException(Messages.MESSAGES.unableToFindJAXBContext(mediaType)); } Class baseType = Types.getCollectionBaseType(type, genericType); XmlAdapterWrapper xmlAdapter = XmlAdapterWrapper.getXmlAdapter(baseType, annotations); JaxbCollection col = null; try { JAXBElement<JaxbCollection> ele = null; if (needsSecurity()) { SAXSource source = null; if (getCharset(mediaType) == null) { source = new SAXSource(new InputSource(new InputStreamReader(entityStream, StandardCharsets.UTF_8))); } else { source = new SAXSource(new InputSource(entityStream)); } JAXBContext ctx = finder.findCachedContext(JaxbCollection.class, mediaType, annotations); Unmarshaller unmarshaller = ctx.createUnmarshaller(); unmarshaller = new SecureUnmarshaller(unmarshaller, disableExternalEntities, enableSecureProcessingFeature, disableDTDs); ele = unmarshaller.unmarshal(source, JaxbCollection.class); } else { StreamSource source = null; if (getCharset(mediaType) == null) { source = new StreamSource(new InputStreamReader(entityStream, StandardCharsets.UTF_8)); } else { source = new StreamSource(entityStream); } JAXBContext ctx = finder.findCachedContext(JaxbCollection.class, mediaType, annotations); ele = ctx.createUnmarshaller().unmarshal(source, JaxbCollection.class); } Wrapped wrapped = FindAnnotation.findAnnotation(annotations, Wrapped.class); if (wrapped != null) { if (!wrapped.element().equals(ele.getName().getLocalPart())) { throw new JAXBUnmarshalException(Messages.MESSAGES.collectionWrappingFailedLocalPart(wrapped.element(), ele.getName().getLocalPart())); } if (!wrapped.namespace().equals(ele.getName().getNamespaceURI())) { throw new JAXBUnmarshalException(Messages.MESSAGES.collectionWrappingFailedNamespace(wrapped.namespace(), ele.getName().getNamespaceURI())); } } col = ele.getValue(); } catch (JAXBException e) { throw new JAXBUnmarshalException(e); } try { JAXBContext ctx = null; if (xmlAdapter != null) { Class<?> adaptedType = xmlAdapter.getValueType(); ctx = finder.findCachedContext(adaptedType, mediaType, null); } else { ctx = finder.findCachedContext(baseType, mediaType, null); } Unmarshaller unmarshaller = ctx.createUnmarshaller(); unmarshaller = AbstractJAXBProvider.decorateUnmarshaller(baseType, annotations, mediaType, unmarshaller); if (type.isArray()) { Object array = Array.newInstance(baseType, col.getValue().size()); for (int i = 0; i < col.getValue().size(); i++) { Element val = (Element) col.getValue().get(i); Object o = unmarshaller.unmarshal(val); if (xmlAdapter != null) { try { o = xmlAdapter.unmarshal(o); } catch (Exception e) { throw new JAXBUnmarshalException(e); } } Array.set(array, i, o); } return array; } else { Collection outCol = null; if (type.isInterface()) { if (List.class.isAssignableFrom(type)) outCol = new ArrayList(); else if (SortedSet.class.isAssignableFrom(type)) outCol = new TreeSet(); else if (Set.class.isAssignableFrom(type)) outCol = new HashSet(); else outCol = new ArrayList(); } else { try { outCol = (Collection) type.newInstance(); } catch (Exception e) { throw new JAXBUnmarshalException(e); } } for (Object obj : col.getValue()) { Element val = (Element) obj; Object o = unmarshaller.unmarshal(val); if (xmlAdapter != null) { try { o = xmlAdapter.unmarshal(o); } catch (Exception e) { throw new JAXBUnmarshalException(e); } } outCol.add(o); } return outCol; } } catch (JAXBException e) { throw new JAXBUnmarshalException(e); } } public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return isWrapped(type, genericType, annotations, mediaType); } public long getSize(Object entry, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return -1; } public void writeTo(Object entry, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { LogMessages.LOGGER.debugf("Provider : %s, Method : writeTo", getClass().getName()); JAXBContextFinder finder = getFinder(mediaType); if (finder == null) { throw new JAXBMarshalException(Messages.MESSAGES.unableToFindJAXBContext(mediaType)); } Class baseType = Types.getCollectionBaseType(type, genericType); XmlAdapterWrapper xmlAdapter = XmlAdapterWrapper.getXmlAdapter(baseType, annotations); if (xmlAdapter != null) { baseType = xmlAdapter.getValueType(); } try { JAXBContext ctx = finder.findCacheContext(mediaType, annotations, JaxbCollection.class, baseType); JaxbCollection col = new JaxbCollection(); if (type.isArray()) { Object[] array = (Object[]) entry; for (Object obj : array) { if (xmlAdapter != null) { try { obj = xmlAdapter.marshal(obj); } catch (Exception e) { throw new JAXBUnmarshalException(e); } } col.getValue().add(obj); } } else { Collection collection = (Collection) entry; for (Object obj : collection) { if (xmlAdapter != null) { try { obj = xmlAdapter.marshal(obj); } catch (Exception e) { throw new JAXBUnmarshalException(e); } } col.getValue().add(obj); } } String element = "collection"; String namespaceURI = ""; String prefix = ""; Wrapped wrapped = FindAnnotation.findAnnotation(annotations, Wrapped.class); if (wrapped != null) { element = wrapped.element(); namespaceURI = wrapped.namespace(); prefix = wrapped.prefix(); } JAXBElement<JaxbCollection> collection = new JAXBElement<JaxbCollection>(new QName(namespaceURI, element, prefix), JaxbCollection.class, col); Marshaller marshaller = ctx.createMarshaller(); AbstractJAXBProvider.decorateMarshaller(baseType, annotations, mediaType, marshaller); marshaller.marshal(collection, entityStream); } catch (JAXBException e) { throw new JAXBMarshalException(e); } } public boolean isDisableExternalEntities() { return disableExternalEntities; } public void setDisableExternalEntities(boolean disableExternalEntities) { this.disableExternalEntities = disableExternalEntities; } public boolean isEnableSecureProcessingFeature() { return enableSecureProcessingFeature; } public void setEnableSecureProcessingFeature(boolean enableSecureProcessingFeature) { this.enableSecureProcessingFeature = enableSecureProcessingFeature; } public boolean isDisableDTDs() { return disableDTDs; } public void setDisableDTDs(boolean disableDTDs) { this.disableDTDs = disableDTDs; } public static String getCharset(final MediaType mediaType) { if (mediaType != null) { return mediaType.getParameters().get("charset"); } return null; } protected boolean needsSecurity() { return true; } }