cxf-rt-rs-client changes since 3.4.3
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java b/transform/src/patch/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java
new file mode 100644
index 0000000..8937909
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java
@@ -0,0 +1,1149 @@
+/**
+ * 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 {
+ Class<?>[] parameterTypes = method.getParameterTypes();
+ if (bodyIndex >= 0 && bodyIndex < parameterTypes.length) {
+ Class<?> paramClass = parameterTypes[bodyIndex];
+ Class<?> bodyClass =
+ paramClass.isAssignableFrom(body.getClass()) ? paramClass : body.getClass();
+ Type genericType = bodyType;
+ if (genericType == null) {
+ Type[] genericParameterTypes = method.getGenericParameterTypes();
+ if (bodyIndex < genericParameterTypes.length) {
+ genericType = genericParameterTypes[bodyIndex];
+ }
+ }
+ 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);
+ }
+ }
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/client/JAXRSClientFactory.java b/transform/src/patch/java/org/apache/cxf/jaxrs/client/JAXRSClientFactory.java
new file mode 100644
index 0000000..1e487ab
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/client/JAXRSClientFactory.java
@@ -0,0 +1,414 @@
+/**
+ * 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.lang.reflect.InvocationHandler;
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.apache.cxf.common.util.ProxyHelper;
+import org.apache.cxf.feature.Feature;
+import org.apache.cxf.jaxrs.model.UserResource;
+
+/**
+ * Factory for creating proxy clients.
+ *
+ */
+public final class JAXRSClientFactory {
+
+ private JAXRSClientFactory() {
+
+ }
+
+ /**
+ * Creates a proxy
+ * @param baseAddress baseAddress
+ * @param cls resource class, if not interface then a CGLIB proxy will be created
+ * @return typed proxy
+ */
+ public static <T> T create(String baseAddress, Class<T> cls) {
+ return create(URI.create(baseAddress), cls);
+ }
+
+ /**
+ * Creates a proxy using a custom class loader
+ * @param baseAddress baseAddress
+ * @param loader class loader
+ * @param cls resource class, if not interface then a CGLIB proxy will be created
+ * @return typed proxy
+ */
+ public static <T> T create(String baseAddress, Class<T> cls, ClassLoader loader) {
+ JAXRSClientFactoryBean bean = getBean(baseAddress, cls, null);
+ bean.setClassLoader(loader);
+ return bean.create(cls);
+ }
+
+ /**
+ * Creates a proxy
+ * @param baseURI baseURI
+ * @param cls resource class, if not interface then a CGLIB proxy will be created
+ * @return typed proxy
+ */
+ public static <T> T create(URI baseURI, Class<T> cls) {
+ return create(baseURI, cls, false);
+ }
+
+ /**
+ * Creates a proxy
+ * @param baseURI baseURI
+ * @param cls resource class, if not interface then a CGLIB proxy will be created
+ * @param inheritHeaders if true then existing proxy headers will be inherited by
+ * subresource proxies if any
+ * @return typed proxy
+ */
+ public static <T> T create(URI baseURI, Class<T> cls, boolean inheritHeaders) {
+ JAXRSClientFactoryBean bean = getBean(baseURI.toString(), cls, null);
+ bean.setInheritHeaders(inheritHeaders);
+ return bean.create(cls);
+ }
+
+ /**
+ * Creates a proxy
+ * @param baseAddress baseAddres
+ * @param cls resource class, if not interface then a CGLIB proxy will be created
+ * @param properties additional properties
+ * @return typed proxy
+ */
+ public static <T> T create(String baseAddress, Class<T> cls, Map<String, Object> properties) {
+ JAXRSClientFactoryBean bean = getBean(baseAddress, cls, null);
+ bean.setProperties(properties);
+ return bean.create(cls);
+ }
+
+ /**
+ * Creates a proxy
+ * @param baseAddress baseAddress
+ * @param cls resource class, if not interface then a CGLIB proxy will be created
+ * @param configLocation classpath location of the configuration resource
+ * @return typed proxy
+ */
+ public static <T> T create(String baseAddress, Class<T> cls, String configLocation) {
+ JAXRSClientFactoryBean bean = getBean(baseAddress, cls, configLocation);
+ return bean.create(cls);
+ }
+
+ /**
+ * Creates a proxy
+ * @param baseAddress baseAddress
+ * @param cls resource class, if not interface then a CGLIB proxy will be created
+ * This class is expected to have a root JAXRS Path annotation containing
+ * template variables, for ex, "/path/{id1}/{id2}"
+ * @param configLocation classpath location of the configuration resource
+ * @param varValues values to replace root Path template variables
+ * @return typed proxy
+ */
+ public static <T> T create(String baseAddress, Class<T> cls, String configLocation,
+ Object... varValues) {
+ JAXRSClientFactoryBean bean = getBean(baseAddress, cls, configLocation);
+ return bean.create(cls, varValues);
+ }
+
+
+ /**
+ * Creates a proxy
+ * @param baseAddress baseAddress
+ * @param cls proxy class, if not interface then a CGLIB proxy will be created
+ * @param providers list of providers
+ * @return typed proxy
+ */
+ public static <T> T create(String baseAddress, Class<T> cls, List<?> providers) {
+ return create(baseAddress, cls, providers, null);
+ }
+
+ /**
+ * Creates a thread safe proxy
+ * @param baseAddress baseAddress
+ * @param cls proxy class, if not interface then a CGLIB proxy will be created
+ * @param providers list of providers
+ * @param threadSafe if true then a thread-safe proxy will be created
+ * @return typed proxy
+ */
+ public static <T> T create(String baseAddress, Class<T> cls, List<?> providers, boolean threadSafe) {
+ return create(baseAddress, cls, providers, Collections.emptyMap(), threadSafe);
+ }
+ /**
+ * Creates a thread safe proxy
+ * @param baseAddress baseAddress
+ * @param cls proxy class, if not interface then a CGLIB proxy will be created
+ * @param providers list of providers
+ * @param threadSafe if true then a thread-safe proxy will be created
+ * @param properties additional properties
+ * @return typed proxy
+ */
+ public static <T> T create(String baseAddress, Class<T> cls, List<?> providers,
+ Map<String, Object> properties, boolean threadSafe) {
+ JAXRSClientFactoryBean bean = getBean(baseAddress, cls, null);
+ bean.setProviders(providers);
+ bean.setProperties(properties);
+ if (threadSafe) {
+ bean.setInitialState(new ThreadLocalClientState(baseAddress, properties));
+ }
+ return bean.create(cls);
+ }
+
+ /**
+ * Creates a thread safe proxy and allows to specify time to keep state.
+ * @param baseAddress baseAddress
+ * @param cls proxy class, if not interface then a CGLIB proxy will be created
+ * @param providers list of providers
+ * @param timeToKeepState how long to keep this state
+ * @return typed proxy
+ */
+ public static <T> T create(String baseAddress, Class<T> cls, List<?> providers, long timeToKeepState) {
+ JAXRSClientFactoryBean bean = getBean(baseAddress, cls, null);
+ bean.setProviders(providers);
+ bean.setInitialState(new ThreadLocalClientState(baseAddress, timeToKeepState));
+ return bean.create(cls);
+ }
+
+ /**
+ * Creates a proxy
+ * @param baseAddress baseAddress
+ * @param cls proxy class, if not interface then a CGLIB proxy will be created
+ * @param providers list of providers
+ * @param configLocation classpath location of the configuration resource
+ * @return typed proxy
+ */
+ public static <T> T create(String baseAddress, Class<T> cls, List<?> providers, String configLocation) {
+ JAXRSClientFactoryBean bean = getBean(baseAddress, cls, configLocation);
+ bean.setProviders(providers);
+ return bean.create(cls);
+ }
+
+ /**
+ * Creates a proxy
+ * @param baseAddress baseAddress
+ * @param cls proxy class, if not interface then a CGLIB proxy will be created
+ * @param providers list of providers
+ * @param features the features which will be applied to the client
+ * @param configLocation classpath location of the configuration resource
+ * @return typed proxy
+ */
+ public static <T> T create(String baseAddress, Class<T> cls, List<?> providers,
+ List<Feature> features,
+ String configLocation) {
+ JAXRSClientFactoryBean bean = getBean(baseAddress, cls, configLocation);
+ bean.setProviders(providers);
+ bean.setFeatures(features);
+ return bean.create(cls);
+ }
+
+ /**
+ * Creates a proxy which will do basic authentication
+ * @param baseAddress baseAddress
+ * @param cls proxy class, if not interface then a CGLIB proxy will be created
+ * @param username username
+ * @param password password
+ * @param configLocation classpath location of the configuration resource
+ * @return typed proxy
+ */
+ public static <T> T create(String baseAddress, Class<T> cls, String username,
+ String password, String configLocation) {
+ JAXRSClientFactoryBean bean = getBean(baseAddress, cls, configLocation);
+ bean.setUsername(username);
+ bean.setPassword(password);
+ return bean.create(cls);
+ }
+
+ /**
+ * Creates a proxy which will do basic authentication
+ * @param baseAddress baseAddress
+ * @param cls proxy class, if not interface then a CGLIB proxy will be created
+ * @param providers list of providers
+ * @param username username
+ * @param password password
+ * @param configLocation classpath location of the configuration resource
+ * @return typed proxy
+ */
+ public static <T> T create(String baseAddress, Class<T> cls, List<?> providers,
+ String username, String password, String configLocation) {
+ JAXRSClientFactoryBean bean = getBean(baseAddress, cls, configLocation);
+ bean.setUsername(username);
+ bean.setPassword(password);
+ bean.setProviders(providers);
+ return bean.create(cls);
+ }
+
+ /**
+ * Creates a proxy using user resource model
+ * @param baseAddress baseAddress
+ * @param cls proxy class, if not interface then a CGLIB proxy will be created
+ * @param modelRef model location
+ * @return typed proxy
+ */
+ public static <T> T createFromModel(String baseAddress, Class<T> cls, String modelRef,
+ String configLocation) {
+ return createFromModel(baseAddress, cls, modelRef, Collections.emptyList(), configLocation);
+ }
+
+ /**
+ * Creates a proxy using user resource model
+ * @param baseAddress baseAddress
+ * @param cls proxy class, if not interface then a CGLIB proxy will be created
+ * @param modelRef model location
+ * @param providers list of providers
+ * @param configLocation classpath location of the configuration resource
+ * @return typed proxy
+ */
+ public static <T> T createFromModel(String baseAddress, Class<T> cls, String modelRef,
+ List<?> providers, String configLocation) {
+ JAXRSClientFactoryBean bean = WebClient.getBean(baseAddress, configLocation);
+ bean.setProviders(providers);
+ bean.setModelRef(modelRef);
+ bean.setServiceClass(cls);
+ return bean.create(cls);
+ }
+
+ /**
+ * Creates a thread safe proxy using user resource model
+ * @param baseAddress baseAddress
+ * @param cls proxy class, if not interface then a CGLIB proxy will be created
+ * @param modelRef model location
+ * @param providers list of providers
+ * @param threadSafe if true then thread-safe proxy will be created
+ * @return typed proxy
+ */
+ public static <T> T createFromModel(String baseAddress, Class<T> cls, String modelRef,
+ List<?> providers, boolean threadSafe) {
+ JAXRSClientFactoryBean bean = WebClient.getBean(baseAddress, null);
+ bean.setProviders(providers);
+ bean.setModelRef(modelRef);
+ bean.setServiceClass(cls);
+ if (threadSafe) {
+ bean.setInitialState(new ThreadLocalClientState(baseAddress));
+ }
+ return bean.create(cls);
+ }
+
+ /**
+ * Creates a thread safe proxy using user resource model and allows to
+ * specify time to keep state.
+ * @param baseAddress baseAddress
+ * @param cls proxy class, if not interface then a CGLIB proxy will be created
+ * @param modelRef model location
+ * @param providers list of providers
+ * @param timeToKeepState how long to keep this state
+ * @return typed proxy
+ */
+ public static <T> T createFromModel(String baseAddress, Class<T> cls, String modelRef,
+ List<?> providers, long timeToKeepState) {
+ JAXRSClientFactoryBean bean = WebClient.getBean(baseAddress, null);
+ bean.setProviders(providers);
+ bean.setModelRef(modelRef);
+ bean.setServiceClass(cls);
+ bean.setInitialState(new ThreadLocalClientState(baseAddress, timeToKeepState));
+ return bean.create(cls);
+ }
+
+ /**
+ * Creates a proxy using user resource model
+ * @param baseAddress baseAddress
+ * @param cls proxy class, if not interface then a CGLIB proxy will be created
+ * @param modelBeans model beans
+ * @param configLocation classpath location of the configuration resource
+ * @return typed proxy
+ */
+ public static <T> T createFromModel(String baseAddress, Class<T> cls, List<UserResource> modelBeans,
+ String configLocation) {
+ return createFromModel(baseAddress, cls, modelBeans, Collections.emptyList(), configLocation);
+ }
+
+ /**
+ * Creates a proxy using user resource model
+ * @param baseAddress baseAddress
+ * @param cls proxy class, if not interface then a CGLIB proxy will be created
+ * @param modelBeans model beans
+ * @param providers list of providers
+ * @param configLocation classpath location of the configuration resource
+ * @return typed proxy
+ */
+ public static <T> T createFromModel(String baseAddress, Class<T> cls, List<UserResource> modelBeans,
+ List<?> providers, String configLocation) {
+ JAXRSClientFactoryBean bean = WebClient.getBean(baseAddress, configLocation);
+
+ bean.setProviders(providers);
+ bean.setModelBeans(modelBeans);
+ bean.setServiceClass(cls);
+ return bean.create(cls);
+ }
+
+ /**
+ * Creates a proxy, baseURI will be set to Client currentURI
+ *
+ * @param client Client instance
+ * @param cls proxy class, if not interface then a CGLIB proxy will be created
+ * @return typed proxy
+ */
+ public static <T> T fromClient(Client client, Class<T> cls) {
+ return fromClient(client, cls, false);
+ }
+
+ /**
+ * Creates a proxy, baseURI will be set to Client currentURI
+ * @param client Client instance
+ * @param cls proxy class, if not interface then a CGLIB proxy will be created
+ * @param inheritHeaders if true then existing Client headers will be inherited by new proxy
+ * and subresource proxies if any
+ * @return typed proxy
+ */
+ public static <T> T fromClient(Client client, Class<T> cls, boolean inheritHeaders) {
+ JAXRSClientFactoryBean bean = getBean(client.getCurrentURI().toString(), cls, null);
+ bean.setInheritHeaders(inheritHeaders);
+
+ ClientState clientState = WebClient.getClientState(client);
+
+ final T proxy;
+ if (clientState == null) {
+ proxy = bean.create(cls);
+ if (inheritHeaders) {
+ WebClient.client(proxy).headers(client.getHeaders());
+ }
+ } else {
+ MultivaluedMap<String, String> headers = inheritHeaders ? client.getHeaders() : null;
+ bean.setInitialState(clientState.newState(client.getCurrentURI(), headers, null, bean.getProperties()));
+ proxy = bean.create(cls);
+ }
+ WebClient.copyProperties(WebClient.client(proxy), client);
+ return proxy;
+ }
+
+ static <T> T createProxy(Class<T> cls, ClassLoader loader, InvocationHandler handler) {
+
+ return cls.cast(ProxyHelper.getProxy(loader == null ? cls.getClassLoader() : loader,
+ new Class[]{Client.class, InvocationHandlerAware.class, cls},
+ handler));
+ }
+
+ private static JAXRSClientFactoryBean getBean(String baseAddress, Class<?> cls, String configLocation) {
+ JAXRSClientFactoryBean bean = WebClient.getBean(baseAddress, configLocation);
+ bean.setServiceClass(cls);
+ return bean;
+ }
+
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/client/LocalClientState.java b/transform/src/patch/java/org/apache/cxf/jaxrs/client/LocalClientState.java
new file mode 100644
index 0000000..477e5d3
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/client/LocalClientState.java
@@ -0,0 +1,185 @@
+/**
+ * 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.net.URI;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.jaxrs.impl.MetadataMap;
+import org.apache.cxf.jaxrs.impl.UriBuilderImpl;
+
+/**
+ * Keeps the client state such as the baseURI, currentURI, requestHeaders, current response
+ *
+ */
+public class LocalClientState implements ClientState {
+ private static final String HTTP_SCHEME = "http";
+ private static final String WS_SCHEME = "ws";
+
+ private MultivaluedMap<String, String> requestHeaders = new MetadataMap<>(false, true);
+ private MultivaluedMap<String, String> templates;
+ private Response response;
+ private URI baseURI;
+ private UriBuilder currentBuilder;
+ private Map<String, Object> properties;
+
+ public LocalClientState() {
+
+ }
+
+ public LocalClientState(URI baseURI) {
+ this(baseURI, Collections.emptyMap());
+ }
+
+ public LocalClientState(URI baseURI, Map<String, Object> properties) {
+ this.baseURI = baseURI;
+
+ if (properties != null) {
+ this.properties = new HashMap<>(properties);
+ }
+
+ resetCurrentUri(properties);
+ }
+
+ public LocalClientState(URI baseURI, URI currentURI) {
+ this(baseURI, currentURI, Collections.emptyMap());
+ }
+
+ public LocalClientState(URI baseURI, URI currentURI, Map<String, Object> properties) {
+ this.baseURI = baseURI;
+
+ if (properties != null) {
+ this.properties = new HashMap<>(properties);
+ }
+
+ this.currentBuilder = new UriBuilderImpl(properties).uri(currentURI);
+ }
+
+ public LocalClientState(LocalClientState cs) {
+ this.requestHeaders = new MetadataMap<>(cs.requestHeaders);
+ this.templates = cs.templates == null ? null : new MetadataMap<String, String>(cs.templates);
+ this.response = cs.response;
+
+ this.baseURI = cs.baseURI;
+ this.currentBuilder = cs.currentBuilder != null ? cs.currentBuilder.clone() : null;
+ this.properties = cs.properties;
+ }
+
+ private void resetCurrentUri(Map<String, Object> props) {
+ if (isSupportedScheme(baseURI)) {
+ this.currentBuilder = new UriBuilderImpl(props).uri(baseURI);
+ } else {
+ this.currentBuilder = new UriBuilderImpl(props).uri("/");
+ }
+ }
+
+ public void setCurrentBuilder(UriBuilder currentBuilder) {
+ this.currentBuilder = currentBuilder;
+ }
+
+ public UriBuilder getCurrentBuilder() {
+ return currentBuilder;
+ }
+
+ public void setBaseURI(URI baseURI) {
+ this.baseURI = baseURI;
+ resetCurrentUri(Collections.emptyMap());
+ }
+
+ public URI getBaseURI() {
+ return baseURI;
+ }
+
+ public void setResponse(Response r) {
+ this.response = r;
+ }
+
+ public Response getResponse() {
+ return response;
+ }
+
+ public void setRequestHeaders(MultivaluedMap<String, String> requestHeaders) {
+ this.requestHeaders = requestHeaders;
+ }
+
+ public MultivaluedMap<String, String> getRequestHeaders() {
+ return requestHeaders;
+ }
+
+ public MultivaluedMap<String, String> getTemplates() {
+ return templates;
+ }
+
+ public void setTemplates(MultivaluedMap<String, String> map) {
+ if (templates == null) {
+ this.templates = map;
+ } else if (map != null) {
+ templates.putAll(map);
+ } else {
+ templates = null;
+ }
+ }
+
+ public void reset() {
+ requestHeaders.clear();
+ response = null;
+ currentBuilder = new UriBuilderImpl(properties).uri(baseURI);
+ templates = null;
+ }
+
+ public ClientState newState(URI currentURI, MultivaluedMap<String, String> headers,
+ MultivaluedMap<String, String> templatesMap, Map<String, Object> props) {
+ final ClientState state;
+ if (isSupportedScheme(currentURI)) {
+ state = new LocalClientState(currentURI, props);
+ } else {
+ state = new LocalClientState(baseURI, currentURI, props);
+ }
+ if (headers != null) {
+ state.setRequestHeaders(headers);
+ }
+ // we need to carry the template parameters forward
+ MultivaluedMap<String, String> newTemplateParams = templates;
+ if (newTemplateParams != null && templatesMap != null) {
+ newTemplateParams.putAll(templatesMap);
+ } else {
+ newTemplateParams = templatesMap;
+ }
+ state.setTemplates(newTemplateParams);
+ return state;
+ }
+
+ public ClientState newState(URI currentURI,
+ MultivaluedMap<String, String> headers,
+ MultivaluedMap<String, String> templatesMap) {
+ return newState(currentURI, headers, templatesMap, properties);
+ }
+
+ private static boolean isSupportedScheme(URI uri) {
+ return !StringUtils.isEmpty(uri.getScheme())
+ && (uri.getScheme().startsWith(HTTP_SCHEME) || uri.getScheme().startsWith(WS_SCHEME));
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/client/WebClient.java b/transform/src/patch/java/org/apache/cxf/jaxrs/client/WebClient.java
new file mode 100644
index 0000000..eaedd4b
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/client/WebClient.java
@@ -0,0 +1,1349 @@
+/**
+ * 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.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.client.AsyncInvoker;
+import javax.ws.rs.client.CompletionStageRxInvoker;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.InvocationCallback;
+import javax.ws.rs.client.RxInvoker;
+import javax.ws.rs.client.RxInvokerProvider;
+import javax.ws.rs.client.SyncInvoker;
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.EntityTag;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.GenericEntity;
+import javax.ws.rs.core.GenericType;
+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.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.bus.spring.SpringBusFactory;
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.classloader.ClassLoaderUtils.ClassLoaderHolder;
+import org.apache.cxf.common.util.ClassHelper;
+import org.apache.cxf.common.util.PropertyUtils;
+import org.apache.cxf.feature.Feature;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.jaxrs.client.spec.ClientImpl.WebTargetImpl;
+import org.apache.cxf.jaxrs.client.spec.InvocationBuilderImpl;
+import org.apache.cxf.jaxrs.impl.ResponseImpl;
+import org.apache.cxf.jaxrs.impl.UriBuilderImpl;
+import org.apache.cxf.jaxrs.model.ParameterType;
+import org.apache.cxf.jaxrs.model.URITemplate;
+import org.apache.cxf.jaxrs.utils.HttpUtils;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.jaxrs.utils.ParameterizedCollectionType;
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.Message;
+
+
+/**
+ * Http-centric web client
+ *
+ */
+public class WebClient extends AbstractClient {
+ private static final String REQUEST_CLASS = "request.class";
+ private static final String REQUEST_TYPE = "request.type";
+ private static final String REQUEST_ANNS = "request.annotations";
+ private static final String RESPONSE_CLASS = "response.class";
+ private static final String RESPONSE_TYPE = "response.type";
+ private static final String WEB_CLIENT_OPERATION_REPORTING = "enable.webclient.operation.reporting";
+ private BodyWriter bodyWriter = new BodyWriter();
+ protected WebClient(String baseAddress) {
+ this(convertStringToURI(baseAddress), Collections.emptyMap());
+ }
+
+ protected WebClient(String baseAddress, Map<String, Object> properties) {
+ this(convertStringToURI(baseAddress), properties);
+ }
+
+ protected WebClient(URI baseURI) {
+ this(baseURI, Collections.emptyMap());
+ }
+
+ protected WebClient(URI baseURI, Map<String, Object> properties) {
+ this(new LocalClientState(baseURI, properties));
+ }
+
+ protected WebClient(ClientState state) {
+ super(state);
+ cfg.getInInterceptors().add(new ClientAsyncResponseInterceptor());
+ }
+
+
+
+
+ /**
+ * Creates WebClient
+ * @param baseAddress baseAddress
+ */
+ public static WebClient create(String baseAddress) {
+ return create(baseAddress, Collections.emptyMap());
+ }
+
+ /**
+ * Creates WebClient
+ * @param baseAddress baseAddress
+ */
+ public static WebClient create(String baseAddress, Map<String, Object> properties) {
+ JAXRSClientFactoryBean bean = new JAXRSClientFactoryBean();
+ bean.setAddress(baseAddress);
+ bean.setProperties(properties);
+ return bean.createWebClient();
+ }
+
+ /**
+ * Creates WebClient
+ * @param baseURI baseURI
+ */
+ public static WebClient create(URI baseURI) {
+ return create(baseURI.toString());
+ }
+
+ /**
+ * Creates WebClient
+ * @param baseURI baseURI
+ */
+ public static WebClient create(String baseURI, boolean threadSafe) {
+ return create(baseURI, Collections.emptyList(), threadSafe);
+ }
+
+ /**
+ * Creates WebClient
+ * @param baseAddress baseURI
+ * @param providers list of providers
+ */
+ public static WebClient create(String baseAddress, List<?> providers) {
+ return create(baseAddress, providers, null);
+ }
+
+ /**
+ * Creates WebClient
+ * @param baseAddress baseURI
+ * @param providers list of providers
+ * @param threadSafe if true ThreadLocalClientState is used
+ */
+ public static WebClient create(String baseAddress, List<?> providers, boolean threadSafe) {
+ return create(baseAddress, providers, Collections.emptyMap(), threadSafe);
+ }
+
+ /**
+ * Creates WebClient
+ * @param baseAddress baseURI
+ * @param providers list of providers
+ * @param threadSafe if true ThreadLocalClientState is used
+ * @param properties additional properties
+ */
+ public static WebClient create(String baseAddress, List<?> providers,
+ Map<String, Object> properties, boolean threadSafe) {
+ JAXRSClientFactoryBean bean = getBean(baseAddress, null);
+ bean.setProviders(providers);
+ bean.setProperties(properties);
+ if (threadSafe) {
+ bean.setInitialState(new ThreadLocalClientState(baseAddress, properties));
+ }
+ return bean.createWebClient();
+ }
+
+ /**
+ * Creates a thread safe WebClient
+ * @param baseAddress baseURI
+ * @param providers list of providers
+ * @param timeToKeepState time to keep this thread safe state.
+ */
+ public static WebClient create(String baseAddress, List<?> providers, long timeToKeepState) {
+ JAXRSClientFactoryBean bean = getBean(baseAddress, null);
+ bean.setProviders(providers);
+ bean.setInitialState(new ThreadLocalClientState(baseAddress, timeToKeepState));
+ return bean.createWebClient();
+ }
+
+ /**
+ * Creates WebClient
+ * @param baseAddress baseAddress
+ * @param providers list of providers
+ * @param configLocation classpath location of the configuration resource, can be null
+ * @return WebClient instance
+ */
+ public static WebClient create(String baseAddress, List<?> providers, String configLocation) {
+ JAXRSClientFactoryBean bean = getBean(baseAddress, configLocation);
+ bean.setProviders(providers);
+ return bean.createWebClient();
+ }
+
+ /**
+ * Creates WebClient with a list of custom features
+ * @param baseAddress baseAddress
+ * @param providers list of providers
+ * @param features the features which will be applied to the client
+ * @param configLocation classpath location of the configuration resource, can be null
+ * @return WebClient instance
+ */
+ public static WebClient create(String baseAddress,
+ List<?> providers,
+ List<? extends Feature> features,
+ String configLocation) {
+ JAXRSClientFactoryBean bean = getBean(baseAddress, configLocation);
+ bean.setProviders(providers);
+ bean.setFeatures(features);
+ return bean.createWebClient();
+ }
+
+ /**
+ * Creates WebClient
+ * @param baseAddress baseAddress
+ * @param configLocation classpath location of the configuration resource, can be null
+ * @return WebClient instance
+ */
+ public static WebClient create(String baseAddress, String configLocation) {
+ JAXRSClientFactoryBean bean = getBean(baseAddress, configLocation);
+
+ return bean.createWebClient();
+ }
+
+ /**
+ * Creates WebClient which will do basic authentication
+ * @param baseAddress baseAddress
+ * @param username username
+ * @param password password
+ * @param configLocation classpath location of the configuration resource, can be null
+ * @return WebClient instance
+ */
+ public static WebClient create(String baseAddress, String username, String password,
+ String configLocation) {
+ JAXRSClientFactoryBean bean = getBean(baseAddress, configLocation);
+
+ bean.setUsername(username);
+ bean.setPassword(password);
+
+ return bean.createWebClient();
+ }
+
+ /**
+ * Creates WebClient which will do basic authentication
+ * @param baseAddress baseAddress
+ * @param providers list of providers
+ * @param username username
+ * @param password password
+ * @param configLocation classpath location of the configuration resource, can be null
+ * @return WebClient instance
+ */
+ public static WebClient create(String baseAddress, List<?> providers,
+ String username, String password, String configLocation) {
+ JAXRSClientFactoryBean bean = getBean(baseAddress, configLocation);
+
+ bean.setUsername(username);
+ bean.setPassword(password);
+ bean.setProviders(providers);
+ return bean.createWebClient();
+ }
+
+ /**
+ * Creates WebClient, baseURI will be set to Client currentURI
+ * @param object existing client object
+ */
+ public static WebClient fromClientObject(Object object) {
+ Client client = client(object);
+ return client == null ? null : fromClient(client, false);
+ }
+
+ /**
+ * Creates WebClient, baseURI will be set to Client currentURI
+ * @param client existing client
+ */
+ public static WebClient fromClient(Client client) {
+ return fromClient(client, false);
+ }
+
+ /**
+ * Creates WebClient, baseURI will be set to Client currentURI
+ * @param client existing client
+ * @param inheritHeaders if existing Client headers can be inherited by new client
+ */
+ public static WebClient fromClient(Client client, boolean inheritHeaders) {
+
+ final WebClient webClient;
+
+ ClientState clientState = getClientState(client);
+ if (clientState == null) {
+ webClient = create(client.getCurrentURI());
+ if (inheritHeaders) {
+ webClient.headers(client.getHeaders());
+ }
+ } else {
+ MultivaluedMap<String, String> headers = inheritHeaders ? client.getHeaders() : null;
+ webClient = new WebClient(clientState.newState(client.getCurrentURI(), headers, null));
+ }
+ copyProperties(webClient, client);
+ return webClient;
+ }
+
+ /**
+ * Converts object to Client
+ * @param object the object
+ * @return Client object converted to Client
+ */
+ public static Client client(Object object) {
+ if (object instanceof Client) {
+ return (Client)object;
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves ClientConfiguration
+ * @param client proxy or http-centric Client
+ * @return underlying ClientConfiguration instance
+ */
+ public static ClientConfiguration getConfig(Object client) {
+ if (client instanceof WebTargetImpl) {
+ client = ((WebTargetImpl)client).getWebClient();
+ } else if (client instanceof InvocationBuilderImpl) {
+ client = ((InvocationBuilderImpl)client).getWebClient();
+ }
+
+ if (client instanceof Client) {
+ if (client instanceof WebClient) {
+ return ((AbstractClient)client).getConfiguration();
+ } else if (client instanceof InvocationHandlerAware) {
+ Object handler = ((InvocationHandlerAware)client).getInvocationHandler();
+ return ((AbstractClient)handler).getConfiguration();
+ }
+ }
+ throw new IllegalArgumentException("Not a valid Client");
+ }
+
+ /**
+ * Does HTTP invocation
+ * @param httpMethod HTTP method
+ * @param body request body, can be null
+ * @return JAXRS Response, entity may hold a string representaion of
+ * error message if client or server error occured
+ */
+ public Response invoke(String httpMethod, Object body) {
+ return doInvoke(httpMethod, body, null, Response.class, Response.class);
+ }
+
+ /**
+ * Does HTTP POST invocation
+ * @param body request body, can be null
+ * @return JAXRS Response
+ */
+ public Response post(Object body) {
+ return invoke(HttpMethod.POST, body);
+ }
+
+ /**
+ * Does HTTP PUT invocation
+ * @param body request body, can be null
+ * @return JAXRS Response
+ */
+ public Response put(Object body) {
+ return invoke(HttpMethod.PUT, body);
+ }
+
+ /**
+ * Does HTTP GET invocation
+ * @return JAXRS Response
+ */
+ public Response get() {
+ return invoke(HttpMethod.GET, null);
+ }
+
+ /**
+ * Does HTTP HEAD invocation
+ * @return JAXRS Response
+ */
+ public Response head() {
+ return invoke(HttpMethod.HEAD, null);
+ }
+
+ /**
+ * Does HTTP OPTIONS invocation
+ * @return JAXRS Response
+ */
+ public Response options() {
+ return invoke(HttpMethod.OPTIONS, null);
+ }
+
+ /**
+ * Does HTTP DELETE invocation
+ * @return JAXRS Response
+ */
+ public Response delete() {
+ return invoke(HttpMethod.DELETE, null);
+ }
+
+ /**
+ * Posts form data
+ * @param values form values
+ * @return JAXRS Response
+ */
+ public Response form(Map<String, List<Object>> values) {
+ type(MediaType.APPLICATION_FORM_URLENCODED);
+ return doInvoke(HttpMethod.POST, values, null, Response.class, Response.class);
+ }
+
+ /**
+ * Posts form data
+ * @param form form values
+ * @return JAXRS Response
+ */
+ public Response form(Form form) {
+ type(MediaType.APPLICATION_FORM_URLENCODED);
+ return doInvoke(HttpMethod.POST, form.asMap(), null, Response.class, Response.class);
+ }
+
+ /**
+ * Does HTTP invocation and returns types response object
+ * @param httpMethod HTTP method
+ * @param body request body, can be null
+ * @param responseType generic response type
+ * @return typed object, can be null. Response status code and headers
+ * can be obtained too, see Client.getResponse()
+ */
+ public <T> T invoke(String httpMethod, Object body, GenericType<T> responseType) {
+ @SuppressWarnings("unchecked")
+ Class<T> responseClass = (Class<T>)responseType.getRawType();
+ Response r = doInvoke(httpMethod, body, null, responseClass, responseType.getType());
+ return castResponse(r, responseClass);
+ }
+
+ /**
+ * Does HTTP invocation and returns types response object
+ * @param httpMethod HTTP method
+ * @param body request body, can be null
+ * @param responseClass expected type of response object
+ * @return typed object, can be null. Response status code and headers
+ * can be obtained too, see Client.getResponse()
+ */
+ public <T> T invoke(String httpMethod, Object body, Class<T> responseClass) {
+ Response r = doInvoke(httpMethod, body, null, responseClass, responseClass);
+ return castResponse(r, responseClass);
+ }
+
+ /**
+ * Does HTTP invocation and returns types response object
+ * @param httpMethod HTTP method
+ * @param body request body, can be null
+ * @param requestClass request body class
+ * @param responseClass expected type of response object
+ * @return typed object, can be null. Response status code and headers
+ * can be obtained too, see Client.getResponse()
+ */
+ public <T> T invoke(String httpMethod, Object body, Class<?> requestClass, Class<T> responseClass) {
+ Response r = doInvoke(httpMethod, body, requestClass, null, responseClass, responseClass);
+ return castResponse(r, responseClass);
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T> T castResponse(Response r, Class<T> responseClass) {
+ return (T)(responseClass == Response.class ? r : r.getEntity());
+ }
+ /**
+ * Does HTTP POST invocation and returns typed response object
+ * @param body request body, can be null
+ * @param responseClass expected type of response object
+ * @return typed object, can be null. Response status code and headers
+ * can be obtained too, see Client.getResponse()
+ */
+ public <T> T post(Object body, Class<T> responseClass) {
+ return invoke(HttpMethod.POST, body, responseClass);
+ }
+
+ /**
+ * Does HTTP POST invocation and returns typed response object
+ * @param body request body, can be null
+ * @param responseType generic response type
+ * @return typed object, can be null. Response status code and headers
+ * can be obtained too, see Client.getResponse()
+ */
+ public <T> T post(Object body, GenericType<T> responseType) {
+ return invoke(HttpMethod.POST, body, responseType);
+ }
+
+ /**
+ * Does HTTP Async POST invocation and returns Future.
+ * Shortcut for async().post(Entity, InvocationCallback)
+ * @param callback invocation callback
+ * @return the future
+ */
+ public <T> Future<T> post(Object body, InvocationCallback<T> callback) {
+ return doInvokeAsyncCallback(HttpMethod.POST, body, body.getClass(), null, callback);
+ }
+
+ /**
+ * Does HTTP PUT invocation and returns typed response object
+ * @param body request body, can be null
+ * @param responseClass expected type of response object
+ * @return typed object, can be null. Response status code and headers
+ * can be obtained too, see Client.getResponse()
+ */
+ public <T> T put(Object body, Class<T> responseClass) {
+ return invoke(HttpMethod.PUT, body, responseClass);
+ }
+
+
+ /**
+ * Does HTTP PUT invocation and returns typed response object
+ * @param body request body, can be null
+ * @param responseType generic response type
+ * @return typed object, can be null. Response status code and headers
+ * can be obtained too, see Client.getResponse()
+ */
+ public <T> T put(Object body, GenericType<T> responseType) {
+ return invoke(HttpMethod.PUT, body, responseType);
+ }
+
+ /**
+ * Does HTTP Async PUT invocation and returns Future.
+ * Shortcut for async().put(Entity, InvocationCallback)
+ * @param callback invocation callback
+ * @return the future
+ */
+ public <T> Future<T> put(Object body, InvocationCallback<T> callback) {
+ return doInvokeAsyncCallback(HttpMethod.PUT, body, body.getClass(), null, callback);
+ }
+
+ /**
+ * Does HTTP invocation and returns a collection of typed objects
+ * @param httpMethod HTTP method
+ * @param body request body, can be null
+ * @param memberClass expected type of collection member class
+ * @return typed collection
+ */
+ public <T> Collection<? extends T> invokeAndGetCollection(String httpMethod, Object body,
+ Class<T> memberClass) {
+ Response r = doInvoke(httpMethod, body, null,
+ Collection.class, new ParameterizedCollectionType(memberClass));
+ return CastUtils.cast((Collection<?>)r.getEntity(), memberClass);
+ }
+
+ /**
+ * Posts a collection of typed objects
+ * @param collection request body
+ * @param memberClass type of collection member class
+ * @return JAX-RS Response
+ */
+ public <T> Response postCollection(Object collection, Class<T> memberClass) {
+ return doInvoke(HttpMethod.POST, collection, new ParameterizedCollectionType(memberClass),
+ Response.class, Response.class);
+ }
+
+ /**
+ * Posts a collection of typed objects
+ * @param collection request body
+ * @param memberClass type of collection member class
+ * @param responseClass expected type of response object
+ * @return JAX-RS Response
+ */
+ public <T1, T2> T2 postCollection(Object collection, Class<T1> memberClass,
+ Class<T2> responseClass) {
+ Response r = doInvoke(HttpMethod.POST, collection, new ParameterizedCollectionType(memberClass),
+ responseClass, responseClass);
+ return responseClass.cast(responseClass == Response.class ? r : r.getEntity());
+ }
+
+ /**
+ * Posts collection of typed objects and returns a collection of typed objects
+ * @param collection request body
+ * @param memberClass type of collection member class
+ * @param responseClass expected type of response object
+ * @return JAX-RS Response
+ */
+ public <T1, T2> Collection<? extends T2> postAndGetCollection(Object collection,
+ Class<T1> memberClass,
+ Class<T2> responseClass) {
+ Response r = doInvoke(HttpMethod.POST, collection, new ParameterizedCollectionType(memberClass),
+ Collection.class, new ParameterizedCollectionType(responseClass));
+ return CastUtils.cast((Collection<?>)r.getEntity(), responseClass);
+ }
+
+ /**
+ * Posts the object and returns a collection of typed objects
+ * @param body request body
+ * @param responseClass expected type of response object
+ * @return JAX-RS Response
+ */
+ public <T> Collection<? extends T> postObjectGetCollection(Object body,
+ Class<T> responseClass) {
+ Response r = doInvoke(HttpMethod.POST, body, null, Collection.class,
+ new ParameterizedCollectionType(responseClass));
+ return CastUtils.cast((Collection<?>)r.getEntity(), responseClass);
+ }
+
+ /**
+ * Posts request body and returns a collection of typed objects
+ * @param body request body, can be null
+ * @param memberClass expected type of collection member class
+ * @return typed collection
+ */
+ public <T> Collection<? extends T> postAndGetCollection(Object body, Class<T> memberClass) {
+ return invokeAndGetCollection(HttpMethod.POST, body, memberClass);
+ }
+
+ /**
+ * Does HTTP GET invocation and returns a collection of typed objects
+ * @param memberClass expected type of collection member class
+ * @return typed collection
+ */
+ public <T> Collection<? extends T> getCollection(Class<T> memberClass) {
+ return invokeAndGetCollection(HttpMethod.GET, null, memberClass);
+ }
+
+ /**
+ * Does HTTP GET invocation and returns typed response object
+ * @param responseClass expected type of response object
+ * @return typed object, can be null. Response status code and headers
+ * can be obtained too, see Client.getResponse()
+ */
+ public <T> T get(Class<T> responseClass) {
+ return invoke(HttpMethod.GET, null, responseClass);
+ }
+
+
+ /**
+ * Does HTTP GET invocation and returns typed response object
+ * @param responseType generic response type
+ * @return typed object, can be null. Response status code and headers
+ * can be obtained too, see Client.getResponse()
+ */
+ public <T> T get(GenericType<T> responseType) {
+ return invoke(HttpMethod.GET, null, responseType);
+ }
+
+ /**
+ * Does HTTP Async GET invocation and returns Future.
+ * Shortcut for async().get(InvocationCallback)
+ * @param callback invocation callback
+ * @return the future
+ */
+ public <T> Future<T> get(InvocationCallback<T> callback) {
+ return doInvokeAsyncCallback(HttpMethod.GET, null, null, null, callback);
+ }
+
+ /**
+ * Updates the current URI path
+ * @param path new relative path segment
+ * @return updated WebClient
+ */
+ public WebClient path(Object path) {
+ getCurrentBuilder().path(convertParamValue(path, null));
+
+ return this;
+ }
+
+ /**
+ * Updates the current URI path with path segment which may contain template variables
+ * @param path new relative path segment
+ * @param values template variable values
+ * @return updated WebClient
+ */
+ public WebClient path(String path, Object... values) {
+ URI u = new UriBuilderImpl().uri(URI.create("http://tempuri")).path(path).buildFromEncoded(values);
+ getState().setTemplates(getTemplateParametersMap(new URITemplate(path), Arrays.asList(values)));
+ return path(u.getRawPath());
+ }
+
+ @Override
+ public WebClient query(String name, Object ...values) {
+ return (WebClient)super.query(name, values);
+ }
+
+ /**
+ * Updates the current URI matrix parameters
+ * @param name matrix name
+ * @param values matrix values
+ * @return updated WebClient
+ */
+ public WebClient matrix(String name, Object ...values) {
+ addMatrixQueryParamsToBuilder(getCurrentBuilder(), name, ParameterType.MATRIX, null, values);
+ return this;
+ }
+
+ /**
+ * Updates the current URI fragment
+ * @param name fragment name
+ * @return updated WebClient
+ */
+ public WebClient fragment(String name) {
+ getCurrentBuilder().fragment(name);
+ return this;
+ }
+
+ /**
+ * Moves WebClient to a new baseURI or forwards to new currentURI
+ * @param newAddress new URI
+ * @param forward if true then currentURI will be based on baseURI
+ * @return updated WebClient
+ */
+ public WebClient to(String newAddress, boolean forward) {
+ getState().setTemplates(null);
+ if (forward) {
+ if (!newAddress.startsWith("/")
+ && !newAddress.startsWith(getBaseURI().toString())) {
+ throw new IllegalArgumentException("Base address can not be preserved");
+ }
+ resetCurrentBuilder(URI.create(newAddress));
+ } else {
+ resetBaseAddress(URI.create(newAddress));
+ }
+ return this;
+ }
+
+ /**
+ * Goes back
+ * @param fast if true then goes back to baseURI otherwise to a previous path segment
+ * @return updated WebClient
+ */
+ public WebClient back(boolean fast) {
+ getState().setTemplates(null);
+ if (fast) {
+ getCurrentBuilder().replacePath(getBaseURI().getPath());
+ } else {
+ URI uri = getCurrentURI();
+ if (uri == getBaseURI()) {
+ return this;
+ }
+ List<PathSegment> segments = JAXRSUtils.getPathSegments(uri.getPath(), false);
+ getCurrentBuilder().replacePath(null);
+ for (int i = 0; i < segments.size() - 1; i++) {
+ getCurrentBuilder().path(HttpUtils.fromPathSegment(segments.get(i)));
+ }
+
+ }
+ return this;
+ }
+
+ /**
+ * Replaces the current path with the new value.
+ * @param path new path value. If it starts from "/" then all the current
+ * path starting from the base URI will be replaced, otherwise only the
+ * last path segment will be replaced. Providing a null value is equivalent
+ * to calling back(true)
+ * @return updated WebClient
+ */
+ public WebClient replacePath(String path) {
+ if (path == null) {
+ return back(true);
+ }
+ back(path.startsWith("/") ? true : false);
+ return path(path);
+ }
+
+ /**
+ * Resets the current query
+ * @return updated WebClient
+ */
+ public WebClient resetQuery() {
+ return replaceQuery(null);
+ }
+
+ /**
+ * Replaces the current query with the new value.
+ * @param queryString the new value, providing a null is
+ * equivalent to calling resetQuery().
+ * @return updated WebClient
+ */
+ public WebClient replaceQuery(String queryString) {
+ getCurrentBuilder().replaceQuery(queryString);
+ return this;
+ }
+
+ /**
+ * Replaces the header value with the new values.
+ * @param headerName headerValues
+ * @param value new values, null is equivalent to removing the header
+ * @return updated WebClient
+ */
+ public WebClient replaceHeader(String headerName, Object value) {
+ MultivaluedMap<String, String> headers = getState().getRequestHeaders();
+ headers.remove(headerName);
+ if (value != null) {
+ super.header(headerName, value);
+ }
+ return this;
+ }
+
+ /**
+ * Replaces the current query with the new value.
+ * @param queryParam query param name
+ * @param value the new value, providing a null is
+ * equivalent to calling resetQuery().
+ * @return updated WebClient
+ */
+ public WebClient replaceQueryParam(String queryParam, Object... value) {
+ getCurrentBuilder().replaceQueryParam(queryParam, value);
+ return this;
+ }
+
+ @Override
+ public WebClient type(MediaType ct) {
+ return (WebClient)super.type(ct);
+ }
+
+ @Override
+ public WebClient type(String type) {
+ return (WebClient)super.type(type);
+ }
+
+ @Override
+ public WebClient accept(MediaType... types) {
+ return (WebClient)super.accept(types);
+ }
+
+ @Override
+ public WebClient accept(String... types) {
+ return (WebClient)super.accept(types);
+ }
+
+ @Override
+ public WebClient language(String language) {
+ return (WebClient)super.language(language);
+ }
+
+ @Override
+ public WebClient acceptLanguage(String ...languages) {
+ return (WebClient)super.acceptLanguage(languages);
+ }
+
+ @Override
+ public WebClient encoding(String encoding) {
+ return (WebClient)super.encoding(encoding);
+ }
+
+ @Override
+ public WebClient acceptEncoding(String ...encodings) {
+ return (WebClient)super.acceptEncoding(encodings);
+ }
+
+ @Override
+ public WebClient match(EntityTag tag, boolean ifNot) {
+ return (WebClient)super.match(tag, ifNot);
+ }
+
+ @Override
+ public WebClient modified(Date date, boolean ifNot) {
+ return (WebClient)super.modified(date, ifNot);
+ }
+
+ @Override
+ public WebClient cookie(Cookie cookie) {
+ return (WebClient)super.cookie(cookie);
+ }
+
+ @Override
+ public WebClient authorization(Object auth) {
+ return (WebClient)super.authorization(auth);
+ }
+
+ @Override
+ public WebClient header(String name, Object... values) {
+ return (WebClient)super.header(name, values);
+ }
+
+ @Override
+ public WebClient headers(MultivaluedMap<String, String> map) {
+ return (WebClient)super.headers(map);
+ }
+
+ @Override
+ public WebClient reset() {
+ //clearTemplates();
+ return (WebClient)super.reset();
+ }
+
+ protected Response doInvoke(String httpMethod,
+ Object body,
+ Type inGenericType,
+ Class<?> responseClass,
+ Type outGenericType) {
+ return doInvoke(httpMethod, body, body == null ? null : body.getClass(), inGenericType,
+ responseClass, outGenericType);
+ }
+
+
+
+ protected Response doInvoke(String httpMethod,
+ Object body,
+ Class<?> requestClass,
+ Type inGenericType,
+ Class<?> responseClass,
+ Type outGenericType) {
+ Annotation[] inAnns = null;
+ if (body instanceof Entity) {
+ Entity<?> entity = (Entity<?>)body;
+ setEntityHeaders(entity);
+ body = entity.getEntity();
+ requestClass = body.getClass();
+ inGenericType = body.getClass();
+ inAnns = entity.getAnnotations();
+ }
+ if (body instanceof GenericEntity) {
+ GenericEntity<?> genericEntity = (GenericEntity<?>)body;
+ body = genericEntity.getEntity();
+ requestClass = genericEntity.getRawType();
+ inGenericType = genericEntity.getType();
+ }
+ MultivaluedMap<String, String> headers = prepareHeaders(responseClass, body);
+ resetResponse();
+ final Response r;
+ try {
+ r = doChainedInvocation(httpMethod, headers, body, requestClass, inGenericType,
+ inAnns, responseClass, outGenericType, null, null);
+ } finally {
+ resetResponseStateImmediatelyIfNeeded();
+ }
+
+ int status = r.getStatus();
+ if (status != 304 && status >= 300 && responseClass != Response.class) {
+ throw convertToWebApplicationException(r);
+ }
+ return r;
+ }
+
+ protected <T> Future<T> doInvokeAsyncCallback(String httpMethod,
+ Object body,
+ Class<?> requestClass,
+ Type inType,
+ InvocationCallback<T> callback) {
+
+ Type outType = getCallbackType(callback);
+ Class<?> respClass = getCallbackClass(outType);
+ return doInvokeAsync(httpMethod, body, requestClass, inType, respClass, outType, callback);
+ }
+
+ protected <T> Future<T> doInvokeAsync(String httpMethod,
+ Object body,
+ Class<?> requestClass,
+ Type inType,
+ Class<?> respClass,
+ Type outType,
+ InvocationCallback<T> callback) {
+ JaxrsClientCallback<T> cb = new JaxrsClientCallback<>(callback, respClass, outType);
+ prepareAsyncClient(httpMethod, body, requestClass, inType, respClass, outType, cb);
+ return cb.createFuture();
+ }
+
+ protected void prepareAsyncClient(String httpMethod,
+ Object body,
+ Class<?> requestClass,
+ Type inType,
+ Class<?> respClass,
+ Type outType,
+ JaxrsClientCallback<?> cb) {
+ Annotation[] inAnns = null;
+ if (body instanceof Entity) {
+ Entity<?> entity = (Entity<?>)body;
+ setEntityHeaders(entity);
+ body = entity.getEntity();
+ requestClass = body.getClass();
+ inType = body.getClass();
+ inAnns = entity.getAnnotations();
+ }
+ if (body instanceof GenericEntity) {
+ GenericEntity<?> genericEntity = (GenericEntity<?>)body;
+ body = genericEntity.getEntity();
+ requestClass = genericEntity.getRawType();
+ inType = genericEntity.getType();
+ }
+
+ MultivaluedMap<String, String> headers = prepareHeaders(respClass, body);
+ resetResponse();
+
+ Message m = finalizeMessage(httpMethod, headers, body, requestClass, inType,
+ inAnns, respClass, outType, null, null);
+
+ m.getExchange().setSynchronous(false);
+ setAsyncMessageObserverIfNeeded(m.getExchange());
+ m.getExchange().put(JaxrsClientCallback.class, cb);
+
+ doRunInterceptorChain(m);
+ }
+
+
+ private MultivaluedMap<String, String> prepareHeaders(Class<?> responseClass, Object body) {
+ MultivaluedMap<String, String> headers = getHeaders();
+ if (headers.getFirst(HttpHeaders.CONTENT_TYPE) == null && body instanceof Form) {
+ headers.putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED);
+ }
+
+ if (responseClass != null && responseClass != Response.class
+ && headers.getFirst(HttpHeaders.ACCEPT) == null) {
+ headers.putSingle(HttpHeaders.ACCEPT, MediaType.WILDCARD);
+ }
+ return headers;
+ }
+
+ class ClientAsyncResponseInterceptor extends AbstractClientAsyncResponseInterceptor {
+ @Override
+ protected void doHandleAsyncResponse(Message message, Response r, JaxrsClientCallback<?> cb) {
+ if (r == null) {
+ try {
+ r = handleResponse(message.getExchange().getOutMessage(),
+ cb.getResponseClass(),
+ cb.getOutGenericType());
+ } catch (Throwable t) {
+ cb.handleException(message, t);
+ return;
+ } finally {
+ completeExchange(message.getExchange(), false);
+ }
+ }
+ if (cb.getResponseClass() == null || Response.class.equals(cb.getResponseClass())) {
+ cb.handleResponse(message, new Object[] {r});
+ } else if (r.getStatus() >= 300) {
+ cb.handleException(message, convertToWebApplicationException(r));
+ } else {
+ cb.handleResponse(message, new Object[] {r.getEntity()});
+ closeAsyncResponseIfPossible(r, message, cb);
+ }
+ }
+ }
+
+
+
+ //TODO: retry invocation will not work in case of async request failures for the moment
+ @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));
+ String httpMethod = (String)reqContext.get(Message.HTTP_REQUEST_METHOD);
+ Class<?> requestClass = (Class<?>)reqContext.get(REQUEST_CLASS);
+ Type inType = (Type)reqContext.get(REQUEST_TYPE);
+ Annotation[] inAnns = (Annotation[])reqContext.get(REQUEST_ANNS);
+ Class<?> respClass = (Class<?>)reqContext.get(RESPONSE_CLASS);
+ Type outType = (Type)reqContext.get(RESPONSE_TYPE);
+ return doChainedInvocation(httpMethod, headers, body, requestClass, inType,
+ inAnns, respClass, outType, exchange, invContext);
+ }
+ //CHECKSTYLE:OFF
+ protected Response doChainedInvocation(String httpMethod, //NOPMD
+ MultivaluedMap<String, String> headers,
+ Object body,
+ Class<?> requestClass,
+ Type inType,
+ Annotation[] inAnns,
+ Class<?> respClass,
+ Type outType,
+ Exchange exchange,
+ Map<String, Object> invContext) {
+ //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 m = finalizeMessage(httpMethod, headers, body, requestClass, inType,
+ inAnns, respClass, outType, exchange, invContext);
+ doRunInterceptorChain(m);
+ return doResponse(m, respClass, outType);
+ } finally {
+ if (origLoader != null) {
+ origLoader.reset();
+ }
+ if (origBus != configuredBus) {
+ BusFactory.setThreadDefaultBus(origBus);
+ }
+ }
+ }
+
+ //CHECKSTYLE:OFF
+ private Message finalizeMessage(String httpMethod, //NOPMD
+ MultivaluedMap<String, String> headers,
+ Object body,
+ Class<?> requestClass,
+ Type inGenericType,
+ Annotation[] inAnns,
+ Class<?> responseClass,
+ Type outGenericType,
+ Exchange exchange,
+ Map<String, Object> invContext) {
+ //CHECKSTYLE:ON
+ URI uri = getCurrentURI();
+ Message m = createMessage(body, httpMethod, headers, uri, exchange,
+ invContext, false);
+ setSupportOnewayResponseProperty(m);
+ if (inAnns != null) {
+ m.put(Annotation.class.getName(), inAnns);
+ }
+ Map<String, Object> reqContext = getRequestContext(m);
+ reqContext.put(Message.HTTP_REQUEST_METHOD, httpMethod);
+ reqContext.put(REQUEST_CLASS, requestClass);
+ reqContext.put(REQUEST_TYPE, inGenericType);
+ reqContext.put(REQUEST_ANNS, inAnns);
+ reqContext.put(RESPONSE_CLASS, responseClass);
+ reqContext.put(RESPONSE_TYPE, outGenericType);
+
+ if (body != null) {
+ m.put(Type.class, inGenericType);
+ }
+ m.getInterceptorChain().add(bodyWriter);
+
+ setWebClientOperationProperty(m, httpMethod);
+
+ return m;
+ }
+
+ private void setWebClientOperationProperty(Message m, String httpMethod) {
+ Object prop = m.getContextualProperty(WEB_CLIENT_OPERATION_REPORTING);
+ // Enable the operation reporting by default
+ if (prop == null || PropertyUtils.isTrue(prop)) {
+ UriBuilder absPathUri = super.getCurrentBuilder().clone();
+ absPathUri.replaceQuery(null);
+ setPlainOperationNameProperty(m, httpMethod + ":" + absPathUri.build().toString());
+ }
+
+ }
+
+ protected Response doResponse(Message m,
+ Class<?> responseClass,
+ Type outGenericType) {
+ try {
+ Object[] results = preProcessResult(m);
+ if (results != null && results.length == 1) {
+ return (Response)results[0];
+ }
+ } catch (WebApplicationException | ProcessingException ex) {
+ throw ex;
+ } catch (Exception ex) {
+ throw new ProcessingException(ex);
+ }
+
+ try {
+ return handleResponse(m, responseClass, outGenericType);
+ } finally {
+ completeExchange(m.getExchange(), false);
+ }
+ }
+
+ protected Response handleResponse(Message outMessage, Class<?> responseClass, Type genericType) {
+ try {
+ ResponseBuilder rb = setResponseBuilder(outMessage, outMessage.getExchange());
+ Response currentResponse = rb.clone().build();
+ ((ResponseImpl)currentResponse).setOutMessage(outMessage);
+
+ Object entity = readBody(currentResponse, outMessage, responseClass, genericType,
+ new Annotation[]{});
+
+ if (entity == null) {
+ int status = currentResponse.getStatus();
+ if (status >= 400) {
+ entity = currentResponse.getEntity();
+ }
+ }
+ rb = JAXRSUtils.fromResponse(currentResponse, false);
+
+ rb.entity(entity instanceof Response
+ ? ((Response)entity).getEntity() : entity);
+
+ Response r = rb.build();
+ getState().setResponse(r);
+ ((ResponseImpl)r).setOutMessage(outMessage);
+ return r;
+ } catch (ProcessingException ex) {
+ throw ex;
+ } catch (Throwable ex) {
+ throw new ProcessingException(ex);
+ } finally {
+ ClientProviderFactory.getInstance(outMessage).clearThreadLocalProxies();
+ }
+ }
+
+
+ private class BodyWriter extends AbstractBodyWriter {
+
+ protected void doWriteBody(Message outMessage,
+ Object body,
+ Type bodyType,
+ Annotation[] customAnns,
+ OutputStream os) throws Fault {
+
+ Map<String, Object> requestContext = WebClient.this.getRequestContext(outMessage);
+ Class<?> requestClass = null;
+ Type requestType = null;
+ if (requestContext != null) {
+ requestClass = (Class<?>)requestContext.get(REQUEST_CLASS);
+ requestType = (Type)requestContext.get(REQUEST_TYPE);
+ }
+ if (bodyType != null) {
+ requestType = bodyType;
+ }
+
+ Annotation[] anns = customAnns != null ? customAnns : new Annotation[]{};
+ boolean isAssignable = requestClass != null && requestClass.isAssignableFrom(body.getClass());
+ try {
+ writeBody(body, outMessage,
+ requestClass == null || !isAssignable ? body.getClass() : requestClass,
+ requestType == null || !isAssignable ? body.getClass() : requestType,
+ anns, os);
+ } catch (Exception ex) {
+ throw new Fault(ex);
+ }
+ }
+ }
+
+ static void copyProperties(Client toClient, Client fromClient) {
+ AbstractClient newClient = toAbstractClient(toClient);
+ AbstractClient oldClient = toAbstractClient(fromClient);
+ newClient.setConfiguration(oldClient.getConfiguration());
+ }
+
+ private static AbstractClient toAbstractClient(Object client) {
+
+ if (client instanceof AbstractClient) {
+ return (AbstractClient)client;
+ } else if (client instanceof InvocationHandlerAware) {
+ return (AbstractClient)((InvocationHandlerAware)client).getInvocationHandler();
+ } else {
+ Object realObject = ClassHelper.getRealObject(client);
+ if (realObject instanceof AbstractClient) {
+ return (AbstractClient)realObject;
+ }
+ }
+ return null;
+ }
+
+ static JAXRSClientFactoryBean getBean(String baseAddress, String configLocation) {
+ JAXRSClientFactoryBean bean = new JAXRSClientFactoryBean();
+
+ if (configLocation != null) {
+ SpringBusFactory bf = new SpringBusFactory();
+ Bus bus = bf.createBus(configLocation);
+ bean.setBus(bus);
+ }
+ bean.setAddress(baseAddress);
+ return bean;
+ }
+
+ static ClientState getClientState(Client client) {
+ AbstractClient newClient = toAbstractClient(client);
+ if (newClient == null) {
+ return null;
+ }
+ return newClient.getState();
+ }
+
+ static URI convertStringToURI(String baseAddress) {
+ try {
+ return URI.create(baseAddress);
+ } catch (RuntimeException ex) {
+ // no need to check "https" scheme or indeed ':'
+ // as the relative address will not work as the base address
+ if (baseAddress.startsWith(HTTP_SCHEME)) {
+ return new UriBuilderImpl().uriAsTemplate(baseAddress).build();
+ }
+ throw ex;
+ }
+ }
+
+ // Link to JAX-RS 2.0 AsyncInvoker
+ public AsyncInvoker async() {
+ return new AsyncInvokerImpl(this);
+ }
+
+ // Link to JAX-RS 2.0 SyncInvoker
+ public SyncInvoker sync() {
+ return new SyncInvokerImpl(this);
+ }
+
+ // Link to JAX-RS 2.1 CompletionStageRxInvoker
+ public CompletionStageRxInvoker rx() {
+ return rx(lookUpExecutorService());
+ }
+
+ public CompletionStageRxInvoker rx(ExecutorService ex) {
+ return new CompletionStageRxInvokerImpl(this, ex);
+ }
+ // Link to JAX-RS 2.1 RxInvoker extensions
+ @SuppressWarnings("rawtypes")
+ public <T extends RxInvoker> T rx(Class<T> rxCls) {
+ return rx(rxCls, (ExecutorService)null);
+ }
+
+ @SuppressWarnings({
+ "rawtypes", "unchecked"
+ })
+ public <T extends RxInvoker> T rx(Class<T> rxCls, ExecutorService executorService) {
+ if (CompletionStageRxInvoker.class.isAssignableFrom(rxCls)) {
+ return (T)rx(executorService);
+ }
+ ClientProviderFactory pf =
+ ClientProviderFactory.getInstance(WebClient.getConfig(this).getEndpoint());
+ RxInvokerProvider rxProvider = pf.getRxInvokerProvider();
+ if (rxProvider != null && rxProvider.isProviderFor(rxCls)) {
+ return (T)rxProvider.getRxInvoker(sync(), executorService);
+ }
+ throw new IllegalStateException("Provider for " + rxCls.getName() + " is not available");
+ }
+
+ private void setEntityHeaders(Entity<?> entity) {
+ type(entity.getMediaType());
+ if (entity.getLanguage() != null) {
+ language(entity.getLanguage().toString());
+ }
+ if (entity.getEncoding() != null) {
+ encoding(entity.getEncoding());
+ }
+ }
+
+ private ExecutorService lookUpExecutorService() {
+ try {
+ javax.naming.InitialContext ic = new javax.naming.InitialContext();
+ Object execService = ic.lookup("java:comp/DefaultManagedExecutorService");
+ if (execService != null) {
+ return (ExecutorService)execService;
+ }
+ } catch (Throwable ex) {
+ // ignore
+ }
+ return null;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/client/spec/ClientRequestContextImpl.java b/transform/src/patch/java/org/apache/cxf/jaxrs/client/spec/ClientRequestContextImpl.java
new file mode 100644
index 0000000..cddf5c6
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/client/spec/ClientRequestContextImpl.java
@@ -0,0 +1,186 @@
+/**
+ * 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.spec;
+
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.util.List;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.GenericEntity;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.apache.cxf.jaxrs.client.ClientProviderFactory;
+import org.apache.cxf.jaxrs.impl.AbstractRequestContextImpl;
+import org.apache.cxf.jaxrs.utils.HttpUtils;
+import org.apache.cxf.jaxrs.utils.InjectionUtils;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageContentsList;
+
+
+public class ClientRequestContextImpl extends AbstractRequestContextImpl
+ implements ClientRequestContext {
+
+ public ClientRequestContextImpl(Message m,
+ boolean responseContext) {
+ super(m, responseContext);
+ }
+
+ @Override
+ public MediaType getMediaType() {
+ if (!hasEntity()) {
+ return null;
+ }
+ Object mt = HttpUtils.getModifiableHeaders(m).getFirst(HttpHeaders.CONTENT_TYPE);
+ return mt instanceof MediaType ? (MediaType)mt : JAXRSUtils.toMediaType(mt.toString());
+ }
+
+ @Override
+ public Client getClient() {
+ return (Client)m.getContextualProperty(Client.class.getName());
+ }
+
+ @Override
+ public Configuration getConfiguration() {
+ ClientProviderFactory cpf = ClientProviderFactory.getInstance(m);
+ return cpf.getConfiguration(m);
+ }
+
+ private Object getMessageContent() {
+ MessageContentsList objs = MessageContentsList.getContentsList(m);
+ if (objs == null || objs.isEmpty()) {
+ return null;
+ }
+ return objs.get(0);
+ }
+
+ @Override
+ public Object getEntity() {
+ return getMessageContent();
+ }
+
+ @Override
+ public Annotation[] getEntityAnnotations() {
+ Annotation[] anns = (Annotation[])m.get(Annotation.class.getName());
+ return anns == null ? new Annotation[] {} : anns;
+ }
+
+ @Override
+ public Class<?> getEntityClass() {
+ Object entity = getEntity();
+ return entity == null ? null : entity.getClass();
+ }
+
+ @Override
+ public Type getEntityType() {
+ Type t = m.get(Type.class);
+ return t != null ? t : getEntityClass();
+ }
+
+ @Override
+ public OutputStream getEntityStream() {
+ return m.getContent(OutputStream.class);
+ }
+
+ @Override
+ public boolean hasEntity() {
+ return getEntity() != null;
+ }
+
+ @Override
+ public void setEntity(Object entity, Annotation[] anns, MediaType mt) {
+ if (mt != null) {
+ MultivaluedMap<String, Object> headers = getHeaders();
+ headers.putSingle(HttpHeaders.CONTENT_TYPE, mt);
+ m.put(Message.CONTENT_TYPE, mt.toString());
+ }
+ if (anns != null) {
+ m.put(Annotation.class.getName(), anns);
+ }
+ doSetEntity(entity);
+ }
+
+ @Override
+ public void setEntity(Object entity) {
+ doSetEntity(entity);
+ }
+
+ private void doSetEntity(Object entity) {
+ Object actualEntity = InjectionUtils.getEntity(entity);
+ m.setContent(List.class, actualEntity == null ? new MessageContentsList()
+ : new MessageContentsList(actualEntity));
+ if (entity != null) {
+ final Type type;
+ if (GenericEntity.class.isAssignableFrom(entity.getClass())) {
+ type = ((GenericEntity<?>)entity).getType();
+ } else {
+ type = entity.getClass();
+ }
+ m.put(Type.class, type);
+ m.remove("org.apache.cxf.empty.request");
+ }
+
+ }
+
+ @Override
+ public URI getUri() {
+ String requestURI = (String)m.get(Message.REQUEST_URI);
+ if (requestURI == null) {
+ return null;
+ }
+ if (requestURI.startsWith("/")) {
+ String endpointAddress = (String)m.get(Message.ENDPOINT_ADDRESS);
+ requestURI = requestURI.length() == 1 ? endpointAddress : endpointAddress + requestURI;
+ }
+ return URI.create(requestURI);
+ }
+
+ @Override
+ public void setEntityStream(OutputStream os) {
+ m.setContent(OutputStream.class, os);
+
+ }
+
+ @Override
+ public void setUri(URI requestURI) {
+ m.put(Message.ENDPOINT_ADDRESS, requestURI.toString());
+ m.put(Message.REQUEST_URI, requestURI.toString());
+
+ }
+
+ @Override
+ public MultivaluedMap<String, Object> getHeaders() {
+ h = null;
+ return HttpUtils.getModifiableHeaders(m);
+ }
+
+ @Override
+ public MultivaluedMap<String, String> getStringHeaders() {
+ h = null;
+ return HttpUtils.getModifiableStringHeaders(m);
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/jaxrs/client/spring/JAXRSClientFactoryBeanDefinitionParser.java b/transform/src/patch/java/org/apache/cxf/jaxrs/client/spring/JAXRSClientFactoryBeanDefinitionParser.java
new file mode 100644
index 0000000..6543ddc
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/jaxrs/client/spring/JAXRSClientFactoryBeanDefinitionParser.java
@@ -0,0 +1,187 @@
+/**
+ * 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.spring;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.Path;
+import javax.ws.rs.ext.Provider;
+import javax.xml.namespace.QName;
+
+import org.w3c.dom.Element;
+
+import org.apache.cxf.bus.spring.BusWiringBeanFactoryPostProcessor;
+import org.apache.cxf.common.util.ClasspathScanner;
+import org.apache.cxf.configuration.spring.AbstractFactoryBeanDefinitionParser;
+import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
+import org.apache.cxf.jaxrs.model.UserResource;
+import org.apache.cxf.jaxrs.utils.ResourceUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.beans.factory.BeanDefinitionStoreException;
+import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+
+
+
+
+public class JAXRSClientFactoryBeanDefinitionParser extends AbstractFactoryBeanDefinitionParser {
+
+ public JAXRSClientFactoryBeanDefinitionParser() {
+ super();
+ setBeanClass(Object.class);
+ }
+
+ @Override
+ protected Class<?> getFactoryClass() {
+ return JAXRSSpringClientFactoryBean.class;
+ }
+
+ @Override
+ protected String getFactoryIdSuffix() {
+ return ".proxyFactory";
+ }
+
+ @Override
+ protected String getSuffix() {
+ return ".jaxrs-client";
+ }
+
+ @Override
+ protected void mapAttribute(BeanDefinitionBuilder bean, Element e, String name, String val) {
+ if ("serviceName".equals(name)) {
+ QName q = parseQName(e, val);
+ bean.addPropertyValue(name, q);
+ } else if ("basePackages".equals(name)) {
+ bean.addPropertyValue("basePackages", ClasspathScanner.parsePackages(val));
+ } else {
+ mapToProperty(bean, name, val);
+ }
+ }
+
+ @Override
+ protected void mapElement(ParserContext ctx, BeanDefinitionBuilder bean, Element el, String name) {
+ if ("properties".equals(name) || "headers".equals(name)) {
+ Map<?, ?> map = ctx.getDelegate().parseMapElement(el, bean.getBeanDefinition());
+ bean.addPropertyValue(name, map);
+ } else if ("executor".equals(name)) {
+ setFirstChildAsProperty(el, ctx, bean, "serviceFactory.executor");
+ } else if ("binding".equals(name)) {
+ setFirstChildAsProperty(el, ctx, bean, "bindingConfig");
+ } else if ("inInterceptors".equals(name) || "inFaultInterceptors".equals(name)
+ || "outInterceptors".equals(name) || "outFaultInterceptors".equals(name)) {
+ List<?> list = ctx.getDelegate().parseListElement(el, bean.getBeanDefinition());
+ bean.addPropertyValue(name, list);
+ } else if ("features".equals(name) || "providers".equals(name)
+ || "schemaLocations".equals(name) || "modelBeans".equals(name)) {
+ List<?> list = ctx.getDelegate().parseListElement(el, bean.getBeanDefinition());
+ bean.addPropertyValue(name, list);
+ } else if ("model".equals(name)) {
+ List<UserResource> resources = ResourceUtils.getResourcesFromElement(el);
+ bean.addPropertyValue("modelBeans", resources);
+ } else {
+ setFirstChildAsProperty(el, ctx, bean, name);
+ }
+ }
+
+ public static class JAXRSSpringClientFactoryBean extends JAXRSClientFactoryBean
+ implements ApplicationContextAware {
+
+ private List<String> basePackages;
+
+ public JAXRSSpringClientFactoryBean() {
+ super();
+ }
+
+ public void setBasePackages(List<String> basePackages) {
+ this.basePackages = basePackages;
+ }
+
+ public void setApplicationContext(ApplicationContext ctx) throws BeansException {
+ try {
+ if (basePackages != null) {
+ final Map< Class< ? extends Annotation >, Collection< Class< ? > > > classes =
+ ClasspathScanner.findClasses(basePackages, Path.class, Provider.class);
+
+ if (classes.get(Path.class).size() > 1) {
+ throw new NoUniqueBeanDefinitionException(Path.class, classes.get(Path.class).size(),
+ "More than one service class (@Path) has been discovered");
+ }
+ AutowireCapableBeanFactory beanFactory = ctx.getAutowireCapableBeanFactory();
+ for (final Class< ? > providerClass: classes.get(Provider.class)) {
+ Object bean;
+ try {
+ bean = beanFactory.createBean(providerClass,
+ AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
+ } catch (Exception ex) {
+ bean = beanFactory.createBean(providerClass);
+ }
+ setProvider(bean);
+ }
+
+ for (final Class< ? > serviceClass: classes.get(Path.class)) {
+ setServiceClass(serviceClass);
+ }
+ }
+ } catch (IOException ex) {
+ throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
+ } catch (ClassNotFoundException ex) {
+ throw new BeanCreationException("Failed to create bean from classfile", ex);
+ }
+
+ if (bus == null) {
+ setBus(BusWiringBeanFactoryPostProcessor.addDefaultBus(ctx));
+ }
+ }
+ }
+
+ static Class<?> getServiceClass(Collection<Class<?>> rootClasses) {
+ for (Class<?> cls : rootClasses) {
+ if (cls.isInterface()) {
+ return cls;
+ }
+ }
+ return rootClasses.iterator().next();
+ }
+ static List<Object> getProviders(ApplicationContext context, Collection<Class<?>> providerClasses) {
+ List<Object> providers = new LinkedList<>();
+ AutowireCapableBeanFactory beanFactory = context.getAutowireCapableBeanFactory();
+ for (final Class< ? > providerClass: providerClasses) {
+ Object bean;
+ try {
+ bean = beanFactory.createBean(providerClass,
+ AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
+ } catch (Exception ex) {
+ bean = beanFactory.createBean(providerClass);
+ }
+ providers.add(bean);
+ }
+ return providers;
+ }
+}