/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.cxf.jaxrs.provider; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.ws.rs.Consumes; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.PathSegment; import javax.ws.rs.core.UriInfo; import javax.ws.rs.ext.Provider; import javax.xml.XMLConstants; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamWriter; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Templates; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.URIResolver; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.sax.SAXSource; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.xml.sax.XMLFilter; import org.xml.sax.XMLReader; import org.apache.cxf.common.classloader.ClassLoaderUtils; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.io.CachedOutputStream; import org.apache.cxf.jaxrs.ext.MessageContext; import org.apache.cxf.jaxrs.ext.xml.XSLTTransform; import org.apache.cxf.jaxrs.utils.AnnotationUtils; import org.apache.cxf.jaxrs.utils.ExceptionUtils; import org.apache.cxf.jaxrs.utils.InjectionUtils; import org.apache.cxf.jaxrs.utils.JAXRSUtils; import org.apache.cxf.jaxrs.utils.ResourceUtils; import org.apache.cxf.staxutils.StaxSource; import org.apache.cxf.staxutils.StaxUtils; @Produces({"application/xml", "application/*+xml", "text/xml", "text/html" }) @Consumes({"application/xml", "application/*+xml", "text/xml", "text/html" }) @Provider public class XSLTJaxbProvider<T> extends JAXBElementProvider<T> { private static final Logger LOG = LogUtils.getL7dLogger(XSLTJaxbProvider.class); private static final String ABSOLUTE_PATH_PARAMETER = "absolute.path"; private static final String BASE_PATH_PARAMETER = "base.path"; private static final String RELATIVE_PATH_PARAMETER = "relative.path"; private static final String XSLT_TEMPLATE_PROPERTY = "xslt.template"; private SAXTransformerFactory factory; private Templates inTemplates; private Templates outTemplates; private Map<String, Templates> inMediaTemplates; private Map<String, Templates> outMediaTemplates; private ConcurrentHashMap<String, Templates> annotationTemplates = new ConcurrentHashMap<String, Templates>(); private List<String> inClassesToHandle; private List<String> outClassesToHandle; private Map<String, Object> inParamsMap; private Map<String, Object> outParamsMap; private Map<String, String> inProperties; private Map<String, String> outProperties; private URIResolver uriResolver; private String systemId; private boolean supportJaxbOnly; private boolean refreshTemplates; private boolean secureProcessing = true; public void setSupportJaxbOnly(boolean support) { this.supportJaxbOnly = support; } @Override public boolean isReadable(Class<?> type, Type genericType, Annotation[] anns, MediaType mt) { if (!super.isReadable(type, genericType, anns, mt)) { return false; } if (InjectionUtils.isSupportedCollectionOrArray(type)) { return supportJaxbOnly; } // if the user has set the list of in classes and a given class // is in that list then it can only be handled by the template if (inClassCanBeHandled(type.getName()) || inClassesToHandle == null && !supportJaxbOnly) { return inTemplatesAvailable(type, anns, mt); } else { return supportJaxbOnly; } } @Override public boolean isWriteable(Class<?> type, Type genericType, Annotation[] anns, MediaType mt) { // JAXB support is required if (!super.isWriteable(type, genericType, anns, mt)) { return false; } if (InjectionUtils.isSupportedCollectionOrArray(type)) { return supportJaxbOnly; } // if the user has set the list of out classes and a given class // is in that list then it can only be handled by the template if (outClassCanBeHandled(type.getName()) || outClassesToHandle == null && !supportJaxbOnly) { return outTemplatesAvailable(type, anns, mt); } else { return supportJaxbOnly; } } protected boolean inTemplatesAvailable(Class<?> cls, Annotation[] anns, MediaType mt) { return inTemplates != null || inMediaTemplates != null && inMediaTemplates.containsKey(mt.getType() + "/" + mt.getSubtype()) || getTemplatesFromAnnotation(cls, anns, mt) != null; } protected boolean outTemplatesAvailable(Class<?> cls, Annotation[] anns, MediaType mt) { return outTemplates != null || outMediaTemplates != null && outMediaTemplates.containsKey(mt.getType() + "/" + mt.getSubtype()) || getTemplatesFromAnnotation(cls, anns, mt) != null; } protected Templates getTemplatesFromAnnotation(Class<?> cls, Annotation[] anns, MediaType mt) { Templates t = null; XSLTTransform ann = getXsltTransformAnn(anns, mt); if (ann != null) { t = annotationTemplates.get(ann.value()); if (t == null || refreshTemplates) { String path = ann.value(); final String cp = "classpath:"; if (!path.startsWith(cp)) { path = cp + path; } t = createTemplates(path); if (t == null) { createTemplates(ClassLoaderUtils.getResource(ann.value(), cls)); } if (t != null) { annotationTemplates.put(ann.value(), t); } } } return t; } protected Templates getAnnotationTemplates(Annotation[] anns) { Templates t = null; XSLTTransform ann = AnnotationUtils.getAnnotation(anns, XSLTTransform.class); if (ann != null) { t = annotationTemplates.get(ann.value()); } return t; } protected XSLTTransform getXsltTransformAnn(Annotation[] anns, MediaType mt) { XSLTTransform ann = AnnotationUtils.getAnnotation(anns, XSLTTransform.class); if (ann != null && ann.type() != XSLTTransform.TransformType.CLIENT) { if (ann.mediaTypes().length > 0) { for (String s : ann.mediaTypes()) { if (mt.isCompatible(JAXRSUtils.toMediaType(s))) { return ann; } } return null; } return ann; } return null; } protected Templates getInTemplates(Annotation[] anns, MediaType mt) { Templates t = createTemplatesFromContext(); if (t != null) { return t; } t = inTemplates != null ? inTemplates : inMediaTemplates != null ? inMediaTemplates.get(mt.getType() + "/" + mt.getSubtype()) : null; if (t == null) { t = getAnnotationTemplates(anns); } return t; } protected Templates getOutTemplates(Annotation[] anns, MediaType mt) { Templates t = createTemplatesFromContext(); if (t != null) { return t; } t = outTemplates != null ? outTemplates : outMediaTemplates != null ? outMediaTemplates.get(mt.getType() + "/" + mt.getSubtype()) : null; if (t == null) { t = getAnnotationTemplates(anns); } return t; } @Override protected Object unmarshalFromInputStream(Unmarshaller unmarshaller, InputStream is, Annotation[] anns, MediaType mt) throws JAXBException { try { Templates t = createTemplates(getInTemplates(anns, mt), inParamsMap, inProperties); if (t == null && supportJaxbOnly) { return super.unmarshalFromInputStream(unmarshaller, is, anns, mt); } if (unmarshaller.getClass().getName().contains("eclipse")) { //eclipse MOXy doesn't work properly with the XMLFilter/Reader thing //so we need to bounce through a DOM Source reader = new StaxSource(StaxUtils.createXMLStreamReader(is)); DOMResult dom = new DOMResult(); t.newTransformer().transform(reader, dom); return unmarshaller.unmarshal(dom.getNode()); } XMLFilter filter = null; try { filter = factory.newXMLFilter(t); } catch (TransformerConfigurationException ex) { TemplatesImpl ti = (TemplatesImpl)t; filter = factory.newXMLFilter(ti.getTemplates()); trySettingProperties(filter, ti); } XMLReader reader = new StaxSource(StaxUtils.createXMLStreamReader(is)); filter.setParent(reader); SAXSource source = new SAXSource(); source.setXMLReader(filter); if (systemId != null) { source.setSystemId(systemId); } return unmarshaller.unmarshal(source); } catch (TransformerException ex) { LOG.warning("Transformation exception : " + ex.getMessage()); throw ExceptionUtils.toInternalServerErrorException(ex, null); } } private void trySettingProperties(Object filter, TemplatesImpl ti) { try { //Saxon doesn't allow creating a Filter or Handler from anything other than it's original //Templates. That then requires setting the parameters after the fact, but there //isn't a standard API for that, so we have to grab the Transformer via reflection to //set the parameters. Transformer tr = (Transformer)filter.getClass().getMethod("getTransformer").invoke(filter); tr.setURIResolver(ti.resolver); for (Map.Entry<String, Object> entry : ti.transformParameters.entrySet()) { tr.setParameter(entry.getKey(), entry.getValue()); } for (Map.Entry<String, String> entry : ti.outProps.entrySet()) { tr.setOutputProperty(entry.getKey(), entry.getValue()); } } catch (Exception e) { LOG.log(Level.WARNING, "Could not set properties for transfomer", e); } } @Override protected Object unmarshalFromReader(Unmarshaller unmarshaller, XMLStreamReader reader, Annotation[] anns, MediaType mt) throws JAXBException { CachedOutputStream out = new CachedOutputStream(); try { XMLStreamWriter writer = StaxUtils.createXMLStreamWriter(out); StaxUtils.copy(new StaxSource(reader), writer); writer.writeEndDocument(); writer.flush(); writer.close(); return unmarshalFromInputStream(unmarshaller, out.getInputStream(), anns, mt); } catch (Exception ex) { throw ExceptionUtils.toBadRequestException(ex, null); } } @Override protected void marshalToWriter(Marshaller ms, Object obj, XMLStreamWriter writer, Annotation[] anns, MediaType mt) throws Exception { CachedOutputStream out = new CachedOutputStream(); marshalToOutputStream(ms, obj, out, anns, mt); StaxUtils.copy(new StreamSource(out.getInputStream()), writer); } @Override protected void addAttachmentMarshaller(Marshaller ms) { // complete } @Override protected void marshalToOutputStream(Marshaller ms, Object obj, OutputStream os, Annotation[] anns, MediaType mt) throws Exception { Templates t = createTemplates(getOutTemplates(anns, mt), outParamsMap, outProperties); if (t == null && supportJaxbOnly) { super.marshalToOutputStream(ms, obj, os, anns, mt); return; } TransformerHandler th = null; try { th = factory.newTransformerHandler(t); } catch (TransformerConfigurationException ex) { TemplatesImpl ti = (TemplatesImpl)t; th = factory.newTransformerHandler(ti.getTemplates()); this.trySettingProperties(th, ti); } Result result = new StreamResult(os); if (systemId != null) { result.setSystemId(systemId); } th.setResult(result); if (getContext() == null) { th.startDocument(); } ms.marshal(obj, th); if (getContext() == null) { th.endDocument(); } } public void setOutTemplate(String loc) { outTemplates = createTemplates(loc); } public void setInTemplate(String loc) { inTemplates = createTemplates(loc); } public void setInMediaTemplates(Map<String, String> map) { inMediaTemplates = new HashMap<>(); for (Map.Entry<String, String> entry : map.entrySet()) { inMediaTemplates.put(entry.getKey(), createTemplates(entry.getValue())); } } public void setOutMediaTemplates(Map<String, String> map) { outMediaTemplates = new HashMap<>(); for (Map.Entry<String, String> entry : map.entrySet()) { outMediaTemplates.put(entry.getKey(), createTemplates(entry.getValue())); } } public void setResolver(URIResolver resolver) { uriResolver = resolver; if (factory != null) { factory.setURIResolver(uriResolver); } } public void setSystemId(String system) { systemId = system; } public void setInParameters(Map<String, Object> inParams) { this.inParamsMap = inParams; } public void setOutParameters(Map<String, Object> outParams) { this.outParamsMap = outParams; } public void setInProperties(Map<String, String> inProps) { this.inProperties = inProps; } public void setOutProperties(Map<String, String> outProps) { this.outProperties = outProps; } public void setInClassNames(List<String> classNames) { inClassesToHandle = classNames; } public boolean inClassCanBeHandled(String className) { return inClassesToHandle != null && inClassesToHandle.contains(className); } public void setOutClassNames(List<String> classNames) { outClassesToHandle = classNames; } public boolean outClassCanBeHandled(String className) { return outClassesToHandle != null && outClassesToHandle.contains(className); } protected Templates createTemplates(Templates templates, Map<String, Object> configuredParams, Map<String, String> outProps) { if (templates == null) { if (supportJaxbOnly) { return null; } else { LOG.severe("No template is available"); throw ExceptionUtils.toInternalServerErrorException(null, null); } } TemplatesImpl templ = new TemplatesImpl(templates, uriResolver); MessageContext mc = getContext(); if (mc != null) { UriInfo ui = mc.getUriInfo(); MultivaluedMap<String, String> params = ui.getPathParameters(); for (Map.Entry<String, List<String>> entry : params.entrySet()) { String value = entry.getValue().get(0); int ind = value.indexOf(";"); if (ind > 0) { value = value.substring(0, ind); } templ.setTransformerParameter(entry.getKey(), value); } List<PathSegment> segments = ui.getPathSegments(); if (!segments.isEmpty()) { setTransformParameters(templ, segments.get(segments.size() - 1).getMatrixParameters()); } setTransformParameters(templ, ui.getQueryParameters()); templ.setTransformerParameter(ABSOLUTE_PATH_PARAMETER, ui.getAbsolutePath().toString()); templ.setTransformerParameter(RELATIVE_PATH_PARAMETER, ui.getPath()); templ.setTransformerParameter(BASE_PATH_PARAMETER, ui.getBaseUri().toString()); if (configuredParams != null) { for (Map.Entry<String, Object> entry : configuredParams.entrySet()) { templ.setTransformerParameter(entry.getKey(), entry.getValue()); } } } if (outProps != null) { templ.setOutProperties(outProps); } return templ; } private void setTransformParameters(TemplatesImpl templ, MultivaluedMap<String, String> params) { for (Map.Entry<String, List<String>> entry : params.entrySet()) { templ.setTransformerParameter(entry.getKey(), entry.getValue().get(0)); } } protected Templates createTemplates(String loc) { try { return createTemplates(ResourceUtils.getResourceURL(loc, this.getBus())); } catch (Exception ex) { LOG.warning("No template can be created : " + ex.getMessage()); } return null; } protected Templates createTemplatesFromContext() { MessageContext mc = getContext(); if (mc != null) { String template = (String)mc.getContextualProperty(XSLT_TEMPLATE_PROPERTY); if (template != null) { return createTemplates(template); } } return null; } protected Templates createTemplates(URL urlStream) { try { if (urlStream == null) { return null; } Reader r = new BufferedReader( new InputStreamReader(urlStream.openStream(), StandardCharsets.UTF_8)); Source source = new StreamSource(r); source.setSystemId(urlStream.toExternalForm()); if (factory == null) { factory = (SAXTransformerFactory)TransformerFactory.newInstance(); factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, secureProcessing); if (uriResolver != null) { factory.setURIResolver(uriResolver); } } return factory.newTemplates(source); } catch (Exception ex) { LOG.warning("No template can be created : " + ex.getMessage()); } return null; } public void setRefreshTemplates(boolean refresh) { this.refreshTemplates = refresh; } public void setSecureProcessing(boolean secureProcessing) { this.secureProcessing = secureProcessing; } private static class TemplatesImpl implements Templates { private Templates templates; private URIResolver resolver; private Map<String, Object> transformParameters = new HashMap<>(); private Map<String, String> outProps = new HashMap<>(); TemplatesImpl(Templates templates, URIResolver resolver) { this.templates = templates; this.resolver = resolver; } public Templates getTemplates() { return templates; } public void setTransformerParameter(String name, Object value) { transformParameters.put(name, value); } public void setOutProperties(Map<String, String> props) { this.outProps = props; } public Properties getOutputProperties() { return templates.getOutputProperties(); } public Transformer newTransformer() throws TransformerConfigurationException { Transformer tr = templates.newTransformer(); tr.setURIResolver(resolver); for (Map.Entry<String, Object> entry : transformParameters.entrySet()) { tr.setParameter(entry.getKey(), entry.getValue()); } for (Map.Entry<String, String> entry : outProps.entrySet()) { tr.setOutputProperty(entry.getKey(), entry.getValue()); } return tr; } } }