blob: 4250963e47abecc4b9627a423636c3a2e34e9b21 [file] [log] [blame]
/**
* 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<>();
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);
}
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);
}
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;
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
}
protected Result getStreamResult(OutputStream os, Annotation[] anns, MediaType mt) throws Exception {
return new StreamResult(os);
}
@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;
}
org.apache.cxf.common.jaxb.JAXBUtils.setMinimumEscapeHandler(ms);
TransformerHandler th;
try {
th = factory.newTransformerHandler(t);
} catch (TransformerConfigurationException ex) {
TemplatesImpl ti = (TemplatesImpl)t;
th = factory.newTransformerHandler(ti.getTemplates());
this.trySettingProperties(th, ti);
}
Result result = getStreamResult(os, anns, mt);
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;
}
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) {
if (urlStream == null) {
return null;
}
try (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;
}
}
}