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