blob: 209d7c158b45ac3a3c0f05b9cbee7e454b7b1b8a [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.client;
import java.io.Closeable;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.net.URI;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.function.Predicate;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.ws.rs.BeanParam;
import javax.ws.rs.CookieParam;
import javax.ws.rs.FormParam;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.MatrixParam;
import javax.ws.rs.PathParam;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.client.InvocationCallback;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import org.apache.cxf.Bus;
import org.apache.cxf.BusFactory;
import org.apache.cxf.common.classloader.ClassLoaderUtils;
import org.apache.cxf.common.classloader.ClassLoaderUtils.ClassLoaderHolder;
import org.apache.cxf.common.i18n.BundleUtils;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.common.util.PrimitiveUtils;
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.endpoint.Endpoint;
import org.apache.cxf.helpers.CastUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.interceptor.InterceptorProvider;
import org.apache.cxf.jaxrs.ext.multipart.Attachment;
import org.apache.cxf.jaxrs.ext.multipart.Multipart;
import org.apache.cxf.jaxrs.impl.MetadataMap;
import org.apache.cxf.jaxrs.impl.ResponseImpl;
import org.apache.cxf.jaxrs.model.ClassResourceInfo;
import org.apache.cxf.jaxrs.model.OperationResourceInfo;
import org.apache.cxf.jaxrs.model.Parameter;
import org.apache.cxf.jaxrs.model.ParameterType;
import org.apache.cxf.jaxrs.utils.AnnotationUtils;
import org.apache.cxf.jaxrs.utils.FormUtils;
import org.apache.cxf.jaxrs.utils.InjectionUtils;
import org.apache.cxf.jaxrs.utils.JAXRSUtils;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.Message;
/**
* Proxy-based client implementation
*
*/
public class ClientProxyImpl extends AbstractClient implements
InvocationHandlerAware, InvocationHandler, Closeable {
protected static final Logger LOG = LogUtils.getL7dLogger(ClientProxyImpl.class);
protected static final ResourceBundle BUNDLE = BundleUtils.getBundle(ClientProxyImpl.class);
protected static final String SLASH = "/";
protected static final String BUFFER_PROXY_RESPONSE = "buffer.proxy.response";
protected static final String PROXY_METHOD_PARAM_BODY_INDEX = "proxy.method.parameter.body.index";
protected ClassResourceInfo cri;
protected ClassLoader proxyLoader;
protected boolean inheritHeaders;
protected boolean isRoot;
protected Map<String, Object> valuesMap = Collections.emptyMap();
protected BodyWriter bodyWriter = new BodyWriter();
protected Client proxy;
public ClientProxyImpl(URI baseURI,
ClassLoader loader,
ClassResourceInfo cri,
boolean isRoot,
boolean inheritHeaders,
Object... varValues) {
this(baseURI, loader, cri, isRoot, inheritHeaders, Collections.emptyMap(), varValues);
}
public ClientProxyImpl(URI baseURI,
ClassLoader loader,
ClassResourceInfo cri,
boolean isRoot,
boolean inheritHeaders,
Map<String, Object> properties,
Object... varValues) {
this(new LocalClientState(baseURI, properties), loader, cri, isRoot, inheritHeaders, varValues);
}
public ClientProxyImpl(ClientState initialState,
ClassLoader loader,
ClassResourceInfo cri,
boolean isRoot,
boolean inheritHeaders,
Object... varValues) {
super(initialState);
this.proxyLoader = loader;
this.cri = cri;
this.isRoot = isRoot;
this.inheritHeaders = inheritHeaders;
initValuesMap(varValues);
cfg.getInInterceptors().add(new ClientAsyncResponseInterceptor());
}
void setProxyClient(Client client) {
this.proxy = client;
}
private void initValuesMap(Object... varValues) {
if (isRoot) {
List<String> vars = cri.getURITemplate().getVariables();
valuesMap = new LinkedHashMap<>();
for (int i = 0; i < vars.size(); i++) {
if (varValues.length > 0) {
if (i < varValues.length) {
valuesMap.put(vars.get(i), varValues[i]);
} else {
org.apache.cxf.common.i18n.Message msg = new org.apache.cxf.common.i18n.Message(
"ROOT_VARS_MISMATCH", BUNDLE, vars.size(), varValues.length);
LOG.info(msg.toString());
break;
}
} else {
valuesMap.put(vars.get(i), "");
}
}
}
}
private static class WrappedException extends Exception {
private static final long serialVersionUID = 1183890106889852917L;
final Throwable wrapped;
WrappedException(Throwable wrapped) {
this.wrapped = wrapped;
}
Throwable getWrapped() {
return wrapped;
}
}
private static Object invokeDefaultMethod(Class<?> declaringClass, Object o, Method m, Object[] params)
throws Throwable {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
@Override
public Object run() throws Exception {
try {
final MethodHandles.Lookup lookup = MethodHandles
.publicLookup()
.in(declaringClass);
// force private access so unreflectSpecial can invoke the interface's default method
Field f;
try {
f = MethodHandles.Lookup.class.getDeclaredField("allowedModes");
} catch (NoSuchFieldException nsfe) {
// IBM and OpenJ9 JDKs use a different field name
f = MethodHandles.Lookup.class.getDeclaredField("accessMode");
m.setAccessible(true);
}
final int modifiers = f.getModifiers();
if (Modifier.isFinal(modifiers)) {
final Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, modifiers & ~Modifier.FINAL);
f.setAccessible(true);
f.set(lookup, MethodHandles.Lookup.PRIVATE);
}
MethodHandle mh = lookup.unreflectSpecial(m, declaringClass).bindTo(o);
return params != null && params.length > 0 ? mh.invokeWithArguments(params) : mh.invoke();
} catch (Throwable t) {
try { // try using built-in JDK 9+ API for invoking default method
return invokeDefaultMethodUsingPrivateLookup(declaringClass, o, m, params);
} catch (final NoSuchMethodException ex) {
throw new WrappedException(t);
}
}
}
});
} catch (PrivilegedActionException pae) {
Throwable wrapped = pae.getCause();
if (wrapped instanceof WrappedException) {
throw ((WrappedException)wrapped).getWrapped();
}
throw wrapped;
}
}
/**
* For JDK 9+, we could use MethodHandles.privateLookupIn, which is not
* available in JDK 8.
*/
private static Object invokeDefaultMethodUsingPrivateLookup(Class<?> declaringClass, Object o, Method m,
Object[] params) throws WrappedException, NoSuchMethodException {
try {
final Method privateLookup = MethodHandles
.class
.getDeclaredMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
return ((MethodHandles.Lookup)privateLookup
.invoke(null, declaringClass, MethodHandles.lookup()))
.unreflectSpecial(m, declaringClass)
.bindTo(o)
.invokeWithArguments(params);
} catch (NoSuchMethodException t) {
throw t;
} catch (Throwable t) {
throw new WrappedException(t);
}
}
/**
* Updates the current state if Client method is invoked, otherwise
* does the remote invocation or returns a new proxy if subresource
* method is invoked. Can throw an expected exception if ResponseExceptionMapper
* is registered
*/
@Override
public Object invoke(Object o, Method m, Object[] params) throws Throwable {
checkClosed();
Class<?> declaringClass = m.getDeclaringClass();
if (Client.class == declaringClass || InvocationHandlerAware.class == declaringClass
|| Object.class == declaringClass || Closeable.class == declaringClass
|| AutoCloseable.class == declaringClass) {
return m.invoke(this, params);
}
resetResponse();
OperationResourceInfo ori = cri.getMethodDispatcher().getOperationResourceInfo(m);
if (ori == null) {
if (m.isDefault()) {
return invokeDefaultMethod(declaringClass, o, m, params);
}
reportInvalidResourceMethod(m, "INVALID_RESOURCE_METHOD");
}
MultivaluedMap<ParameterType, Parameter> types = getParametersInfo(m, params, ori);
List<Parameter> beanParamsList = getParameters(types, ParameterType.BEAN);
int bodyIndex = getBodyIndex(types, ori);
List<Object> pathParams = getPathParamValues(m, params, types, beanParamsList, ori, bodyIndex);
UriBuilder builder = getCurrentBuilder().clone();
if (isRoot) {
addNonEmptyPath(builder, ori.getClassResourceInfo().getURITemplate().getValue());
}
addNonEmptyPath(builder, ori.getURITemplate().getValue());
handleMatrixes(m, params, types, beanParamsList, builder);
handleQueries(m, params, types, beanParamsList, builder);
URI uri = builder.buildFromEncoded(pathParams.toArray()).normalize();
MultivaluedMap<String, String> headers = getHeaders();
MultivaluedMap<String, String> paramHeaders = new MetadataMap<>();
handleHeaders(m, params, paramHeaders, beanParamsList, types);
handleCookies(m, params, paramHeaders, beanParamsList, types);
if (ori.isSubResourceLocator()) {
ClassResourceInfo subCri = cri.getSubResource(m.getReturnType(), m.getReturnType());
if (subCri == null) {
reportInvalidResourceMethod(m, "INVALID_SUBRESOURCE");
}
MultivaluedMap<String, String> subHeaders = paramHeaders;
if (inheritHeaders) {
subHeaders.putAll(headers);
}
ClientState newState = getState().newState(uri, subHeaders,
getTemplateParametersMap(ori.getURITemplate(), pathParams));
ClientProxyImpl proxyImpl =
new ClientProxyImpl(newState, proxyLoader, subCri, false, inheritHeaders);
proxyImpl.setConfiguration(getConfiguration());
return JAXRSClientFactory.createProxy(m.getReturnType(), proxyLoader, proxyImpl);
}
headers.putAll(paramHeaders);
getState().setTemplates(getTemplateParametersMap(ori.getURITemplate(), pathParams));
Object body = null;
if (bodyIndex != -1) {
body = params[bodyIndex];
if (body == null) {
bodyIndex = -1;
}
} else if (types.containsKey(ParameterType.FORM)) {
body = handleForm(m, params, types, beanParamsList);
} else if (types.containsKey(ParameterType.REQUEST_BODY)) {
body = handleMultipart(types, ori, params);
} else if (hasFormParams(params, beanParamsList)) {
body = handleForm(m, params, types, beanParamsList);
}
setRequestHeaders(headers, ori, types.containsKey(ParameterType.FORM),
body == null ? null : body.getClass(), m.getReturnType());
try {
return doChainedInvocation(uri, headers, ori, params, body, bodyIndex, null, null);
} finally {
resetResponseStateImmediatelyIfNeeded();
}
}
protected void addNonEmptyPath(UriBuilder builder, String pathValue) {
if (!SLASH.equals(pathValue)) {
builder.path(pathValue);
}
}
protected MultivaluedMap<ParameterType, Parameter> getParametersInfo(Method m,
Object[] params, OperationResourceInfo ori) {
MultivaluedMap<ParameterType, Parameter> map = new MetadataMap<>();
List<Parameter> parameters = ori.getParameters();
if (parameters.isEmpty()) {
return map;
}
int requestBodyParam = 0;
int multipartParam = 0;
for (Parameter p : parameters) {
if (isIgnorableParameter(m, p)) {
continue;
}
if (p.getType() == ParameterType.REQUEST_BODY) {
requestBodyParam++;
if (getMultipart(ori, p.getIndex()) != null) {
multipartParam++;
}
}
map.add(p.getType(), p);
}
if (map.containsKey(ParameterType.REQUEST_BODY)) {
if (requestBodyParam > 1 && requestBodyParam != multipartParam) {
reportInvalidResourceMethod(ori.getMethodToInvoke(), "SINGLE_BODY_ONLY");
}
if (map.containsKey(ParameterType.FORM)) {
reportInvalidResourceMethod(ori.getMethodToInvoke(), "ONLY_FORM_ALLOWED");
}
}
return map;
}
protected boolean isIgnorableParameter(Method m, Parameter p) {
if (p.getType() == ParameterType.CONTEXT) {
return true;
}
return p.getType() == ParameterType.REQUEST_BODY
&& m.getParameterTypes()[p.getIndex()] == AsyncResponse.class;
}
protected static int getBodyIndex(MultivaluedMap<ParameterType, Parameter> map,
OperationResourceInfo ori) {
List<Parameter> list = map.get(ParameterType.REQUEST_BODY);
int index = list == null || list.size() > 1 ? -1 : list.get(0).getIndex();
if (ori.isSubResourceLocator() && index != -1) {
reportInvalidResourceMethod(ori.getMethodToInvoke(), "NO_BODY_IN_SUBRESOURCE");
}
return index;
}
protected void checkResponse(Method m, Response r, Message inMessage) throws Throwable {
Throwable t = null;
int status = r.getStatus();
if (status >= 300) {
Class<?>[] exTypes = m.getExceptionTypes();
if (exTypes.length == 0) {
exTypes = new Class<?>[]{WebApplicationException.class};
}
for (Class<?> exType : exTypes) {
ResponseExceptionMapper<?> mapper = findExceptionMapper(inMessage, exType);
if (mapper != null) {
t = mapper.fromResponse(r);
if (t != null) {
throw t;
}
}
}
if ((t == null) && (m.getReturnType() == Response.class) && (m.getExceptionTypes().length == 0)) {
return;
}
t = convertToWebApplicationException(r);
if (inMessage.getExchange().get(Message.RESPONSE_CODE) == null) {
throw t;
}
Endpoint ep = inMessage.getExchange().getEndpoint();
inMessage.getExchange().put(InterceptorProvider.class, getConfiguration());
inMessage.setContent(Exception.class, new Fault(t));
inMessage.getInterceptorChain().abort();
if (ep.getInFaultObserver() != null) {
ep.getInFaultObserver().onMessage(inMessage);
}
throw t;
}
}
protected static ResponseExceptionMapper<?> findExceptionMapper(Message message, Class<?> exType) {
ClientProviderFactory pf = ClientProviderFactory.getInstance(message);
return pf.createResponseExceptionMapper(message, exType);
}
protected MultivaluedMap<String, String> setRequestHeaders(MultivaluedMap<String, String> headers,
OperationResourceInfo ori,
boolean formParams,
Class<?> bodyClass,
Class<?> responseClass) {
if (headers.getFirst(HttpHeaders.CONTENT_TYPE) == null) {
if (formParams || bodyClass != null && MultivaluedMap.class.isAssignableFrom(bodyClass)) {
headers.putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED);
} else {
String ctType = null;
List<MediaType> consumeTypes = ori.getConsumeTypes();
if (!consumeTypes.isEmpty() && !consumeTypes.get(0).equals(MediaType.WILDCARD_TYPE)) {
ctType = JAXRSUtils.mediaTypeToString(ori.getConsumeTypes().get(0));
}
if (ctType != null) {
headers.putSingle(HttpHeaders.CONTENT_TYPE, ctType);
}
}
}
List<MediaType> accepts = getAccept(headers);
if (accepts == null) {
if (responseClass == Void.class || responseClass == Void.TYPE) {
accepts = Collections.singletonList(MediaType.WILDCARD_TYPE);
} else {
List<MediaType> produceTypes = ori.getProduceTypes();
boolean produceWildcard = produceTypes.isEmpty()
|| produceTypes.get(0).equals(MediaType.WILDCARD_TYPE);
if (produceWildcard) {
accepts = InjectionUtils.isPrimitive(responseClass)
? Collections.singletonList(MediaType.TEXT_PLAIN_TYPE)
: Collections.singletonList(MediaType.APPLICATION_XML_TYPE);
} else {
accepts = produceTypes;
}
}
for (MediaType mt : accepts) {
headers.add(HttpHeaders.ACCEPT, JAXRSUtils.mediaTypeToString(mt));
}
}
return headers;
}
protected List<MediaType> getAccept(MultivaluedMap<String, String> allHeaders) {
List<String> headers = allHeaders.get(HttpHeaders.ACCEPT);
if (headers == null || headers.isEmpty()) {
return null;
}
return headers.stream().
flatMap(header -> JAXRSUtils.parseMediaTypes(header).stream()).collect(Collectors.toList());
}
protected List<Object> getPathParamValues(Method m,
Object[] params,
MultivaluedMap<ParameterType, Parameter> map,
List<Parameter> beanParams,
OperationResourceInfo ori,
int bodyIndex) {
List<Object> list = new ArrayList<>();
List<String> methodVars = ori.getURITemplate().getVariables();
List<Parameter> paramsList = getParameters(map, ParameterType.PATH);
Map<String, BeanPair> beanParamValues = new HashMap<>(beanParams.size());
beanParams.forEach(p -> {
beanParamValues.putAll(getValuesFromBeanParam(params[p.getIndex()], PathParam.class));
});
if (!beanParamValues.isEmpty() && !methodVars.containsAll(beanParamValues.keySet())) {
List<String> classVars = ori.getClassResourceInfo().getURITemplate().getVariables();
classVars.forEach(classVar -> {
BeanPair pair = beanParamValues.get(classVar);
if (pair != null) {
Object paramValue = convertParamValue(pair.getValue(), pair.getAnns());
if (isRoot) {
valuesMap.put(classVar, paramValue);
} else {
list.add(paramValue);
}
}
});
}
if (isRoot) {
list.addAll(valuesMap.values());
}
Map<String, Parameter> paramsMap = new LinkedHashMap<>();
paramsList.forEach(p -> {
if (p.getName().isEmpty()) {
MultivaluedMap<String, Object> values = InjectionUtils.extractValuesFromBean(params[p.getIndex()], "");
methodVars.forEach(var -> {
list.addAll(values.get(var));
});
} else {
paramsMap.put(p.getName(), p);
}
});
Object requestBody = bodyIndex == -1 ? null : params[bodyIndex];
methodVars.forEach(varName -> {
Parameter p = paramsMap.remove(varName);
if (p != null) {
list.add(convertParamValue(params[p.getIndex()],
m.getParameterTypes()[p.getIndex()],
getParamAnnotations(m, p)));
} else if (beanParamValues.containsKey(varName)) {
BeanPair pair = beanParamValues.get(varName);
list.add(convertParamValue(pair.getValue(), pair.getAnns()));
} else if (requestBody != null) {
try {
Method getter = requestBody.getClass().
getMethod("get" + StringUtils.capitalize(varName), new Class<?>[]{});
list.add(getter.invoke(requestBody, new Object[]{}));
} catch (Exception ex) {
// continue
}
}
});
for (Parameter p : paramsMap.values()) {
if (valuesMap.containsKey(p.getName())) {
int index = 0;
for (Iterator<String> it = valuesMap.keySet().iterator(); it.hasNext(); index++) {
if (it.next().equals(p.getName()) && index < list.size()) {
list.set(index, convertParamValue(params[p.getIndex()], null));
break;
}
}
}
}
return list;
}
protected static Annotation[] getParamAnnotations(Method m, Parameter p) {
return m.getParameterAnnotations()[p.getIndex()];
}
protected static List<Parameter> getParameters(MultivaluedMap<ParameterType, Parameter> map,
ParameterType key) {
return map.get(key) == null ? Collections.emptyList() : map.get(key);
}
protected void handleQueries(Method m,
Object[] params,
MultivaluedMap<ParameterType, Parameter> map,
List<Parameter> beanParams,
UriBuilder ub) {
List<Parameter> qs = getParameters(map, ParameterType.QUERY);
qs.stream().
filter(p -> params[p.getIndex()] != null).
forEachOrdered(p -> {
addMatrixQueryParamsToBuilder(ub, p.getName(), ParameterType.QUERY,
getParamAnnotations(m, p), params[p.getIndex()]);
});
beanParams.stream().
map(p -> getValuesFromBeanParam(params[p.getIndex()], QueryParam.class)).
forEachOrdered(values -> {
values.forEach((key, value) -> {
if (value != null) {
addMatrixQueryParamsToBuilder(ub, key, ParameterType.QUERY,
value.getAnns(), value.getValue());
}
});
});
}
protected Map<String, BeanPair> getValuesFromBeanParam(Object bean, Class<? extends Annotation> annClass) {
Map<String, BeanPair> values = new HashMap<>();
getValuesFromBeanParam(bean, annClass, values);
return values;
}
protected Map<String, BeanPair> getValuesFromBeanParam(Object bean,
Class<? extends Annotation> annClass,
Map<String, BeanPair> values) {
boolean completeFieldIntrospectionNeeded = false;
for (Method m : bean.getClass().getMethods()) {
if (m.getName().startsWith("set")) {
try {
String propertyName = m.getName().substring(3);
Annotation methodAnnotation = m.getAnnotation(annClass);
boolean beanParam = m.getAnnotation(BeanParam.class) != null;
if (methodAnnotation != null || beanParam) {
Method getter = bean.getClass().getMethod("get" + propertyName, new Class<?>[]{});
Object value = getter.invoke(bean, new Object[]{});
if (value != null) {
if (methodAnnotation != null) {
String annotationValue = AnnotationUtils.getAnnotationValue(methodAnnotation);
values.put(annotationValue, new BeanPair(value, m.getParameterAnnotations()[0]));
} else {
getValuesFromBeanParam(value, annClass, values);
}
}
} else {
String fieldName = StringUtils.uncapitalize(propertyName);
Field f = InjectionUtils.getDeclaredField(bean.getClass(), fieldName);
if (f == null) {
completeFieldIntrospectionNeeded = true;
continue;
}
boolean jaxrsParamAnnAvailable = getValuesFromBeanParamField(bean, f, annClass, values);
if (!jaxrsParamAnnAvailable && f.getAnnotation(BeanParam.class) != null) {
Object value = ReflectionUtil.accessDeclaredField(f, bean, Object.class);
if (value != null) {
getValuesFromBeanParam(value, annClass, values);
}
}
}
} catch (Throwable t) {
// ignore
}
}
if (completeFieldIntrospectionNeeded) {
for (Field f : bean.getClass().getDeclaredFields()) {
boolean jaxrsParamAnnAvailable = getValuesFromBeanParamField(bean, f, annClass, values);
if (!jaxrsParamAnnAvailable && f.getAnnotation(BeanParam.class) != null) {
Object value = ReflectionUtil.accessDeclaredField(f, bean, Object.class);
if (value != null) {
getValuesFromBeanParam(value, annClass, values);
}
}
}
}
}
return values;
}
protected boolean getValuesFromBeanParamField(Object bean,
Field f,
Class<? extends Annotation> annClass,
Map<String, BeanPair> values) {
boolean jaxrsParamAnnAvailable = false;
Annotation fieldAnnotation = f.getAnnotation(annClass);
if (fieldAnnotation != null) {
jaxrsParamAnnAvailable = true;
Object value = ReflectionUtil.accessDeclaredField(f, bean, Object.class);
if (value != null) {
String annotationValue = AnnotationUtils.getAnnotationValue(fieldAnnotation);
values.put(annotationValue, new BeanPair(value, f.getAnnotations()));
}
}
return jaxrsParamAnnAvailable;
}
protected void handleMatrixes(Method m,
Object[] params,
MultivaluedMap<ParameterType, Parameter> map,
List<Parameter> beanParams,
UriBuilder ub) {
List<Parameter> mx = getParameters(map, ParameterType.MATRIX);
mx.stream().
filter(p -> params[p.getIndex()] != null).
forEachOrdered(p -> {
addMatrixQueryParamsToBuilder(ub, p.getName(), ParameterType.MATRIX,
getParamAnnotations(m, p), params[p.getIndex()]);
});
beanParams.stream().
map(p -> getValuesFromBeanParam(params[p.getIndex()], MatrixParam.class)).
forEachOrdered(values -> {
values.forEach((key, value) -> {
if (value != null) {
addMatrixQueryParamsToBuilder(ub, key, ParameterType.MATRIX,
value.getAnns(), value.getValue());
}
});
});
}
protected MultivaluedMap<String, String> handleForm(Method m,
Object[] params,
MultivaluedMap<ParameterType, Parameter> map,
List<Parameter> beanParams) {
MultivaluedMap<String, String> form = new MetadataMap<>();
List<Parameter> fm = getParameters(map, ParameterType.FORM);
fm.forEach(p -> {
addFormValue(form, p.getName(), params[p.getIndex()], getParamAnnotations(m, p));
});
beanParams.stream().
map(p -> getValuesFromBeanParam(params[p.getIndex()], FormParam.class)).
forEachOrdered(values -> {
values.forEach((key, value) -> {
addFormValue(form, key, value.getValue(), value.getAnns());
});
});
return form;
}
protected void addFormValue(MultivaluedMap<String, String> form, String name, Object pValue, Annotation[] anns) {
if (pValue != null) {
if (InjectionUtils.isSupportedCollectionOrArray(pValue.getClass())) {
Collection<?> c = pValue.getClass().isArray()
? Arrays.asList((Object[]) pValue) : (Collection<?>) pValue;
for (Iterator<?> it = c.iterator(); it.hasNext();) {
FormUtils.addPropertyToForm(form, name, convertParamValue(it.next(), anns));
}
} else {
FormUtils.addPropertyToForm(form, name, name.isEmpty()
? pValue : convertParamValue(pValue, anns));
}
}
}
protected List<Attachment> handleMultipart(MultivaluedMap<ParameterType, Parameter> map,
OperationResourceInfo ori,
Object[] params) {
List<Parameter> fm = getParameters(map, ParameterType.REQUEST_BODY);
List<Attachment> atts = new ArrayList<>(fm.size());
fm.forEach(p -> {
Multipart part = getMultipart(ori, p.getIndex());
if (part != null) {
Object partObject = params[p.getIndex()];
if (partObject != null) {
atts.add(new Attachment(part.value(), part.type(), partObject));
}
}
});
return atts;
}
protected void handleHeaders(Method m,
Object[] params,
MultivaluedMap<String, String> headers,
List<Parameter> beanParams,
MultivaluedMap<ParameterType, Parameter> map) {
List<Parameter> hs = getParameters(map, ParameterType.HEADER);
hs.stream().
filter(p -> params[p.getIndex()] != null).
forEachOrdered(p -> {
headers.add(p.getName(), convertParamValue(params[p.getIndex()], getParamAnnotations(m, p)));
});
beanParams.stream().
map(p -> getValuesFromBeanParam(params[p.getIndex()], HeaderParam.class)).
forEachOrdered(values -> {
values.forEach((key, value) -> {
if (value != null) {
headers.add(key, convertParamValue(value.getValue(), value.getAnns()));
}
});
});
}
protected static Multipart getMultipart(OperationResourceInfo ori, int index) {
Method aMethod = ori.getAnnotatedMethod();
return aMethod != null ? AnnotationUtils.getAnnotation(
aMethod.getParameterAnnotations()[index], Multipart.class) : null;
}
protected void handleCookies(Method m,
Object[] params,
MultivaluedMap<String, String> headers,
List<Parameter> beanParams,
MultivaluedMap<ParameterType, Parameter> map) {
List<Parameter> cs = getParameters(map, ParameterType.COOKIE);
cs.stream().
filter(p -> params[p.getIndex()] != null).
forEachOrdered(p -> {
headers.add(HttpHeaders.COOKIE,
p.getName() + '='
+ convertParamValue(params[p.getIndex()].toString(), getParamAnnotations(m, p)));
});
beanParams.stream().
map(p -> getValuesFromBeanParam(params[p.getIndex()], CookieParam.class)).
forEachOrdered(values -> {
values.forEach((key, value) -> {
if (value != null) {
headers.add(HttpHeaders.COOKIE,
key + "=" + convertParamValue(value.getValue(), value.getAnns()));
}
});
});
}
protected Message createMessage(Object body,
OperationResourceInfo ori,
MultivaluedMap<String, String> headers,
URI currentURI,
Exchange exchange,
Map<String, Object> invocationContext,
boolean isProxy) {
return createMessage(body, ori.getHttpMethod(), headers, currentURI,
exchange, invocationContext, isProxy);
}
//CHECKSTYLE:OFF
protected Object doChainedInvocation(URI uri,
MultivaluedMap<String, String> headers,
OperationResourceInfo ori,
Object[] methodParams,
Object body,
int bodyIndex,
Exchange exchange,
Map<String, Object> invocationContext) throws Throwable {
//CHECKSTYLE:ON
Bus configuredBus = getConfiguration().getBus();
Bus origBus = BusFactory.getAndSetThreadDefaultBus(configuredBus);
ClassLoaderHolder origLoader = null;
try {
ClassLoader loader = configuredBus.getExtension(ClassLoader.class);
if (loader != null) {
origLoader = ClassLoaderUtils.setThreadContextClassloader(loader);
}
Message outMessage = createMessage(body, ori, headers, uri, exchange, invocationContext, true);
if (bodyIndex != -1) {
outMessage.put(Type.class, ori.getMethodToInvoke().getGenericParameterTypes()[bodyIndex]);
}
outMessage.getExchange().setOneWay(ori.isOneway());
setSupportOnewayResponseProperty(outMessage);
outMessage.setContent(OperationResourceInfo.class, ori);
setPlainOperationNameProperty(outMessage, ori.getMethodToInvoke().getName());
outMessage.getExchange().put(Method.class, ori.getMethodToInvoke());
outMessage.put(Annotation.class.getName(),
getMethodAnnotations(ori.getAnnotatedMethod(), bodyIndex));
outMessage.getExchange().put(Message.SERVICE_OBJECT, proxy);
if (methodParams != null) {
outMessage.put(List.class, Arrays.asList(methodParams));
}
if (body != null) {
outMessage.put(PROXY_METHOD_PARAM_BODY_INDEX, bodyIndex);
}
outMessage.getInterceptorChain().add(bodyWriter);
Map<String, Object> reqContext = getRequestContext(outMessage);
reqContext.put(OperationResourceInfo.class.getName(), ori);
reqContext.put(PROXY_METHOD_PARAM_BODY_INDEX, bodyIndex);
// execute chain
InvocationCallback<Object> asyncCallback = checkAsyncCallback(ori, reqContext, outMessage);
if (asyncCallback != null) {
return doInvokeAsync(ori, outMessage, asyncCallback);
}
doRunInterceptorChain(outMessage);
Object[] results = preProcessResult(outMessage);
if (results != null && results.length == 1) {
return results[0];
}
try {
return handleResponse(outMessage, ori.getClassResourceInfo().getServiceClass());
} finally {
completeExchange(outMessage.getExchange(), true);
}
} finally {
if (origLoader != null) {
origLoader.reset();
}
if (origBus != configuredBus) {
BusFactory.setThreadDefaultBus(origBus);
}
}
}
protected InvocationCallback<Object> checkAsyncCallback(OperationResourceInfo ori,
Map<String, Object> reqContext,
Message outMessage) {
Object callbackProp = reqContext.get(InvocationCallback.class.getName());
if (callbackProp != null) {
if (callbackProp instanceof Collection) {
@SuppressWarnings("unchecked")
Collection<InvocationCallback<Object>> callbacks = (Collection<InvocationCallback<Object>>)callbackProp;
for (InvocationCallback<Object> callback : callbacks) {
if (doCheckAsyncCallback(ori, callback) != null) {
return callback;
}
}
} else {
@SuppressWarnings("unchecked")
InvocationCallback<Object> callback = (InvocationCallback<Object>)callbackProp;
return doCheckAsyncCallback(ori, callback);
}
}
return null;
}
protected InvocationCallback<Object> doCheckAsyncCallback(OperationResourceInfo ori,
InvocationCallback<Object> callback) {
Type callbackOutType = getCallbackType(callback);
Class<?> callbackRespClass = getCallbackClass(callbackOutType);
Class<?> methodReturnType = ori.getMethodToInvoke().getReturnType();
if (Object.class == callbackRespClass
|| callbackRespClass.isAssignableFrom(methodReturnType)
|| PrimitiveUtils.canPrimitiveTypeBeAutoboxed(methodReturnType, callbackRespClass)) {
return callback;
}
return null;
}
protected Object doInvokeAsync(OperationResourceInfo ori,
Message outMessage,
InvocationCallback<Object> asyncCallback) {
outMessage.getExchange().setSynchronous(false);
setAsyncMessageObserverIfNeeded(outMessage.getExchange());
JaxrsClientCallback<?> cb = newJaxrsClientCallback(asyncCallback, outMessage,
ori.getMethodToInvoke().getReturnType(), ori.getMethodToInvoke().getGenericReturnType());
outMessage.getExchange().put(JaxrsClientCallback.class, cb);
doRunInterceptorChain(outMessage);
return null;
}
protected JaxrsClientCallback<?> newJaxrsClientCallback(InvocationCallback<Object> asyncCallback,
Message outMessage,
Class<?> responseClass,
Type outGenericType) {
return new JaxrsClientCallback<>(asyncCallback, responseClass, outGenericType);
}
@Override
protected Object retryInvoke(URI newRequestURI,
MultivaluedMap<String, String> headers,
Object body,
Exchange exchange,
Map<String, Object> invContext) throws Throwable {
Map<String, Object> reqContext = CastUtils.cast((Map<?, ?>)invContext.get(REQUEST_CONTEXT));
int bodyIndex = body != null ? (Integer)reqContext.get(PROXY_METHOD_PARAM_BODY_INDEX) : -1;
OperationResourceInfo ori =
(OperationResourceInfo)reqContext.get(OperationResourceInfo.class.getName());
return doChainedInvocation(newRequestURI, headers, ori, null,
body, bodyIndex, exchange, invContext);
}
protected Object handleResponse(Message outMessage, Class<?> serviceCls)
throws Throwable {
try {
Response r = setResponseBuilder(outMessage, outMessage.getExchange()).build();
((ResponseImpl)r).setOutMessage(outMessage);
getState().setResponse(r);
Method method = outMessage.getExchange().get(Method.class);
checkResponse(method, r, outMessage);
if (method.getReturnType() == Void.class || method.getReturnType() == Void.TYPE) {
return null;
}
if (method.getReturnType() == Response.class
&& (r.getEntity() == null || InputStream.class.isAssignableFrom(r.getEntity().getClass())
&& ((InputStream)r.getEntity()).available() == 0)) {
return r;
}
if (PropertyUtils.isTrue(super.getConfiguration().getResponseContext().get(BUFFER_PROXY_RESPONSE))) {
r.bufferEntity();
}
Class<?> returnType = getReturnType(method, outMessage);
Type genericType = getGenericReturnType(serviceCls, method, returnType);
returnType = InjectionUtils.updateParamClassToTypeIfNeeded(returnType, genericType);
return readBody(r,
outMessage,
returnType,
genericType,
method.getDeclaredAnnotations());
} finally {
ClientProviderFactory.getInstance(outMessage).clearThreadLocalProxies();
}
}
protected Type getGenericReturnType(Class<?> serviceCls, Method method, Class<?> returnType) {
return InjectionUtils.processGenericTypeIfNeeded(serviceCls, returnType, method.getGenericReturnType());
}
protected Class<?> getReturnType(Method method, Message outMessage) {
return method.getReturnType();
}
public Object getInvocationHandler() {
return this;
}
protected static void reportInvalidResourceMethod(Method m, String name) {
org.apache.cxf.common.i18n.Message errorMsg =
new org.apache.cxf.common.i18n.Message(name,
BUNDLE,
m.getDeclaringClass().getName(),
m.getName());
LOG.severe(errorMsg.toString());
throw new ProcessingException(errorMsg.toString());
}
protected static Annotation[] getMethodAnnotations(Method aMethod, int bodyIndex) {
return aMethod == null || bodyIndex == -1 ? new Annotation[0]
: aMethod.getParameterAnnotations()[bodyIndex];
}
/**
* Checks if @BeanParam object has at least one @FormParam declaration.
* @param params parameter values
* @param beanParams bean parameters
* @return "true" @BeanParam object has at least one @FormParam, "false" otherwise
*/
private boolean hasFormParams(Object[] params, List<Parameter> beanParams) {
return beanParams
.stream()
.map(p -> getValuesFromBeanParam(params[p.getIndex()], FormParam.class))
.anyMatch(((Predicate<Map<String, BeanPair>>) Map::isEmpty).negate());
}
protected class BodyWriter extends AbstractBodyWriter {
@Override
protected void doWriteBody(Message outMessage,
Object body,
Type bodyType,
Annotation[] customAnns,
OutputStream os) throws Fault {
OperationResourceInfo ori = outMessage.getContent(OperationResourceInfo.class);
if (ori == null) {
return;
}
Method method = ori.getMethodToInvoke();
int bodyIndex = (Integer)outMessage.get(PROXY_METHOD_PARAM_BODY_INDEX);
Annotation[] anns = customAnns != null ? customAnns
: getMethodAnnotations(ori.getAnnotatedMethod(), bodyIndex);
try {
if (bodyIndex != -1) {
Class<?> paramClass = method.getParameterTypes()[bodyIndex];
Class<?> bodyClass =
paramClass.isAssignableFrom(body.getClass()) ? paramClass : body.getClass();
Type genericType = method.getGenericParameterTypes()[bodyIndex];
if (bodyType != null) {
genericType = bodyType;
}
genericType = InjectionUtils.processGenericTypeIfNeeded(
ori.getClassResourceInfo().getServiceClass(), bodyClass, genericType);
bodyClass = InjectionUtils.updateParamClassToTypeIfNeeded(bodyClass, genericType);
writeBody(body, outMessage, bodyClass, genericType, anns, os);
} else {
Type paramType = body.getClass();
if (bodyType != null) {
paramType = bodyType;
}
writeBody(body, outMessage, body.getClass(), paramType,
anns, os);
}
} catch (Exception ex) {
throw new Fault(ex);
}
}
}
protected static class BeanPair {
protected Object value;
protected Annotation[] anns;
BeanPair(Object value, Annotation[] anns) {
this.value = value;
this.anns = anns;
}
public Object getValue() {
return value;
}
public Annotation[] getAnns() {
return anns;
}
}
class ClientAsyncResponseInterceptor extends AbstractClientAsyncResponseInterceptor {
@Override
protected void doHandleAsyncResponse(Message message, Response r, JaxrsClientCallback<?> cb) {
try {
Object entity = handleResponse(message.getExchange().getOutMessage(),
cb.getResponseClass());
cb.handleResponse(message, new Object[] {entity});
} catch (Throwable t) {
cb.handleException(message, t);
} finally {
completeExchange(message.getExchange(), false);
closeAsyncResponseIfPossible(r, message, cb);
}
}
}
}