blob: f264b481b6afd692913ae0ada36ca71924b932a3 [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.utils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.ws.rs.ClientErrorException;
import javax.ws.rs.Consumes;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.container.ResourceContext;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.PathSegment;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Providers;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.ReaderInterceptorContext;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;
import javax.xml.namespace.QName;
import org.apache.cxf.common.i18n.BundleUtils;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.common.util.PackageUtils;
import org.apache.cxf.common.util.PropertyUtils;
import org.apache.cxf.common.util.ReflectionUtil;
import org.apache.cxf.common.util.StringUtils;
import org.apache.cxf.helpers.DOMUtils;
import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.helpers.LoadingByteArrayOutputStream;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.io.CacheAndWriteOutputStream;
import org.apache.cxf.io.ReaderInputStream;
import org.apache.cxf.jaxrs.JAXRSServiceImpl;
import org.apache.cxf.jaxrs.ext.ContextProvider;
import org.apache.cxf.jaxrs.ext.DefaultMethod;
import org.apache.cxf.jaxrs.ext.MessageContext;
import org.apache.cxf.jaxrs.ext.MessageContextImpl;
import org.apache.cxf.jaxrs.ext.ProtocolHeaders;
import org.apache.cxf.jaxrs.ext.ProtocolHeadersImpl;
import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
import org.apache.cxf.jaxrs.impl.AsyncResponseImpl;
import org.apache.cxf.jaxrs.impl.ContainerRequestContextImpl;
import org.apache.cxf.jaxrs.impl.ContainerResponseContextImpl;
import org.apache.cxf.jaxrs.impl.HttpHeadersImpl;
import org.apache.cxf.jaxrs.impl.MediaTypeHeaderProvider;
import org.apache.cxf.jaxrs.impl.MetadataMap;
import org.apache.cxf.jaxrs.impl.PathSegmentImpl;
import org.apache.cxf.jaxrs.impl.ProvidersImpl;
import org.apache.cxf.jaxrs.impl.ReaderInterceptorContextImpl;
import org.apache.cxf.jaxrs.impl.ReaderInterceptorMBR;
import org.apache.cxf.jaxrs.impl.RequestImpl;
import org.apache.cxf.jaxrs.impl.ResourceContextImpl;
import org.apache.cxf.jaxrs.impl.ResourceInfoImpl;
import org.apache.cxf.jaxrs.impl.ResponseBuilderImpl;
import org.apache.cxf.jaxrs.impl.ResponseImpl;
import org.apache.cxf.jaxrs.impl.SecurityContextImpl;
import org.apache.cxf.jaxrs.impl.UriInfoImpl;
import org.apache.cxf.jaxrs.impl.WriterInterceptorContextImpl;
import org.apache.cxf.jaxrs.impl.WriterInterceptorMBW;
import org.apache.cxf.jaxrs.model.BeanParamInfo;
import org.apache.cxf.jaxrs.model.BeanResourceInfo;
import org.apache.cxf.jaxrs.model.ClassResourceInfo;
import org.apache.cxf.jaxrs.model.ClassResourceInfoComparator;
import org.apache.cxf.jaxrs.model.MethodInvocationInfo;
import org.apache.cxf.jaxrs.model.OperationResourceInfo;
import org.apache.cxf.jaxrs.model.OperationResourceInfoComparator;
import org.apache.cxf.jaxrs.model.OperationResourceInfoStack;
import org.apache.cxf.jaxrs.model.Parameter;
import org.apache.cxf.jaxrs.model.ParameterType;
import org.apache.cxf.jaxrs.model.ProviderInfo;
import org.apache.cxf.jaxrs.model.URITemplate;
import org.apache.cxf.jaxrs.provider.AbstractConfigurableProvider;
import org.apache.cxf.jaxrs.provider.ProviderFactory;
import org.apache.cxf.jaxrs.provider.ServerProviderFactory;
import org.apache.cxf.jaxrs.utils.multipart.AttachmentUtils;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageUtils;
import org.apache.cxf.phase.PhaseInterceptorChain;
import org.apache.cxf.service.Service;
public final class JAXRSUtils {
public static final MediaType ALL_TYPES = new MediaType();
public static final String ROOT_RESOURCE_CLASS = "root.resource.class";
public static final String IGNORE_MESSAGE_WRITERS = "ignore.message.writers";
public static final String ROOT_INSTANCE = "service.root.instance";
public static final String ROOT_PROVIDER = "service.root.provider";
public static final String EXCEPTION_FROM_MAPPER = "exception.from.mapper";
public static final String SECOND_JAXRS_EXCEPTION = "second.jaxrs.exception";
public static final String PARTIAL_HIERARCHICAL_MEDIA_SUBTYPE_CHECK =
"media.subtype.partial.check";
public static final String DOC_LOCATION = "wadl.location";
public static final String MEDIA_TYPE_Q_PARAM = "q";
public static final String MEDIA_TYPE_QS_PARAM = "qs";
private static final String MEDIA_TYPE_DISTANCE_PARAM = "d";
private static final String DEFAULT_CONTENT_TYPE = "default.content.type";
private static final String KEEP_SUBRESOURCE_CANDIDATES = "keep.subresource.candidates";
private static final Logger LOG = LogUtils.getL7dLogger(JAXRSUtils.class);
private static final ResourceBundle BUNDLE = BundleUtils.getBundle(JAXRSUtils.class);
private static final String PATH_SEGMENT_SEP = "/";
private static final String REPORT_FAULT_MESSAGE_PROPERTY = "org.apache.cxf.jaxrs.report-fault-message";
private static final String NO_CONTENT_EXCEPTION = "javax.ws.rs.core.NoContentException";
private static final String HTTP_CHARSET_PARAM = "charset";
private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
private static final Set<Class<?>> STREAMING_OUT_TYPES = new HashSet<>(
Arrays.asList(InputStream.class, Reader.class, StreamingOutput.class));
private JAXRSUtils() {
}
public static boolean isStreamingOutType(Class<?> type) {
return STREAMING_OUT_TYPES.contains(type);
}
public static List<PathSegment> getPathSegments(String thePath, boolean decode) {
return getPathSegments(thePath, decode, true);
}
public static List<PathSegment> getPathSegments(String thePath, boolean decode,
boolean ignoreLastSlash) {
List<PathSegment> theList =
Arrays.asList(thePath.split("/")).stream()
.filter(StringUtils.notEmpty())
.map(p -> new PathSegmentImpl(p, decode))
.collect(Collectors.toList());
int len = thePath.length();
if (len > 0 && thePath.charAt(len - 1) == '/') {
String value = ignoreLastSlash ? "" : "/";
theList.add(new PathSegmentImpl(value, false));
}
return theList;
}
private static String[] getUserMediaTypes(Object provider, boolean consumes) {
String[] values = null;
if (AbstractConfigurableProvider.class.isAssignableFrom(provider.getClass())) {
final List<String> types;
if (consumes) {
types = ((AbstractConfigurableProvider)provider).getConsumeMediaTypes();
} else {
types = ((AbstractConfigurableProvider)provider).getProduceMediaTypes();
}
if (types != null) {
values = !types.isEmpty() ? types.toArray(new String[0])
: new String[]{"*/*"};
}
}
return values;
}
public static List<MediaType> getProviderConsumeTypes(MessageBodyReader<?> provider) {
String[] values = getUserMediaTypes(provider, true);
if (values == null) {
return getConsumeTypes(provider.getClass().getAnnotation(Consumes.class));
}
return JAXRSUtils.getMediaTypes(values);
}
public static List<MediaType> getProviderProduceTypes(MessageBodyWriter<?> provider) {
String[] values = getUserMediaTypes(provider, false);
if (values == null) {
return getProduceTypes(provider.getClass().getAnnotation(Produces.class));
}
return JAXRSUtils.getMediaTypes(values);
}
public static List<MediaType> getMediaTypes(String[] values) {
List<MediaType> supportedMimeTypes = new ArrayList<>(values.length);
for (int i = 0; i < values.length; i++) {
supportedMimeTypes.addAll(parseMediaTypes(values[i]));
}
return supportedMimeTypes;
}
public static void injectParameters(OperationResourceInfo ori,
Object requestObject,
Message message) {
injectParameters(ori, ori.getClassResourceInfo(), requestObject, message);
}
@SuppressWarnings("unchecked")
public static void injectParameters(OperationResourceInfo ori,
BeanResourceInfo bri,
Object requestObject,
Message message) {
if (bri.isSingleton()
&& (!bri.getParameterMethods().isEmpty() || !bri.getParameterFields().isEmpty())) {
LOG.fine("Injecting request parameters into singleton resource is not thread-safe");
}
// Param methods
MultivaluedMap<String, String> values =
(MultivaluedMap<String, String>)message.get(URITemplate.TEMPLATE_PARAMETERS);
for (Method m : bri.getParameterMethods()) {
Parameter p = ResourceUtils.getParameter(0, m.getAnnotations(),
m.getParameterTypes()[0]);
Object o;
if (p.getType() == ParameterType.BEAN) {
o = createBeanParamValue(message, m.getParameterTypes()[0], ori);
} else {
o = createHttpParameterValue(p,
m.getParameterTypes()[0],
m.getGenericParameterTypes()[0],
m.getParameterAnnotations()[0],
message,
values,
ori);
}
InjectionUtils.injectThroughMethod(requestObject, m, o, message);
}
// Param fields
for (Field f : bri.getParameterFields()) {
Parameter p = ResourceUtils.getParameter(0, f.getAnnotations(),
f.getType());
final Object o;
if (p.getType() == ParameterType.BEAN) {
o = createBeanParamValue(message, f.getType(), ori);
} else {
o = createHttpParameterValue(p,
f.getType(),
f.getGenericType(),
f.getAnnotations(),
message,
values,
ori);
}
InjectionUtils.injectFieldValue(f, requestObject, o);
}
}
public static Map<ClassResourceInfo, MultivaluedMap<String, String>> selectResourceClass(
List<ClassResourceInfo> resources, String path, Message message) {
LOG.fine(() -> new org.apache.cxf.common.i18n.Message("START_CRI_MATCH",
BUNDLE,
path).toString());
if (resources.size() == 1) {
MultivaluedMap<String, String> values = new MetadataMap<>();
return resources.get(0).getURITemplate().match(path, values)
? Collections.singletonMap(resources.get(0), values) : null;
}
SortedMap<ClassResourceInfo, MultivaluedMap<String, String>> candidateList =
new TreeMap<ClassResourceInfo, MultivaluedMap<String, String>>(
new ClassResourceInfoComparator(message));
for (ClassResourceInfo cri : resources) {
MultivaluedMap<String, String> map = new MetadataMap<>();
if (cri.getURITemplate().match(path, map)) {
candidateList.put(cri, map);
LOG.fine(() -> new org.apache.cxf.common.i18n.Message("CRI_SELECTED_POSSIBLY",
BUNDLE,
cri.getServiceClass().getName(),
path,
cri.getURITemplate().getValue()).toString());
} else {
LOG.fine(() -> new org.apache.cxf.common.i18n.Message("CRI_NO_MATCH",
BUNDLE,
path,
cri.getServiceClass().getName()).toString());
}
}
if (!candidateList.isEmpty()) {
Map<ClassResourceInfo, MultivaluedMap<String, String>> cris =
new LinkedHashMap<>(candidateList.size());
ClassResourceInfo firstCri = null;
for (Map.Entry<ClassResourceInfo, MultivaluedMap<String, String>> entry : candidateList.entrySet()) {
ClassResourceInfo cri = entry.getKey();
if (cris.isEmpty()) {
firstCri = cri;
cris.put(cri, entry.getValue());
} else if (firstCri != null
&& URITemplate.compareTemplates(firstCri.getURITemplate(), cri.getURITemplate()) == 0) {
cris.put(cri, entry.getValue());
} else {
break;
}
LOG.fine(() -> new org.apache.cxf.common.i18n.Message("CRI_SELECTED",
BUNDLE,
cri.getServiceClass().getName(),
path, cri.getURITemplate().getValue()).toString());
}
return cris;
}
return null;
}
public static OperationResourceInfo findTargetMethod(
Map<ClassResourceInfo, MultivaluedMap<String, String>> matchedResources,
Message message,
String httpMethod,
MultivaluedMap<String, String> matchedValues,
String requestContentType,
List<MediaType> acceptContentTypes) {
return findTargetMethod(matchedResources, message, httpMethod, matchedValues,
requestContentType, acceptContentTypes, true, true);
}
//CHECKSTYLE:OFF
public static OperationResourceInfo findTargetMethod(
Map<ClassResourceInfo, MultivaluedMap<String, String>> matchedResources,
Message message,
String httpMethod,
MultivaluedMap<String, String> matchedValues,
String requestContentType,
List<MediaType> acceptContentTypes,
boolean throwException,
boolean recordMatchedUri) {
//CHECKSTYLE:ON
final boolean getMethod = HttpMethod.GET.equals(httpMethod);
MediaType requestType;
try {
requestType = toMediaType(requestContentType);
} catch (IllegalArgumentException ex) {
throw ExceptionUtils.toNotSupportedException(ex, null);
}
SortedMap<OperationResourceInfo, MultivaluedMap<String, String>> candidateList =
new TreeMap<OperationResourceInfo, MultivaluedMap<String, String>>(
new OperationResourceInfoComparator(message, httpMethod,
getMethod, requestType, acceptContentTypes));
int pathMatched = 0;
int methodMatched = 0;
int consumeMatched = 0;
List<OperationResourceInfo> finalPathSubresources = null;
for (Map.Entry<ClassResourceInfo, MultivaluedMap<String, String>> rEntry : matchedResources.entrySet()) {
ClassResourceInfo resource = rEntry.getKey();
MultivaluedMap<String, String> values = rEntry.getValue();
String path = getCurrentPath(values);
LOG.fine(() -> new org.apache.cxf.common.i18n.Message("START_OPER_MATCH",
BUNDLE,
resource.getServiceClass().getName()).toString());
for (OperationResourceInfo ori : resource.getMethodDispatcher().getOperationResourceInfos()) {
boolean added = false;
URITemplate uriTemplate = ori.getURITemplate();
MultivaluedMap<String, String> map = new MetadataMap<>(values);
if (uriTemplate != null && uriTemplate.match(path, map)) {
String finalGroup = map.getFirst(URITemplate.FINAL_MATCH_GROUP);
boolean finalPath = StringUtils.isEmpty(finalGroup) || PATH_SEGMENT_SEP.equals(finalGroup);
if (ori.isSubResourceLocator()) {
candidateList.put(ori, map);
if (finalPath) {
if (finalPathSubresources == null) {
finalPathSubresources = new LinkedList<>();
}
finalPathSubresources.add(ori);
}
added = true;
} else if (finalPath) {
pathMatched++;
if (matchHttpMethod(ori.getHttpMethod(), httpMethod)) {
methodMatched++;
//CHECKSTYLE:OFF
if (getMethod || matchConsumeTypes(requestType, ori)) {
consumeMatched++;
for (MediaType acceptType : acceptContentTypes) {
if (matchProduceTypes(acceptType, ori)) {
candidateList.put(ori, map);
added = true;
break;
}
}
}
//CHECKSTYLE:ON
}
}
}
LOG.fine(matchMessageLogSupplier(ori, path, httpMethod, requestType, acceptContentTypes, added));
}
}
if (finalPathSubresources != null && pathMatched > 0
&& !MessageUtils.getContextualBoolean(message, KEEP_SUBRESOURCE_CANDIDATES, false)) {
for (OperationResourceInfo key : finalPathSubresources) {
candidateList.remove(key);
}
}
if (!candidateList.isEmpty()) {
Map.Entry<OperationResourceInfo, MultivaluedMap<String, String>> firstEntry =
candidateList.entrySet().iterator().next();
matchedValues.clear();
matchedValues.putAll(firstEntry.getValue());
OperationResourceInfo ori = firstEntry.getKey();
if (headMethodPossible(ori.getHttpMethod(), httpMethod)) {
LOG.info(new org.apache.cxf.common.i18n.Message("GET_INSTEAD_OF_HEAD",
BUNDLE, ori.getClassResourceInfo().getServiceClass().getName(),
ori.getMethodToInvoke().getName()).toString());
}
LOG.fine(() -> new org.apache.cxf.common.i18n.Message("OPER_SELECTED",
BUNDLE, ori.getMethodToInvoke().getName(),
ori.getClassResourceInfo().getServiceClass().getName()).toString());
if (!ori.isSubResourceLocator()) {
MediaType responseMediaType = intersectSortMediaTypes(acceptContentTypes,
ori.getProduceTypes(),
false).get(0);
message.getExchange().put(Message.CONTENT_TYPE, mediaTypeToString(responseMediaType,
MEDIA_TYPE_Q_PARAM,
MEDIA_TYPE_QS_PARAM));
}
if (recordMatchedUri) {
pushOntoStack(ori, matchedValues, message);
}
return ori;
}
if (!throwException) {
return null;
}
int status;
// criteria matched the least number of times will determine the error code;
// priority : path, method, consumes, produces;
if (pathMatched == 0) {
status = 404;
} else if (methodMatched == 0) {
status = 405;
} else if (consumeMatched == 0) {
status = 415;
} else {
// Not a single Produces match
status = 406;
}
Map.Entry<ClassResourceInfo, MultivaluedMap<String, String>> firstCri =
matchedResources.entrySet().iterator().next();
String name = firstCri.getKey().isRoot() ? "NO_OP_EXC" : "NO_SUBRESOURCE_METHOD_FOUND";
org.apache.cxf.common.i18n.Message errorMsg =
new org.apache.cxf.common.i18n.Message(name,
BUNDLE,
message.get(Message.REQUEST_URI),
getCurrentPath(firstCri.getValue()),
httpMethod,
mediaTypeToString(requestType),
convertTypesToString(acceptContentTypes));
if (!"OPTIONS".equalsIgnoreCase(httpMethod)) {
Level logLevel = getExceptionLogLevel(message, ClientErrorException.class);
LOG.log(logLevel == null ? Level.FINE : logLevel, () -> errorMsg.toString());
}
Response response =
createResponse(getRootResources(message), message, errorMsg.toString(), status, methodMatched == 0);
throw ExceptionUtils.toHttpException(null, response);
}
public static Level getExceptionLogLevel(Message message, Class<? extends WebApplicationException> exClass) {
Level logLevel = null;
Object logLevelProp = message.get(exClass.getName() + ".log.level");
if (logLevelProp != null) {
if (logLevelProp instanceof Level) {
logLevel = (Level)logLevelProp;
} else {
try {
logLevel = Level.parse(logLevelProp.toString());
} catch (Exception ex) {
// ignore
}
}
}
return logLevel;
}
private static List<MediaType> intersectSortMediaTypes(List<MediaType> acceptTypes,
List<MediaType> producesTypes,
final boolean checkDistance) {
List<MediaType> all = intersectMimeTypes(acceptTypes, producesTypes, true, checkDistance);
if (all.size() > 1) {
all.sort(new Comparator<MediaType>() {
public int compare(MediaType mt1, MediaType mt2) {
int result = compareMediaTypes(mt1, mt2, null);
if (result == 0) {
result = compareQualityAndDistance(mt1, mt2, checkDistance);
}
return result;
}
});
}
return all;
}
private static int compareQualityAndDistance(MediaType mt1, MediaType mt2, boolean checkDistance) {
int result = compareMediaTypesQualityFactors(mt1, mt2, MEDIA_TYPE_Q_PARAM);
if (result == 0) {
result = compareMediaTypesQualityFactors(mt1, mt2, MEDIA_TYPE_QS_PARAM);
}
if (result == 0 && checkDistance) {
Integer dist1 = Integer.valueOf(mt1.getParameters().get(MEDIA_TYPE_DISTANCE_PARAM));
Integer dist2 = Integer.valueOf(mt2.getParameters().get(MEDIA_TYPE_DISTANCE_PARAM));
result = dist1.compareTo(dist2);
}
return result;
}
private static String getCurrentPath(MultivaluedMap<String, String> values) {
String path = values.getFirst(URITemplate.FINAL_MATCH_GROUP);
return path == null ? "/" : path;
}
public static List<ClassResourceInfo> getRootResources(Message message) {
Service service = message.getExchange().getService();
return ((JAXRSServiceImpl)service).getClassResourceInfos();
}
public static boolean noResourceMethodForOptions(Response exResponse, String httpMethod) {
return exResponse != null && exResponse.getStatus() == 405
&& "OPTIONS".equalsIgnoreCase(httpMethod);
}
private static Supplier<String> matchMessageLogSupplier(OperationResourceInfo ori,
String path, String httpMethod, MediaType requestType, List<MediaType> acceptContentTypes,
boolean added) {
org.apache.cxf.common.i18n.Message errorMsg = added
? new org.apache.cxf.common.i18n.Message("OPER_SELECTED_POSSIBLY",
BUNDLE, ori.getMethodToInvoke().getName())
: new org.apache.cxf.common.i18n.Message("OPER_NO_MATCH",
BUNDLE,
ori.getMethodToInvoke().getName(),
path,
ori.getURITemplate().getValue(),
httpMethod,
ori.getHttpMethod(),
requestType.toString(),
convertTypesToString(ori.getConsumeTypes()),
convertTypesToString(acceptContentTypes),
convertTypesToString(ori.getProduceTypes()));
return () -> errorMsg.toString();
}
public static Response createResponse(List<ClassResourceInfo> cris, Message msg,
String responseMessage, int status, boolean addAllow) {
ResponseBuilder rb = toResponseBuilder(status);
if (addAllow) {
Set<String> allowedMethods = new HashSet<>();
for (ClassResourceInfo cri : cris) {
allowedMethods.addAll(cri.getAllowedMethods());
}
for (String m : allowedMethods) {
rb.header("Allow", m);
}
// "OPTIONS" are supported all the time really
if (!allowedMethods.contains("OPTIONS")) {
rb.header("Allow", "OPTIONS");
}
if (!allowedMethods.contains("HEAD") && allowedMethods.contains("GET")) {
rb.header("Allow", "HEAD");
}
}
if (msg != null && MessageUtils.getContextualBoolean(msg, REPORT_FAULT_MESSAGE_PROPERTY)) {
rb.type(MediaType.TEXT_PLAIN_TYPE).entity(responseMessage);
}
return rb.build();
}
private static boolean matchHttpMethod(String expectedMethod, String httpMethod) {
return expectedMethod.equalsIgnoreCase(httpMethod)
|| headMethodPossible(expectedMethod, httpMethod)
|| expectedMethod.equals(DefaultMethod.class.getSimpleName());
}
public static boolean headMethodPossible(String expectedMethod, String httpMethod) {
return HttpMethod.HEAD.equalsIgnoreCase(httpMethod) && HttpMethod.GET.equals(expectedMethod);
}
private static String convertTypesToString(List<MediaType> types) {
StringBuilder sb = new StringBuilder();
for (MediaType type : types) {
sb.append(mediaTypeToString(type)).append(',');
}
return sb.toString();
}
public static List<MediaType> getConsumeTypes(Consumes cm) {
return getConsumeTypes(cm, Collections.singletonList(ALL_TYPES));
}
public static List<MediaType> getConsumeTypes(Consumes cm, List<MediaType> defaultTypes) {
return cm == null ? defaultTypes
: getMediaTypes(cm.value());
}
public static List<MediaType> getProduceTypes(Produces pm) {
return getProduceTypes(pm, Collections.singletonList(ALL_TYPES));
}
public static List<MediaType> getProduceTypes(Produces pm, List<MediaType> defaultTypes) {
return pm == null ? defaultTypes
: getMediaTypes(pm.value());
}
public static int compareSortedConsumesMediaTypes(List<MediaType> mts1, List<MediaType> mts2, MediaType ct) {
List<MediaType> actualMts1 = getCompatibleMediaTypes(mts1, ct);
List<MediaType> actualMts2 = getCompatibleMediaTypes(mts2, ct);
return compareSortedMediaTypes(actualMts1, actualMts2, null);
}
public static int compareSortedAcceptMediaTypes(List<MediaType> mts1, List<MediaType> mts2,
List<MediaType> acceptTypes) {
List<MediaType> actualMts1 = intersectSortMediaTypes(mts1, acceptTypes, true);
List<MediaType> actualMts2 = intersectSortMediaTypes(mts2, acceptTypes, true);
int size1 = actualMts1.size();
int size2 = actualMts2.size();
for (int i = 0; i < size1 && i < size2; i++) {
int result = compareMediaTypes(actualMts1.get(i), actualMts2.get(i), null);
if (result == 0) {
result = compareQualityAndDistance(actualMts1.get(i), actualMts2.get(i), true);
}
if (result != 0) {
return result;
}
}
return size1 == size2 ? 0 : size1 < size2 ? -1 : 1;
}
private static List<MediaType> getCompatibleMediaTypes(List<MediaType> mts, MediaType ct) {
List<MediaType> actualMts;
if (mts.size() == 1) {
actualMts = mts;
} else {
actualMts = new LinkedList<>();
for (MediaType mt : mts) {
if (isMediaTypeCompatible(mt, ct)) {
actualMts.add(mt);
}
}
}
return actualMts;
}
public static int compareSortedMediaTypes(List<MediaType> mts1, List<MediaType> mts2, String qs) {
int size1 = mts1.size();
int size2 = mts2.size();
for (int i = 0; i < size1 && i < size2; i++) {
int result = compareMediaTypes(mts1.get(i), mts2.get(i), qs);
if (result != 0) {
return result;
}
}
return size1 == size2 ? 0 : size1 < size2 ? -1 : 1;
}
public static int compareMethodParameters(Class<?>[] paraList1, Class<?>[] paraList2) {
int size1 = paraList1.length;
int size2 = paraList2.length;
for (int i = 0; i < size1 && i < size2; i++) {
if (!paraList1[i].equals(paraList2[i])) {
// Handling the case when bridge / synthetic methods may be taken
// into account (f.e. when service implements generic interfaces or
// extends the generic classes).
if (paraList1[i].isAssignableFrom(paraList2[i])) {
return 1;
} else if (paraList2[i].isAssignableFrom(paraList1[i])) {
return -1;
} else {
int result = paraList1[i].getName().compareTo(paraList2[i].getName());
if (result != 0) {
return result;
}
}
}
}
return size1 == size2 ? 0 : size1 < size2 ? -1 : 1;
}
public static int compareMediaTypes(MediaType mt1, MediaType mt2) {
return compareMediaTypes(mt1, mt2, MEDIA_TYPE_Q_PARAM);
}
public static int compareMediaTypes(MediaType mt1, MediaType mt2, String qs) {
boolean mt1TypeWildcard = mt1.isWildcardType();
boolean mt2TypeWildcard = mt2.isWildcardType();
if (mt1TypeWildcard && !mt2TypeWildcard) {
return 1;
}
if (!mt1TypeWildcard && mt2TypeWildcard) {
return -1;
}
boolean mt1SubTypeWildcard = mt1.getSubtype().contains(MediaType.MEDIA_TYPE_WILDCARD);
boolean mt2SubTypeWildcard = mt2.getSubtype().contains(MediaType.MEDIA_TYPE_WILDCARD);
if (mt1SubTypeWildcard && !mt2SubTypeWildcard) {
return 1;
}
if (!mt1SubTypeWildcard && mt2SubTypeWildcard) {
return -1;
}
return qs != null ? compareMediaTypesQualityFactors(mt1, mt2, qs) : 0;
}
public static int compareMediaTypesQualityFactors(MediaType mt1, MediaType mt2) {
float q1 = getMediaTypeQualityFactor(mt1.getParameters().get(MEDIA_TYPE_Q_PARAM));
float q2 = getMediaTypeQualityFactor(mt2.getParameters().get(MEDIA_TYPE_Q_PARAM));
return Float.compare(q1, q2) * -1;
}
public static int compareMediaTypesQualityFactors(MediaType mt1, MediaType mt2, String qs) {
float q1 = getMediaTypeQualityFactor(mt1.getParameters().get(qs));
float q2 = getMediaTypeQualityFactor(mt2.getParameters().get(qs));
return Float.compare(q1, q2) * -1;
}
public static float getMediaTypeQualityFactor(String q) {
if (q == null) {
return 1;
}
if (q.charAt(0) == '.') {
q = '0' + q;
}
try {
return Float.parseFloat(q);
} catch (NumberFormatException ex) {
// default value will do
}
return 1;
}
//Message contains following information: PATH, HTTP_REQUEST_METHOD, CONTENT_TYPE, InputStream.
public static List<Object> processParameters(OperationResourceInfo ori,
MultivaluedMap<String, String> values,
Message message)
throws IOException, WebApplicationException {
Class<?>[] parameterTypes = ori.getInParameterTypes();
List<Parameter> paramsInfo = ori.getParameters();
boolean preferModelParams = paramsInfo.size() > parameterTypes.length
&& !PropertyUtils.isTrue(message.getContextualProperty("org.apache.cxf.preferMethodParameters"));
final int parameterTypesLength = preferModelParams ? paramsInfo.size() : parameterTypes.length;
if (parameterTypesLength < 1) {
return Collections.emptyList();
}
Type[] genericParameterTypes = ori.getInGenericParameterTypes();
Annotation[][] anns = ori.getInParameterAnnotations();
Object[] params = new Object[parameterTypesLength];
// Ensure we process all request-body parameters first, then all @*Params, etc.
ParamTuple[] tuple = new ParamTuple[parameterTypesLength];
for (int i = 0; i < parameterTypesLength; i++) {
tuple[i] = new ParamTuple();
if (!preferModelParams) {
tuple[i].param = parameterTypes[i];
tuple[i].genericParam = InjectionUtils.processGenericTypeIfNeeded(
ori.getClassResourceInfo().getServiceClass(), tuple[i].param, genericParameterTypes[i]);
tuple[i].param = InjectionUtils.updateParamClassToTypeIfNeeded(tuple[i].param,
tuple[i].genericParam);
tuple[i].paramAnns = anns == null ? EMPTY_ANNOTATIONS : anns[i];
} else {
tuple[i].param = paramsInfo.get(i).getJavaType();
tuple[i].genericParam = tuple[i].param;
tuple[i].paramAnns = EMPTY_ANNOTATIONS;
}
if (paramsInfo.get(i).getType() == ParameterType.REQUEST_BODY) {
params[i] = processRequestBodyParameter(tuple[i].param,
tuple[i].genericParam,
tuple[i].paramAnns,
message,
ori);
}
}
for (int i = 0; i < parameterTypesLength; i++) {
if (paramsInfo.get(i).getType() != ParameterType.REQUEST_BODY) {
params[i] = processParameter(tuple[i].param,
tuple[i].genericParam,
tuple[i].paramAnns,
paramsInfo.get(i),
values,
message,
ori);
}
}
return Arrays.asList(params);
}
private static class ParamTuple {
private Class<?> param;
private Type genericParam;
private Annotation[] paramAnns;
}
private static Object processRequestBodyParameter(Class<?> parameterClass,
Type parameterType,
Annotation[] parameterAnns,
Message message,
OperationResourceInfo ori)
throws IOException, WebApplicationException {
if (parameterClass == AsyncResponse.class) {
return new AsyncResponseImpl(message);
}
String contentType = (String)message.get(Message.CONTENT_TYPE);
if (contentType == null) {
String defaultCt = (String)message.getContextualProperty(DEFAULT_CONTENT_TYPE);
contentType = defaultCt == null ? MediaType.APPLICATION_OCTET_STREAM : defaultCt;
}
final MediaType contentTypeMt = toMediaType(contentType);
final MessageContext mc = new MessageContextImpl(message);
MediaType mt = mc.getHttpHeaders().getMediaType();
if (mt == null) {
mt = contentTypeMt;
}
InputStream is;
if (mt.isCompatible(MediaType.APPLICATION_FORM_URLENCODED_TYPE)) {
is = copyAndGetEntityStream(message);
} else {
is = message.getContent(InputStream.class);
}
if (is == null) {
Reader reader = message.getContent(Reader.class);
if (reader != null) {
is = new ReaderInputStream(reader);
}
}
return readFromMessageBody(parameterClass,
parameterType,
parameterAnns,
is,
contentTypeMt,
ori,
message);
}
private static Object processParameter(Class<?> parameterClass,
Type parameterType,
Annotation[] parameterAnns,
Parameter parameter,
MultivaluedMap<String, String> values,
Message message,
OperationResourceInfo ori)
throws IOException, WebApplicationException {
if (parameter.getType() == ParameterType.REQUEST_BODY) {
return processRequestBodyParameter(parameterClass, parameterType, parameterAnns, message, ori);
} else if (parameter.getType() == ParameterType.CONTEXT) {
return createContextValue(message, parameterType, parameterClass);
} else if (parameter.getType() == ParameterType.BEAN) {
return createBeanParamValue(message, parameterClass, ori);
} else {
return createHttpParameterValue(parameter,
parameterClass,
parameterType,
parameterAnns,
message,
values,
ori);
}
}
public static Object createHttpParameterValue(Parameter parameter,
Class<?> parameterClass,
Type genericParam,
Annotation[] paramAnns,
Message message,
MultivaluedMap<String, String> values,
OperationResourceInfo ori) {
boolean isEncoded = parameter.isEncoded() || ori != null && ori.isEncodedEnabled();
String defaultValue = parameter.getDefaultValue();
if (defaultValue == null && ori != null) {
defaultValue = ori.getDefaultParameterValue();
}
if (parameter.getType() == ParameterType.PATH) {
return readFromUriParam(message, parameter.getName(), parameterClass, genericParam,
paramAnns, values, defaultValue, !isEncoded);
}
if (parameter.getType() == ParameterType.QUERY) {
return readQueryString(parameter.getName(), parameterClass, genericParam,
paramAnns, message, defaultValue, !isEncoded);
}
if (parameter.getType() == ParameterType.MATRIX) {
return processMatrixParam(message, parameter.getName(), parameterClass, genericParam,
paramAnns, defaultValue, !isEncoded);
}
if (parameter.getType() == ParameterType.FORM) {
return processFormParam(message, parameter.getName(), parameterClass, genericParam,
paramAnns, defaultValue, !isEncoded);
}
if (parameter.getType() == ParameterType.COOKIE) {
return processCookieParam(message, parameter.getName(), parameterClass, genericParam,
paramAnns, defaultValue);
}
Object result = null;
if (parameter.getType() == ParameterType.HEADER) {
result = processHeaderParam(message, parameter.getName(), parameterClass, genericParam,
paramAnns, defaultValue);
}
return result;
}
private static Object processMatrixParam(Message m, String key,
Class<?> pClass, Type genericType,
Annotation[] paramAnns,
String defaultValue,
boolean decode) {
List<PathSegment> segments = JAXRSUtils.getPathSegments(
(String)m.get(Message.REQUEST_URI), decode);
if (!segments.isEmpty()) {
MultivaluedMap<String, String> params = new MetadataMap<>();
for (PathSegment ps : segments) {
MultivaluedMap<String, String> matrix = ps.getMatrixParameters();
for (Map.Entry<String, List<String>> entry : matrix.entrySet()) {
for (String value : entry.getValue()) {
params.add(entry.getKey(), value);
}
}
}
if ("".equals(key)) {
return InjectionUtils.handleBean(pClass, paramAnns, params, ParameterType.MATRIX, m, false);
}
List<String> values = params.get(key);
return InjectionUtils.createParameterObject(values,
pClass,
genericType,
paramAnns,
defaultValue,
false,
ParameterType.MATRIX,
m);
}
return null;
}
private static Object processFormParam(Message m, String key,
Class<?> pClass, Type genericType,
Annotation[] paramAnns,
String defaultValue,
boolean decode) {
MessageContext mc = new MessageContextImpl(m);
MediaType mt = mc.getHttpHeaders().getMediaType();
@SuppressWarnings("unchecked")
MultivaluedMap<String, String> params =
(MultivaluedMap<String, String>)m.get(FormUtils.FORM_PARAM_MAP);
String enc = HttpUtils.getEncoding(mt, StandardCharsets.UTF_8.name());
if (params == null) {
params = new MetadataMap<>();
m.put(FormUtils.FORM_PARAM_MAP, params);
if (mt == null || mt.isCompatible(MediaType.APPLICATION_FORM_URLENCODED_TYPE)) {
InputStream entityStream = copyAndGetEntityStream(m);
String body = FormUtils.readBody(entityStream, enc);
// Do not decode unless the key is empty value, fe @FormParam("")
FormUtils.populateMapFromStringOrHttpRequest(params, m, body, enc, StringUtils.isEmpty(key) && decode);
} else {
if ("multipart".equalsIgnoreCase(mt.getType())
&& MediaType.MULTIPART_FORM_DATA_TYPE.isCompatible(mt)) {
MultipartBody body = AttachmentUtils.getMultipartBody(mc);
FormUtils.populateMapFromMultipart(params, body, m, decode);
} else {
org.apache.cxf.common.i18n.Message errorMsg =
new org.apache.cxf.common.i18n.Message("WRONG_FORM_MEDIA_TYPE",
BUNDLE,
mt.toString());
LOG.warning(errorMsg.toString());
throw ExceptionUtils.toNotSupportedException(null, null);
}
}
}
if (decode) {
List<String> values = params.get(key);
if (values != null) {
values = values.stream().map(value -> HttpUtils.urlDecode(value, enc)).collect(Collectors.toList());
params.replace(key, values);
}
}
if ("".equals(key)) {
return InjectionUtils.handleBean(pClass, paramAnns, params, ParameterType.FORM, m, false);
}
List<String> results = params.get(key);
return InjectionUtils.createParameterObject(results,
pClass,
genericType,
paramAnns,
defaultValue,
false,
ParameterType.FORM,
m);
}
public static MultivaluedMap<String, String> getMatrixParams(String path, boolean decode) {
int index = path.indexOf(';');
return index == -1 ? new MetadataMap<String, String>()
: JAXRSUtils.getStructuredParams(path.substring(index + 1), ";", decode, false);
}
private static Object processHeaderParam(Message m,
String header,
Class<?> pClass,
Type genericType,
Annotation[] paramAnns,
String defaultValue) {
List<String> values = new HttpHeadersImpl(m).getRequestHeader(header);
if (values != null && values.isEmpty()) {
values = null;
}
return InjectionUtils.createParameterObject(values,
pClass,
genericType,
paramAnns,
defaultValue,
false,
ParameterType.HEADER,
m);
}
private static Object processCookieParam(Message m, String cookieName,
Class<?> pClass, Type genericType,
Annotation[] paramAnns, String defaultValue) {
Cookie c = new HttpHeadersImpl(m).getCookies().get(cookieName);
if (c == null && defaultValue != null) {
c = Cookie.valueOf(cookieName + '=' + defaultValue);
}
if (c == null) {
return null;
}
if (pClass.isAssignableFrom(Cookie.class)) {
return c;
}
String value = InjectionUtils.isSupportedCollectionOrArray(pClass)
&& InjectionUtils.getActualType(genericType) == Cookie.class
? c.toString() : c.getValue();
return InjectionUtils.createParameterObject(Collections.singletonList(value),
pClass,
genericType,
paramAnns,
null,
false,
ParameterType.COOKIE,
m);
}
public static Object createBeanParamValue(Message m, Class<?> clazz, OperationResourceInfo ori) {
BeanParamInfo bmi = ServerProviderFactory.getInstance(m).getBeanParamInfo(clazz);
if (bmi == null) {
// we could've started introspecting now but the fact no bean info
// is available indicates that the one created at start up has been
// lost and hence it is 500
LOG.warning("Bean parameter info is not available");
throw ExceptionUtils.toInternalServerErrorException(null, null);
}
Object instance;
try {
instance = clazz.newInstance();
} catch (Throwable t) {
throw ExceptionUtils.toInternalServerErrorException(t, null);
}
JAXRSUtils.injectParameters(ori, bmi, instance, m);
InjectionUtils.injectContexts(instance, bmi, m);
return instance;
}
public static Message getContextMessage(Message m) {
Message contextMessage = m.getExchange() != null ? m.getExchange().getInMessage() : m;
if (contextMessage == null && !PropertyUtils.isTrue(m.get(Message.INBOUND_MESSAGE))) {
contextMessage = m;
}
return contextMessage;
}
public static <T> T createContextValue(Message m, Type genericType, Class<T> clazz) {
Message contextMessage = getContextMessage(m);
Object o = null;
if (UriInfo.class.isAssignableFrom(clazz)) {
o = createUriInfo(contextMessage);
} else if (HttpHeaders.class.isAssignableFrom(clazz)
|| ProtocolHeaders.class.isAssignableFrom(clazz)) {
o = createHttpHeaders(contextMessage, clazz);
} else if (SecurityContext.class.isAssignableFrom(clazz)) {
SecurityContext customContext = contextMessage.get(SecurityContext.class);
o = customContext == null ? new SecurityContextImpl(contextMessage) : customContext;
} else if (MessageContext.class.isAssignableFrom(clazz)) {
o = new MessageContextImpl(m);
} else if (ResourceInfo.class.isAssignableFrom(clazz)) {
o = new ResourceInfoImpl(contextMessage);
} else if (ResourceContext.class.isAssignableFrom(clazz)) {
o = new ResourceContextImpl(contextMessage, contextMessage.getExchange().get(OperationResourceInfo.class));
} else if (Request.class.isAssignableFrom(clazz)) {
o = new RequestImpl(contextMessage);
} else if (Providers.class.isAssignableFrom(clazz)) {
o = new ProvidersImpl(contextMessage);
} else if (ContextResolver.class.isAssignableFrom(clazz)) {
o = createContextResolver(genericType, contextMessage);
} else if (Configuration.class.isAssignableFrom(clazz)) {
o = ProviderFactory.getInstance(contextMessage).getConfiguration(contextMessage);
} else if (Application.class.isAssignableFrom(clazz)) {
ProviderInfo<?> providerInfo =
(ProviderInfo<?>)contextMessage.getExchange().getEndpoint().get(Application.class.getName());
o = providerInfo == null ? null : providerInfo.getProvider();
} else if (contextMessage != null) {
ContextProvider<?> provider =
ProviderFactory.getInstance(contextMessage).createContextProvider(clazz, contextMessage);
if (provider != null) {
o = provider.createContext(contextMessage);
}
}
if (o == null && contextMessage != null && !MessageUtils.isRequestor(contextMessage)) {
o = HttpUtils.createServletResourceValue(contextMessage, clazz);
}
return clazz.cast(o);
}
@SuppressWarnings("unchecked")
private static UriInfo createUriInfo(Message m) {
if (MessageUtils.isRequestor(m)) {
m = m.getExchange() != null ? m.getExchange().getOutMessage() : m;
}
MultivaluedMap<String, String> templateParams =
(MultivaluedMap<String, String>)m.get(URITemplate.TEMPLATE_PARAMETERS);
return new UriInfoImpl(m, templateParams);
}
private static Object createHttpHeaders(Message m, Class<?> ctxClass) {
if (MessageUtils.isRequestor(m)) {
m = m.getExchange() != null ? m.getExchange().getOutMessage() : m;
}
return HttpHeaders.class.isAssignableFrom(ctxClass) ? new HttpHeadersImpl(m)
: new ProtocolHeadersImpl(m);
}
public static ContextResolver<?> createContextResolver(Type genericType, Message m) {
if (genericType instanceof ParameterizedType) {
return ProviderFactory.getInstance(m).createContextResolver(
((ParameterizedType)genericType).getActualTypeArguments()[0], m);
} else if (m != null) {
return ProviderFactory.getInstance(m).createContextResolver(genericType, m);
} else {
return null;
}
}
public static Object createResourceValue(Message m, Type genericType, Class<?> clazz) {
// lets assume we're aware of servlet types only that can be @Resource-annotated
return createContextValue(m, genericType, clazz);
}
//CHECKSTYLE:OFF
private static Object readFromUriParam(Message m,
String parameterName,
Class<?> paramType,
Type genericType,
Annotation[] paramAnns,
MultivaluedMap<String, String> values,
String defaultValue,
boolean decoded) {
//CHECKSTYLE:ON
if ("".equals(parameterName)) {
return InjectionUtils.handleBean(paramType, paramAnns, values, ParameterType.PATH, m, decoded);
}
List<String> results = values.get(parameterName);
return InjectionUtils.createParameterObject(results,
paramType,
genericType,
paramAnns,
defaultValue,
decoded,
ParameterType.PATH,
m);
}
//TODO : multiple query string parsing, do it once
private static Object readQueryString(String queryName,
Class<?> paramType,
Type genericType,
Annotation[] paramAnns,
Message m,
String defaultValue,
boolean decode) {
MultivaluedMap<String, String> queryMap = new UriInfoImpl(m, null).getQueryParameters(decode);
if ("".equals(queryName)) {
return InjectionUtils.handleBean(paramType, paramAnns, queryMap, ParameterType.QUERY, m, false);
}
return InjectionUtils.createParameterObject(queryMap.get(queryName),
paramType,
genericType,
paramAnns,
defaultValue,
false,
ParameterType.QUERY, m);
}
/**
* Retrieve map of query parameters from the passed in message
* @return a Map of query parameters.
*/
public static MultivaluedMap<String, String> getStructuredParams(String query,
String sep,
boolean decode,
boolean decodePlus) {
MultivaluedMap<String, String> map =
new MetadataMap<>(new LinkedHashMap<String, List<String>>());
getStructuredParams(map, query, sep, decode, decodePlus);
return map;
}
public static void getStructuredParams(MultivaluedMap<String, String> queries,
String query,
String sep,
boolean decode,
boolean decodePlus) {
getStructuredParams(queries, query, sep, decode, decodePlus, false);
}
public static void getStructuredParams(MultivaluedMap<String, String> queries,
String query,
String sep,
boolean decode,
boolean decodePlus,
boolean valueIsCollection) {
if (!StringUtils.isEmpty(query)) {
for (String part : query.split(sep)) { // fastpath expected
int index = part.indexOf('=');
final String name;
String value = null;
if (index == -1) {
name = part;
} else {
name = part.substring(0, index);
value = index < part.length() ? part.substring(index + 1) : "";
}
if (valueIsCollection) {
if (value != null) {
for (String s : value.split(",")) {
addStructuredPartToMap(queries, sep, name, s, decode, decodePlus);
}
}
} else {
addStructuredPartToMap(queries, sep, name, value, decode, decodePlus);
}
}
}
}
private static void addStructuredPartToMap(MultivaluedMap<String, String> queries,
String sep,
String name,
String value,
boolean decode,
boolean decodePlus) {
if (value != null) {
if (decodePlus && value.contains("+")) {
value = value.replace('+', ' ');
}
if (decode) {
value = (";".equals(sep))
? HttpUtils.pathDecode(value) : HttpUtils.urlDecode(value);
}
}
queries.add(HttpUtils.urlDecode(name), value);
}
private static Object readFromMessageBody(Class<?> targetTypeClass,
Type parameterType,
Annotation[] parameterAnnotations,
InputStream is,
MediaType contentType,
OperationResourceInfo ori,
Message m) throws IOException, WebApplicationException {
List<MediaType> types = JAXRSUtils.intersectMimeTypes(ori.getConsumeTypes(), contentType);
final ProviderFactory pf = ServerProviderFactory.getInstance(m);
for (MediaType type : types) {
List<ReaderInterceptor> readers = pf.createMessageBodyReaderInterceptor(
targetTypeClass,
parameterType,
parameterAnnotations,
type,
m,
true,
ori.getNameBindings());
if (readers != null) {
try {
return readFromMessageBodyReader(readers,
targetTypeClass,
parameterType,
parameterAnnotations,
is,
type,
m);
} catch (IOException e) {
if (e.getClass().getName().equals(NO_CONTENT_EXCEPTION)) {
throw ExceptionUtils.toBadRequestException(e, null);
}
throw e;
} catch (WebApplicationException ex) {
throw ex;
} catch (Exception ex) {
throw new Fault(ex);
}
}
}
logMessageHandlerProblem("NO_MSG_READER", targetTypeClass, contentType);
throw new WebApplicationException(Response.Status.UNSUPPORTED_MEDIA_TYPE);
}
@SuppressWarnings("unchecked")
public static Object readFromMessageBodyReader(List<ReaderInterceptor> readers,
Class<?> targetTypeClass,
Type parameterType,
Annotation[] parameterAnnotations,
InputStream is,
MediaType mediaType,
Message m) throws IOException, WebApplicationException {
// Verbose but avoids an extra context instantiation for the typical path
if (readers.size() > 1) {
ReaderInterceptor first = readers.remove(0);
ReaderInterceptorContext context = new ReaderInterceptorContextImpl(targetTypeClass,
parameterType,
parameterAnnotations,
is,
m,
readers);
return first.aroundReadFrom(context);
}
MessageBodyReader<?> provider = ((ReaderInterceptorMBR)readers.get(0)).getMBR();
@SuppressWarnings("rawtypes")
Class cls = targetTypeClass;
return provider.readFrom(
cls, parameterType, parameterAnnotations, mediaType,
new HttpHeadersImpl(m).getRequestHeaders(), is);
}
//CHECKSTYLE:OFF
public static void writeMessageBody(List<WriterInterceptor> writers,
Object entity,
Class<?> type, Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
Message message)
throws WebApplicationException, IOException {
OutputStream entityStream = message.getContent(OutputStream.class);
if ("org.apache.cxf.jaxrs.reactivestreams.server.StreamingAsyncSubscriber$StreamingResponseImpl".equals(
entity.getClass().getName())) {
//cache the OutputStream when it's reactive response
entityStream = new CacheAndWriteOutputStream(entityStream);
}
if (writers.size() > 1) {
WriterInterceptor first = writers.remove(0);
WriterInterceptorContext context = new WriterInterceptorContextImpl(entity,
type,
genericType,
annotations,
entityStream,
message,
writers);
first.aroundWriteTo(context);
} else {
MessageBodyWriter<Object> writer = ((WriterInterceptorMBW)writers.get(0)).getMBW();
if (type == byte[].class) {
long size = writer.getSize(entity, type, genericType, annotations, mediaType);
if (size != -1) {
httpHeaders.putSingle(HttpHeaders.CONTENT_LENGTH, Long.toString(size));
}
}
HttpUtils.convertHeaderValuesToString(httpHeaders, true);
writer.writeTo(entity, type, genericType, annotations, mediaType,
httpHeaders,
entityStream);
}
}
//CHECKSTYLE:ON
public static boolean matchConsumeTypes(MediaType requestContentType,
OperationResourceInfo ori) {
return doMimeTypesIntersect(ori.getConsumeTypes(), requestContentType);
}
public static boolean matchProduceTypes(MediaType acceptContentType,
OperationResourceInfo ori) {
return doMimeTypesIntersect(ori.getProduceTypes(), acceptContentType);
}
public static boolean matchMimeTypes(MediaType requestContentType,
MediaType acceptContentType,
OperationResourceInfo ori) {
return doMimeTypesIntersect(ori.getConsumeTypes(), requestContentType)
&& doMimeTypesIntersect(ori.getProduceTypes(), acceptContentType);
}
public static List<MediaType> parseMediaTypes(String types) {
List<MediaType> acceptValues = new ArrayList<>();
if (types != null) {
int x = 0;
int y = types.indexOf(',');
while (y > 0) {
acceptValues.add(toMediaType(types.substring(x, y).trim()));
x = y + 1;
y = types.indexOf(',', x);
}
String lastMediaType = types.substring(x).trim();
if (!lastMediaType.isEmpty()) {
acceptValues.add(toMediaType(lastMediaType));
}
} else {
acceptValues.add(ALL_TYPES);
}
return acceptValues;
}
public static boolean doMimeTypesIntersect(List<MediaType> mimeTypesA, MediaType mimeTypeB) {
return doMimeTypesIntersect(mimeTypesA, Collections.singletonList(mimeTypeB));
}
public static boolean doMimeTypesIntersect(List<MediaType> requiredMediaTypes, List<MediaType> userMediaTypes) {
final NonAccumulatingIntersector intersector = new NonAccumulatingIntersector();
intersectMimeTypes(requiredMediaTypes, userMediaTypes, intersector);
return intersector.doIntersect();
}
/**
* intersect two mime types
*
* @param requiredMediaTypes
* @param userMediaTypes
* @param addRequiredParamsIfPossible
* @return return a list of intersected mime types
*/
public static List<MediaType> intersectMimeTypes(List<MediaType> requiredMediaTypes,
List<MediaType> userMediaTypes,
boolean addRequiredParamsIfPossible) {
return intersectMimeTypes(requiredMediaTypes, userMediaTypes, addRequiredParamsIfPossible, false);
}
public static List<MediaType> intersectMimeTypes(List<MediaType> requiredMediaTypes,
List<MediaType> userMediaTypes,
boolean addRequiredParamsIfPossible,
boolean addDistanceParameter) {
final AccumulatingIntersector intersector = new AccumulatingIntersector(addRequiredParamsIfPossible,
addDistanceParameter);
intersectMimeTypes(requiredMediaTypes, userMediaTypes, intersector);
return new ArrayList<>(intersector.getSupportedMimeTypeList());
}
private static void intersectMimeTypes(List<MediaType> requiredMediaTypes, List<MediaType> userMediaTypes,
MimeTypesIntersector intersector) {
for (MediaType requiredType : requiredMediaTypes) {
for (MediaType userType : userMediaTypes) {
boolean isCompatible = isMediaTypeCompatible(requiredType, userType);
if (isCompatible) {
boolean parametersMatched = true;
for (Map.Entry<String, String> entry : userType.getParameters().entrySet()) {
String value = requiredType.getParameters().get(entry.getKey());
if (value != null && entry.getValue() != null && !(stripDoubleQuotesIfNeeded(value)
.equals(stripDoubleQuotesIfNeeded(entry.getValue())))) {
if (HTTP_CHARSET_PARAM.equals(entry.getKey()) && value.equalsIgnoreCase(entry.getValue())) {
continue;
}
parametersMatched = false;
break;
}
}
if (!parametersMatched) {
continue;
}
if (!intersector.intersect(requiredType, userType)) {
return;
}
}
}
}
}
private static String stripDoubleQuotesIfNeeded(String value) {
if (value != null && value.startsWith("\"")
&& value.endsWith("\"") && value.length() > 1) {
value = value.substring(1, value.length() - 1);
}
return value;
}
private static boolean isMediaTypeCompatible(MediaType requiredType, MediaType userType) {
boolean isCompatible = requiredType.isCompatible(userType);
if (!isCompatible && requiredType.getType().equalsIgnoreCase(userType.getType())) {
isCompatible = compareCompositeSubtypes(requiredType, userType,
PhaseInterceptorChain.getCurrentMessage());
}
return isCompatible;
}
static boolean compareCompositeSubtypes(String requiredType, String userType,
Message message) {
return compareCompositeSubtypes(toMediaType(requiredType), toMediaType(userType), message);
}
private static boolean compareCompositeSubtypes(MediaType requiredType, MediaType userType,
Message message) {
boolean isCompatible = false;
// check if we have composite subtypes
String subType1 = requiredType.getSubtype();
String subType2 = userType.getSubtype();
String subTypeAfterPlus1 = splitMediaSubType(subType1, true);
String subTypeAfterPlus2 = splitMediaSubType(subType2, true);
if (message != null && MessageUtils.getContextualBoolean(message, PARTIAL_HIERARCHICAL_MEDIA_SUBTYPE_CHECK)) {
if (subTypeAfterPlus1 != null || subTypeAfterPlus2 != null) {
boolean nullPossible = subTypeAfterPlus1 == null || subTypeAfterPlus2 == null;
isCompatible = subTypeAfterPlus1 == null && subTypeAfterPlus2.equals(subType1)
|| subTypeAfterPlus2 == null && subTypeAfterPlus1.equals(subType2);
if (!isCompatible && !nullPossible) {
isCompatible = subTypeAfterPlus1.equalsIgnoreCase(subTypeAfterPlus2)
&& (subType1.charAt(0) == '*' || subType2.charAt(0) == '*');
}
if (!isCompatible) {
String subTypeBeforePlus1 = splitMediaSubType(subType1, false);
String subTypeBeforePlus2 = splitMediaSubType(subType2, false);
nullPossible = subTypeBeforePlus1 == null || subTypeBeforePlus2 == null;
isCompatible = subTypeBeforePlus1 == null && subTypeBeforePlus2 != null
&& subTypeBeforePlus2.equals(subType1)
|| subTypeBeforePlus2 == null && subTypeBeforePlus1 != null
&& subTypeBeforePlus1.equals(subType2);
if (!isCompatible && !nullPossible) {
isCompatible = subTypeBeforePlus1.equalsIgnoreCase(subTypeBeforePlus2)
&& (subType1.charAt(subType1.length() - 1) == '*'
|| subType2.charAt(subType2.length() - 1) == '*');
}
}
}
} else {
if (subTypeAfterPlus1 != null && subTypeAfterPlus2 != null) {
isCompatible = subTypeAfterPlus1.equalsIgnoreCase(subTypeAfterPlus2)
&& (subType1.charAt(0) == '*' || subType2.charAt(0) == '*');
if (!isCompatible) {
String subTypeBeforePlus1 = splitMediaSubType(subType1, false);
String subTypeBeforePlus2 = splitMediaSubType(subType2, false);
isCompatible = subTypeBeforePlus1 != null && subTypeBeforePlus1.equalsIgnoreCase(subTypeBeforePlus2)
&& (subType1.charAt(subType1.length() - 1) == '*'
|| subType2.charAt(subType2.length() - 1) == '*');
}
}
}
return isCompatible;
}
private static String splitMediaSubType(String type, boolean after) {
int index = type.indexOf('+');
return index == -1 ? null : after ? type.substring(index + 1) : type.substring(0, index);
}
public static List<MediaType> intersectMimeTypes(List<MediaType> mimeTypesA,
MediaType mimeTypeB) {
return intersectMimeTypes(mimeTypesA,
Collections.singletonList(mimeTypeB), false);
}
public static List<MediaType> intersectMimeTypes(String mimeTypesA,
String mimeTypesB) {
return intersectMimeTypes(parseMediaTypes(mimeTypesA),
parseMediaTypes(mimeTypesB),
false);
}
public static List<MediaType> sortMediaTypes(String mediaTypes, String qs) {
return sortMediaTypes(JAXRSUtils.parseMediaTypes(mediaTypes), qs);
}
public static List<MediaType> sortMediaTypes(List<MediaType> types, final String qs) {
if (types.size() > 1) {
Collections.sort(types, new Comparator<MediaType>() {
public int compare(MediaType mt1, MediaType mt2) {
return JAXRSUtils.compareMediaTypes(mt1, mt2, qs);
}
});
}
return types;
}
public static <T extends Throwable> Response convertFaultToResponse(T ex, Message currentMessage) {
return ExceptionUtils.convertFaultToResponse(ex, currentMessage);
}
public static void setMessageContentType(Message message, Response response) {
if (response != null) {
Object ct = response.getMetadata().getFirst(HttpHeaders.CONTENT_TYPE);
if (ct != null) {
Exchange ex = message.getExchange();
if (ex.getInMessage() == message) {
ex.put(Message.CONTENT_TYPE, ct.toString());
} else {
message.put(Message.CONTENT_TYPE, ct.toString());
}
}
}
}
public static QName getClassQName(Class<?> type) {
String nsURI = PackageUtils.getNamespace(PackageUtils.getPackageName(type));
if (nsURI.endsWith("/")) {
nsURI = nsURI.substring(0, nsURI.length() - 1);
}
return new QName(nsURI, type.getSimpleName(), "ns1");
}
public static QName convertStringToQName(String name) {
return DOMUtils.convertStringToQName(name, "");
}
public static boolean runContainerRequestFilters(ServerProviderFactory pf,
Message m,
boolean preMatch,
Set<String> names) throws IOException {
List<ProviderInfo<ContainerRequestFilter>> containerFilters = preMatch
? pf.getPreMatchContainerRequestFilters() : pf.getPostMatchContainerRequestFilters(names);
if (!containerFilters.isEmpty()) {
ContainerRequestContext context = new ContainerRequestContextImpl(m, preMatch, false);
for (ProviderInfo<ContainerRequestFilter> filter : containerFilters) {
InjectionUtils.injectContexts(filter.getProvider(), filter, m);
filter.getProvider().filter(context);
Response response = m.getExchange().get(Response.class);
if (response != null) {
setMessageContentType(m, response);
return true;
}
}
}
return false;
}
public static void runContainerResponseFilters(ServerProviderFactory pf,
ResponseImpl r,
Message m,
OperationResourceInfo ori,
Method invoked) throws IOException, Throwable {
List<ProviderInfo<ContainerResponseFilter>> containerFilters =
pf.getContainerResponseFilters(ori == null ? null : ori.getNameBindings());
if (!containerFilters.isEmpty()) {
ContainerRequestContext requestContext =
new ContainerRequestContextImpl(m.getExchange().getInMessage(),
false,
true);
ContainerResponseContext responseContext =
new ContainerResponseContextImpl(r, m,
ori == null ? null : ori.getClassResourceInfo().getServiceClass(), invoked);
for (ProviderInfo<ContainerResponseFilter> filter : containerFilters) {
InjectionUtils.injectContexts(filter.getProvider(), filter, m);
filter.getProvider().filter(requestContext, responseContext);
}
}
}
public static String mediaTypeToString(MediaType mt, String... ignoreParams) {
List<String> list = ignoreParams == null || ignoreParams.length == 0 ? null
: Arrays.asList(ignoreParams);
return MediaTypeHeaderProvider.typeToString(mt, list);
}
public static MediaType toMediaType(String value) {
if (value == null) {
return ALL_TYPES;
}
return MediaTypeHeaderProvider.valueOf(value);
}
public static Response toResponse(int status) {
return toResponseBuilder(status).build();
}
public static Response toResponse(Response.Status status) {
return toResponse(status.getStatusCode());
}
public static ResponseBuilder toResponseBuilder(int status) {
return new ResponseBuilderImpl().status(status);
}
public static ResponseBuilder toResponseBuilder(Response.Status status) {
return toResponseBuilder(status.getStatusCode());
}
public static ResponseBuilder fromResponse(Response response) {
return fromResponse(response, true);
}
public static ResponseBuilder fromResponse(Response response, boolean copyEntity) {
ResponseBuilder rb = toResponseBuilder(response.getStatus());
if (copyEntity) {
rb.entity(response.getEntity());
}
for (Map.Entry<String, List<Object>> entry : response.getMetadata().entrySet()) {
List<Object> values = entry.getValue();
for (Object value : values) {
rb.header(entry.getKey(), value);
}
}
return rb;
}
public static Response copyResponseIfNeeded(Response response) {
if (!(response instanceof ResponseImpl)) {
Response r = fromResponse(response).build();
Field[] declaredFields = ReflectionUtil.getDeclaredFields(response.getClass());
for (Field f : declaredFields) {
Class<?> declClass = f.getType();
if (declClass == Annotation[].class) {
try {
Annotation[] fieldAnnotations =
ReflectionUtil.accessDeclaredField(f, response, Annotation[].class);
((ResponseImpl)r).setEntityAnnotations(fieldAnnotations);
} catch (Throwable ex) {
LOG.warning("Custom annotations if any can not be copied");
}
break;
}
}
return r;
}
return response;
}
public static Message getCurrentMessage() {
return PhaseInterceptorChain.getCurrentMessage();
}
public static ClassResourceInfo getRootResource(Message m) {
return (ClassResourceInfo)m.getExchange().get(JAXRSUtils.ROOT_RESOURCE_CLASS);
}
public static void pushOntoStack(OperationResourceInfo ori,
MultivaluedMap<String, String> params,
Message msg) {
OperationResourceInfoStack stack = msg.get(OperationResourceInfoStack.class);
if (stack == null) {
stack = new OperationResourceInfoStack();
msg.put(OperationResourceInfoStack.class, stack);
}
final List<String> values;
if (params.size() <= 1) {
values = Collections.emptyList();
} else {
values = new ArrayList<>(params.size() - 1);
addTemplateVarValues(values, params, ori.getClassResourceInfo().getURITemplate());
addTemplateVarValues(values, params, ori.getURITemplate());
}
Class<?> realClass = ori.getClassResourceInfo().getServiceClass();
stack.push(new MethodInvocationInfo(ori, realClass, values));
}
private static void addTemplateVarValues(List<String> values,
MultivaluedMap<String, String> params,
URITemplate template) {
if (template != null) {
for (String var : template.getVariables()) {
List<String> paramValues = params.get(var);
if (paramValues != null) {
values.addAll(paramValues);
}
}
}
}
public static String logMessageHandlerProblem(String name, Class<?> cls, MediaType ct) {
org.apache.cxf.common.i18n.Message errorMsg =
new org.apache.cxf.common.i18n.Message(name, BUNDLE, cls.getName(), mediaTypeToString(ct));
String errorMessage = errorMsg.toString();
LOG.severe(errorMessage);
return errorMessage;
}
/**
* Get path URI template, combining base path, class & method & subresource templates
* @param message message instance
* @param cri class resource info
* @param ori operation resource info
* @param subOri operation subresource info
* @return the URI template for the method in question
*/
public static String getUriTemplate(Message message, ClassResourceInfo cri, OperationResourceInfo ori,
OperationResourceInfo subOri) {
final String template = getUriTemplate(message, cri, ori);
final String methodPathTemplate = getUriTemplate(subOri);
return combineUriTemplates(template, methodPathTemplate);
}
/**
* Get path URI template, combining base path, class & method templates
* @param message message instance
* @param cri class resource info
* @param ori operation resource info
* @return the URI template for the method in question
*/
public static String getUriTemplate(Message message, ClassResourceInfo cri, OperationResourceInfo ori) {
final String basePath = (String)message.get(Message.BASE_PATH);
final String classPathTemplate = getUriTemplate(cri);
final String methodPathTemplate = getUriTemplate(ori);
// The application path (@ApplicationPath) is incorporated into Message.BASE_PATH,
// since it is part of the address.
String template = basePath;
if (StringUtils.isEmpty(template)) {
template = "/";
} else if (!template.startsWith("/")) {
template = "/" + template;
}
template = combineUriTemplates(template, classPathTemplate);
return combineUriTemplates(template, methodPathTemplate);
}
/**
* Gets the URI template of the operation from its resource info
* to assemble final URI template
* @param ori operation resource info
* @return URI template
*/
private static String getUriTemplate(OperationResourceInfo ori) {
final URITemplate template = ori.getURITemplate();
if (template != null) {
return template.getValue();
} else {
return null;
}
}
/**
* Goes over sub-resource class resource templates (through parent chain) if necessary
* to assemble final URI template
* @param cri root or subresource class resource info
* @return URI template chain
*/
private static String getUriTemplate(ClassResourceInfo cri) {
final URITemplate template = cri.getURITemplate();
if (template != null) {
return template.getValue();
} else if (cri.getParent() != null) { /* probably subresource */
return getUriTemplate(cri.getParent());
} else {
return null; /* should not happen */
}
}
/**
* Combines two URI templates together
* @param parent parent URI template
* @param child child URI template
* @return the URI template combined from the parent and child
*/
private static String combineUriTemplates(final String parent, final String child) {
if (StringUtils.isEmpty(child)) {
return parent;
}
// The way URI templates are normalized in org.apache.cxf.jaxrs.model.URITemplate:
// - empty or null become "/"
// - "/" is added at the start if not present
if ("/".equals(parent)) {
return child;
} else if ("/".equals(child)) {
return parent;
} else if (parent.endsWith("/")) {
// Remove only last slash
return parent.replaceAll("/$", "") + child;
} else {
return parent + child;
}
}
// copy the input stream so that it is not inadvertently closed
private static InputStream copyAndGetEntityStream(Message m) {
LoadingByteArrayOutputStream baos = new LoadingByteArrayOutputStream();
try (InputStream in = m.getContent(InputStream.class)) {
IOUtils.copy(in, baos);
} catch (IOException e) {
throw ExceptionUtils.toInternalServerErrorException(e, null);
}
m.setContent(InputStream.class, baos.createInputStream());
return baos.createInputStream();
}
}