org.apache.cxf.jaxrs.utils.JAXRSUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cxf-rt-frontend-jaxrs Show documentation
Show all versions of cxf-rt-frontend-jaxrs Show documentation
Apache CXF Runtime JAX-RS Frontend
/**
* 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.Closeable;
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.Optional;
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.xml.namespace.QName;
import javax.xml.transform.Source;
import jakarta.ws.rs.ClientErrorException;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.container.AsyncResponse;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.container.ResourceContext;
import jakarta.ws.rs.container.ResourceInfo;
import jakarta.ws.rs.core.Application;
import jakarta.ws.rs.core.Configuration;
import jakarta.ws.rs.core.Cookie;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.PathSegment;
import jakarta.ws.rs.core.Request;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.ResponseBuilder;
import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.core.StreamingOutput;
import jakarta.ws.rs.core.UriInfo;
import jakarta.ws.rs.ext.ContextResolver;
import jakarta.ws.rs.ext.MessageBodyReader;
import jakarta.ws.rs.ext.MessageBodyWriter;
import jakarta.ws.rs.ext.Providers;
import jakarta.ws.rs.ext.ReaderInterceptor;
import jakarta.ws.rs.ext.ReaderInterceptorContext;
import jakarta.ws.rs.ext.WriterInterceptor;
import jakarta.ws.rs.ext.WriterInterceptorContext;
import org.apache.cxf.common.classloader.ClassLoaderUtils;
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 = "jakarta.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> STREAMING_OUT_TYPES = new HashSet<>(
Arrays.asList(InputStream.class, Reader.class, StreamingOutput.class));
private static final Set STREAMING_LIKE_OUT_TYPES = new HashSet<>(
Arrays.asList(
"org.apache.cxf.jaxrs.ext.xml.XMLSource",
"org.apache.cxf.jaxrs.ext.multipart.InputStreamDataSource",
"org.apache.cxf.jaxrs.ext.multipart.MultipartBody",
"org.apache.cxf.jaxrs.ext.multipart.Attachment"
));
private static final Set REACTIVE_OUT_TYPES = new HashSet<>(
Arrays.asList(
// Reactive Streams
"org.reactivestreams.Publisher",
// Project Reactor
"reactor.core.publisher.Mono",
"reactor.core.publisher.Flux",
// RxJava
"rx.Observable",
// RxJava2
"io.reactivex.Flowable",
"io.reactivex.Maybe",
"io.reactivex.Observable",
"io.reactivex.Single",
// RxJava3
"io.reactivex.rxjava3.core.Flowable",
"io.reactivex.rxjava3.core.Maybe",
"io.reactivex.rxjava3.core.Observable",
"io.reactivex.rxjava3.core.Single",
// JDK
"java.util.concurrent.CompletableFuture",
"java.util.concurrent.CompletionStage"
));
private static final LazyLoadedClass DATA_SOURCE_CLASS = new LazyLoadedClass("jakarta.activation.DataSource");
// Class to lazily call the ClassLoaderUtil.loadClass, but do it once
// and cache the result. Then use the class to create instances as needed.
// This avoids calling loadClass every time as calling loadClass is super expensive,
// particularly if the class cannot be found and particularly in OSGi where the
// search is very complex. This would record that the class is not found and prevent
// future searches.
private static class LazyLoadedClass {
private final String className;
private volatile boolean initialized;
private Class> cls;
LazyLoadedClass(String cn) {
className = cn;
}
synchronized Optional> load() {
if (!initialized) {
try {
cls = ClassLoaderUtils.loadClass(className, ProviderFactory.class);
} catch (final Throwable ex) {
LOG.fine(className + " not available, skipping");
}
initialized = true;
}
return Optional.ofNullable(cls);
}
boolean isAssignableFrom(Class> clz) {
return load().map(c -> c.isAssignableFrom(clz)).orElse(false);
}
}
private JAXRSUtils() {
}
/**
* Consider additional types as stream-like and returns if the class and corresponding type
* refer to one of those.
* @param cls class to check
* @param type type to check
* @return "true" is the class and corresponding type could be considered streaming-like,
* "false" otherwise.
*/
public static boolean isStreamingLikeOutType(Class> cls, Type type) {
if (cls != null && (isStreamingOutType(cls)
|| STREAMING_LIKE_OUT_TYPES.contains(cls.getName())
|| REACTIVE_OUT_TYPES.contains(cls.getName()))) {
return true;
}
if (type instanceof ParameterizedType) {
final ParameterizedType parameterizedType = (ParameterizedType)type;
for (Type arg: parameterizedType.getActualTypeArguments()) {
if (isStreamingLikeOutType(null, arg)) {
return true;
}
}
}
if (type instanceof Class) {
final Class> typeCls = (Class>)type;
return isStreamingOutType(typeCls) || STREAMING_LIKE_OUT_TYPES.contains(typeCls.getName());
}
return false;
}
public static boolean isStreamingOutType(Class> type) {
return STREAMING_OUT_TYPES.contains(type)
|| Closeable.class.isAssignableFrom(type)
|| Source.class.isAssignableFrom(type)
|| DATA_SOURCE_CLASS.isAssignableFrom(type);
}
public static List getPathSegments(String thePath, boolean decode) {
return getPathSegments(thePath, decode, true);
}
/**
* Parses path segments taking into account the URI templates and template regexes. Per RFC-3986,
* "A path consists of a sequence of path segments separated by a slash ("/") character.", however
* it is possible to include slash ("/") inside template regex, for example "/my/path/{a:b/c}", see
* please {@link URITemplate}. In this case, the whole template definition is extracted as a path
* segment, without breaking it.
* @param thePath path
* @param decode should the path segments be decoded or not
* @param ignoreLastSlash should the last slash be ignored or not
* @return
*/
public static List getPathSegments(String thePath, boolean decode,
boolean ignoreLastSlash) {
final List segments = new ArrayList<>();
int templateDepth = 0;
int start = 0;
for (int i = 0; i < thePath.length(); ++i) {
if (thePath.charAt(i) == '/') {
// The '/' is in template (possibly, with arbitrary regex) definition
if (templateDepth != 0) {
continue;
} else if (start != i) {
final String segment = thePath.substring(start, i);
segments.add(new PathSegmentImpl(segment, decode));
}
// advance the positions, empty path segments
start = i + 1;
} else if (thePath.charAt(i) == '{') {
++templateDepth;
} else if (thePath.charAt(i) == '}') {
--templateDepth; // could go negative, since the template could be unbalanced
}
}
// the URI has unbalanced curly braces, backtrack to the last seen position of the path
// segment separator and just split segments as-is from there
if (templateDepth != 0) {
segments.addAll(
Arrays
.stream(thePath.substring(start).split("/"))
.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 ? "" : "/";
segments.add(new PathSegmentImpl(value, false));
}
} else {
// the last symbol is slash
if (start == thePath.length() && start > 0 && thePath.charAt(start - 1) == '/') {
String value = ignoreLastSlash ? "" : "/";
segments.add(new PathSegmentImpl(value, false));
} else if (!thePath.isEmpty()) {
final String segment = thePath.substring(start);
segments.add(new PathSegmentImpl(segment, decode));
}
}
return segments;
}
private static String[] getUserMediaTypes(Object provider, boolean consumes) {
String[] values = null;
if (AbstractConfigurableProvider.class.isAssignableFrom(provider.getClass())) {
final List 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 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 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 getMediaTypes(String[] values) {
List 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 values =
(MultivaluedMap)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> selectResourceClass(
List 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 values = new MetadataMap<>();
return resources.get(0).getURITemplate().match(path, values)
? Collections.singletonMap(resources.get(0), values) : null;
}
SortedMap> candidateList =
new TreeMap>(
new ClassResourceInfoComparator(message));
for (ClassResourceInfo cri : resources) {
MultivaluedMap 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> cris =
new LinkedHashMap<>(candidateList.size());
ClassResourceInfo firstCri = null;
for (Map.Entry> 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> matchedResources,
Message message,
String httpMethod,
MultivaluedMap matchedValues,
String requestContentType,
List acceptContentTypes) {
return findTargetMethod(matchedResources, message, httpMethod, matchedValues,
requestContentType, acceptContentTypes, true, true);
}
//CHECKSTYLE:OFF
public static OperationResourceInfo findTargetMethod(
Map> matchedResources,
Message message,
String httpMethod,
MultivaluedMap matchedValues,
String requestContentType,
List 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> candidateList =
new TreeMap>(
new OperationResourceInfoComparator(message, httpMethod,
getMethod, requestType, acceptContentTypes));
int pathMatched = 0;
int methodMatched = 0;
int consumeMatched = 0;
List finalPathSubresources = null;
for (Map.Entry> rEntry : matchedResources.entrySet()) {
ClassResourceInfo resource = rEntry.getKey();
MultivaluedMap 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 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));
}
}
// We may get several matching candidates with different HTTP methods which match subresources
// and resources. Before excluding subresources, let us make sure we have at least one matching
// HTTP method candidate.
boolean isOptions = HttpMethod.OPTIONS.equalsIgnoreCase(httpMethod);
if (finalPathSubresources != null && (methodMatched > 0 || isOptions)
&& !MessageUtils.getContextualBoolean(message, KEEP_SUBRESOURCE_CANDIDATES, false)) {
for (OperationResourceInfo key : finalPathSubresources) {
candidateList.remove(key);
}
}
if (!candidateList.isEmpty()) {
Map.Entry> 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> 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 intersectSortMediaTypes(List acceptTypes,
List producesTypes,
final boolean checkDistance) {
List all = intersectMimeTypes(acceptTypes, producesTypes, true, checkDistance);
if (all.size() > 1) {
all.sort(new Comparator() {
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 values) {
String path = values.getFirst(URITemplate.FINAL_MATCH_GROUP);
return path == null ? "/" : path;
}
public static List 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 matchMessageLogSupplier(OperationResourceInfo ori,
String path, String httpMethod, MediaType requestType, List 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 cris, Message msg,
String responseMessage, int status, boolean addAllow) {
ResponseBuilder rb = toResponseBuilder(status);
if (addAllow) {
Set 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 types) {
StringBuilder sb = new StringBuilder();
for (MediaType type : types) {
sb.append(mediaTypeToString(type)).append(',');
}
return sb.toString();
}
public static List getConsumeTypes(Consumes cm) {
return getConsumeTypes(cm, Collections.singletonList(ALL_TYPES));
}
public static List getConsumeTypes(Consumes cm, List defaultTypes) {
return cm == null ? defaultTypes
: getMediaTypes(cm.value());
}
public static List getProduceTypes(Produces pm) {
return getProduceTypes(pm, Collections.singletonList(ALL_TYPES));
}
public static List getProduceTypes(Produces pm, List defaultTypes) {
return pm == null ? defaultTypes
: getMediaTypes(pm.value());
}
public static int compareSortedConsumesMediaTypes(List mts1, List mts2, MediaType ct) {
List actualMts1 = getCompatibleMediaTypes(mts1, ct);
List actualMts2 = getCompatibleMediaTypes(mts2, ct);
return compareSortedMediaTypes(actualMts1, actualMts2, null);
}
public static int compareSortedAcceptMediaTypes(List mts1, List mts2,
List acceptTypes) {
List actualMts1 = intersectSortMediaTypes(mts1, acceptTypes, true);
List 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 getCompatibleMediaTypes(List mts, MediaType ct) {
List 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 mts1, List 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
© 2015 - 2025 Weber Informatics LLC | Privacy Policy