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-bundle-minimal Show documentation
Show all versions of cxf-bundle-minimal Show documentation
Apache CXF Minimal Bundle Jar
/**
* 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.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.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
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.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Application;
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.UriInfo;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Providers;
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.StringUtils;
import org.apache.cxf.helpers.XMLUtils;
import org.apache.cxf.jaxrs.ext.ContextProvider;
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.HttpHeadersImpl;
import org.apache.cxf.jaxrs.impl.HttpServletResponseFilter;
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.RequestImpl;
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.model.ClassResourceInfo;
import org.apache.cxf.jaxrs.model.ClassResourceInfoComparator;
import org.apache.cxf.jaxrs.model.OperationResourceInfo;
import org.apache.cxf.jaxrs.model.OperationResourceInfoComparator;
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.utils.multipart.AttachmentUtils;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageUtils;
import org.apache.cxf.transport.http.AbstractHTTPDestination;
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 DOC_LOCATION = "wadl.location";
public static final String DEFAULT_PROVIDERS_FOR_SIMPLE_TYPES = "defaultProviders.for.simpleTypes";
private static final Logger LOG = LogUtils.getL7dLogger(JAXRSUtils.class);
private static final ResourceBundle BUNDLE = BundleUtils.getBundle(JAXRSUtils.class);
private static final String PROPAGATE_EXCEPTION = "org.apache.cxf.propagate.exception";
private static final String REPORT_FAULT_MESSAGE_PROPERTY = "org.apache.cxf.jaxrs.report-fault-message";
private JAXRSUtils() {
}
public static List getPathSegments(String thePath, boolean decode) {
return getPathSegments(thePath, decode, true);
}
public static List getPathSegments(String thePath, boolean decode,
boolean ignoreLastSlash) {
String[] segments = StringUtils.split(thePath, "/");
List theList = new ArrayList();
for (String path : segments) {
if (!StringUtils.isEmpty(path)) {
theList.add(new PathSegmentImpl(path, decode));
}
}
int len = thePath.length();
if (len > 0 && thePath.charAt(len - 1) == '/') {
String value = ignoreLastSlash ? "" : "/";
theList.add(new PathSegmentImpl(value, false));
}
return theList;
}
@SuppressWarnings("unchecked")
private static String[] getUserMediaTypes(Object provider, String methodName) {
String[] values = null;
if (AbstractConfigurableProvider.class.isAssignableFrom(provider.getClass())) {
try {
Method m = provider.getClass().getMethod(methodName, new Class[]{});
List types = (List)m.invoke(provider, new Object[]{});
if (types != null) {
values = types.size() > 0 ? types.toArray(new String[]{})
: new String[]{"*/*"};
}
} catch (Exception ex) {
// ignore
}
}
return values;
}
public static List getProviderConsumeTypes(MessageBodyReader> provider) {
String[] values = getUserMediaTypes(provider, "getConsumeMediaTypes");
if (values == null) {
return getConsumeTypes(provider.getClass().getAnnotation(Consumes.class));
} else {
return JAXRSUtils.getMediaTypes(values);
}
}
public static List getProviderProduceTypes(MessageBodyWriter> provider) {
String[] values = getUserMediaTypes(provider, "getProduceMediaTypes");
if (values == null) {
return getProduceTypes(provider.getClass().getAnnotation(Produces.class));
} else {
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.add(toMediaType(values[i]));
}
return supportedMimeTypes;
}
@SuppressWarnings("unchecked")
public static void injectParameters(OperationResourceInfo ori,
Object requestObject,
Message message) {
ClassResourceInfo cri = ori.getClassResourceInfo();
if (cri.isSingleton()
&& (!cri.getParameterMethods().isEmpty() || !cri.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 : cri.getParameterMethods()) {
Parameter p = ResourceUtils.getParameter(0, m.getAnnotations(),
m.getParameterTypes()[0]);
Object 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 : cri.getParameterFields()) {
Parameter p = ResourceUtils.getParameter(0, f.getAnnotations(),
f.getType());
Object o = createHttpParameterValue(p,
f.getType(),
f.getGenericType(),
f.getAnnotations(),
message,
values,
ori);
InjectionUtils.injectFieldValue(f, requestObject, o);
}
}
public static ClassResourceInfo selectResourceClass(List resources,
String path,
MultivaluedMap values,
Message message) {
boolean isFineLevelLoggable = LOG.isLoggable(Level.FINE);
if (isFineLevelLoggable) {
LOG.fine(new org.apache.cxf.common.i18n.Message("START_CRI_MATCH",
BUNDLE,
path).toString());
}
if (resources.size() == 1) {
return resources.get(0).getURITemplate().match(path, values)
? resources.get(0) : 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);
if (isFineLevelLoggable) {
LOG.fine(new org.apache.cxf.common.i18n.Message("CRI_SELECTED_POSSIBLY",
BUNDLE,
cri.getServiceClass().getName(),
path,
cri.getURITemplate().getValue()).toString());
}
} else if (isFineLevelLoggable) {
LOG.fine(new org.apache.cxf.common.i18n.Message("CRI_NO_MATCH",
BUNDLE,
path,
cri.getServiceClass().getName()).toString());
}
}
if (!candidateList.isEmpty()) {
Map.Entry> firstEntry =
candidateList.entrySet().iterator().next();
values.putAll(firstEntry.getValue());
ClassResourceInfo cri = firstEntry.getKey();
if (isFineLevelLoggable) {
LOG.fine(new org.apache.cxf.common.i18n.Message("CRI_SELECTED",
BUNDLE,
cri.getServiceClass().getName(),
path, cri.getURITemplate().getValue()).toString());
}
return cri;
}
return null;
}
public static OperationResourceInfo findTargetMethod(ClassResourceInfo resource,
Message message,
String httpMethod,
MultivaluedMap values,
String requestContentType,
List acceptContentTypes,
boolean logNow) {
boolean isFineLevelLoggable = LOG.isLoggable(Level.FINE);
if (isFineLevelLoggable) {
org.apache.cxf.common.i18n.Message msg =
new org.apache.cxf.common.i18n.Message("START_OPER_MATCH",
BUNDLE,
resource.getServiceClass().getName());
LOG.fine(msg.toString());
}
String path = values.getFirst(URITemplate.FINAL_MATCH_GROUP);
if (path == null) {
path = "/";
}
SortedMap> candidateList =
new TreeMap>(
new OperationResourceInfoComparator(message, httpMethod));
MediaType requestType;
try {
requestType = requestContentType == null
? ALL_TYPES : toMediaType(requestContentType);
} catch (IllegalArgumentException ex) {
throw new WebApplicationException(ex, 415);
}
int pathMatched = 0;
int methodMatched = 0;
int consumeMatched = 0;
int produceMatched = 0;
boolean subresourcesOnly = true;
for (MediaType acceptType : acceptContentTypes) {
for (OperationResourceInfo ori : resource.getMethodDispatcher().getOperationResourceInfos()) {
URITemplate uriTemplate = ori.getURITemplate();
MultivaluedMap map = new MetadataMap(values);
if (uriTemplate != null && uriTemplate.match(path, map)) {
boolean added = false;
if (ori.isSubResourceLocator()) {
candidateList.put(ori, map);
added = true;
} else {
String finalGroup = map.getFirst(URITemplate.FINAL_MATCH_GROUP);
if (finalGroup == null || StringUtils.isEmpty(finalGroup)
|| finalGroup.equals("/")) {
pathMatched++;
boolean mMatched = matchHttpMethod(ori.getHttpMethod(), httpMethod);
boolean cMatched = matchConsumeTypes(requestType, ori);
boolean pMatched = matchProduceTypes(acceptType, ori);
if (mMatched && cMatched && pMatched) {
subresourcesOnly = false;
candidateList.put(ori, map);
added = true;
} else {
methodMatched = mMatched ? methodMatched + 1 : methodMatched;
produceMatched = pMatched ? produceMatched + 1 : produceMatched;
consumeMatched = cMatched ? consumeMatched + 1 : consumeMatched;
logNoMatchMessage(ori, path, httpMethod, requestType, acceptContentTypes);
}
} else {
logNoMatchMessage(ori, path, httpMethod, requestType, acceptContentTypes);
}
}
if (added && isFineLevelLoggable) {
LOG.fine(new org.apache.cxf.common.i18n.Message("OPER_SELECTED_POSSIBLY",
BUNDLE,
ori.getMethodToInvoke().getName()).toString());
}
} else {
logNoMatchMessage(ori, path, httpMethod, requestType, acceptContentTypes);
}
}
if (!candidateList.isEmpty() && !subresourcesOnly) {
break;
}
}
if (!candidateList.isEmpty()) {
Map.Entry> firstEntry =
candidateList.entrySet().iterator().next();
values.clear();
values.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, resource.getServiceClass().getName(),
ori.getMethodToInvoke().getName()).toString());
}
if (isFineLevelLoggable) {
LOG.fine(new org.apache.cxf.common.i18n.Message("OPER_SELECTED",
BUNDLE, ori.getMethodToInvoke().getName(),
resource.getServiceClass().getName()).toString());
}
return ori;
}
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 <= produceMatched) {
status = 415;
} else {
status = 406;
}
String name = resource.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),
path,
httpMethod,
mediaTypeToString(requestType),
convertTypesToString(acceptContentTypes));
if (!"OPTIONS".equalsIgnoreCase(httpMethod) && logNow) {
LOG.warning(errorMsg.toString());
}
Response response =
createResponse(resource, message, errorMsg.toString(), status, methodMatched == 0);
throw new WebApplicationException(response);
}
public static boolean noResourceMethodForOptions(Response exResponse, String httpMethod) {
return exResponse != null && exResponse.getStatus() == 405
&& "OPTIONS".equalsIgnoreCase(httpMethod);
}
private static void logNoMatchMessage(OperationResourceInfo ori,
String path, String httpMethod, MediaType requestType, List acceptContentTypes) {
if (!LOG.isLoggable(Level.FINE)) {
return;
}
org.apache.cxf.common.i18n.Message errorMsg =
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()));
LOG.fine(errorMsg.toString());
}
public static Response createResponse(ClassResourceInfo cri, Message msg,
String responseMessage, int status, boolean addAllow) {
ResponseBuilder rb = Response.status(status);
if (addAllow) {
Set allowedMethods = 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.isTrue(msg.getContextualProperty(REPORT_FAULT_MESSAGE_PROPERTY))) {
rb.type(MediaType.TEXT_PLAIN_TYPE).entity(responseMessage);
}
return rb.build();
}
private static boolean matchHttpMethod(String expectedMethod, String httpMethod) {
if (expectedMethod.equalsIgnoreCase(httpMethod)
|| headMethodPossible(expectedMethod, httpMethod)) {
return true;
}
return false;
}
public static boolean headMethodPossible(String expectedMethod, String httpMethod) {
return "HEAD".equalsIgnoreCase(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 cm == null ? Collections.singletonList(ALL_TYPES)
: getMediaTypes(cm.value());
}
public static List getProduceTypes(Produces pm) {
return pm == null ? Collections.singletonList(ALL_TYPES)
: getMediaTypes(pm.value());
}
public static int compareSortedMediaTypes(List mts1, List mts2) {
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));
if (result != 0) {
return result;
}
}
return size1 == size2 ? 0 : size1 < size2 ? -1 : 1;
}
public static int compareMediaTypes(MediaType mt1, MediaType mt2) {
if (mt1.isWildcardType() && !mt2.isWildcardType()) {
return 1;
}
if (!mt1.isWildcardType() && mt2.isWildcardType()) {
return -1;
}
if (mt1.isWildcardSubtype() && !mt2.isWildcardSubtype()) {
return 1;
}
if (!mt1.isWildcardSubtype() && mt2.isWildcardSubtype()) {
return -1;
}
return compareMediaTypesQualityFactors(mt1, mt2);
}
public static int compareMediaTypesQualityFactors(MediaType mt1, MediaType mt2) {
float q1 = getMediaTypeQualityFactor(mt1.getParameters().get("q"));
float q2 = getMediaTypeQualityFactor(mt2.getParameters().get("q"));
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