001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.camel.component.cxf.jaxrs;
019    
020    import java.lang.reflect.Method;
021    import java.lang.reflect.ParameterizedType;
022    import java.lang.reflect.Type;
023    import java.util.Collection;
024    import java.util.List;
025    import java.util.Map;
026    
027    import javax.ws.rs.core.Response;
028    
029    import org.apache.camel.CamelException;
030    import org.apache.camel.Exchange;
031    import org.apache.camel.Message;
032    import org.apache.camel.component.cxf.CxfConstants;
033    import org.apache.camel.impl.DefaultProducer;
034    import org.apache.commons.logging.Log;
035    import org.apache.commons.logging.LogFactory;
036    import org.apache.cxf.jaxrs.JAXRSServiceFactoryBean;
037    import org.apache.cxf.jaxrs.client.Client;
038    import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
039    import org.apache.cxf.jaxrs.client.WebClient;
040    
041    
042    /**
043     * CxfRsProducer binds a Camel exchange to a CXF exchange, acts as a CXF 
044     * JAXRS client, it will turn the normal Object invocation to a RESTful request
045     * according to resource annotation.  Any response will be bound to Camel exchange. 
046     */
047    public class CxfRsProducer extends DefaultProducer {
048        
049        private static final Log LOG = LogFactory.getLog(CxfRsProducer.class);
050    
051        JAXRSClientFactoryBean cfb;
052    
053        public CxfRsProducer(CxfRsEndpoint endpoint) {
054            super(endpoint);
055            cfb = endpoint.createJAXRSClientFactoryBean();
056        }
057    
058        public void process(Exchange exchange) throws Exception {
059            
060            if (LOG.isTraceEnabled()) {
061                LOG.trace("Process exchange: " + exchange);
062            }
063            
064            Message inMessage = exchange.getIn();
065            Boolean httpClientAPI = inMessage.getHeader(CxfConstants.CAMEL_CXF_RS_USING_HTTP_API, Boolean.class);
066            // set the value with endpoint's option
067            if (httpClientAPI == null) {
068                httpClientAPI = ((CxfRsEndpoint)getEndpoint()).isHttpClientAPI();
069            }
070            if (httpClientAPI.booleanValue()) {
071                invokeHttpClient(exchange);
072            } else {
073                invokeProxyClient(exchange);            
074            }
075            
076        }
077        
078        @SuppressWarnings("unchecked")
079        protected void invokeHttpClient(Exchange exchange) throws Exception {
080            Message inMessage = exchange.getIn();       
081            WebClient client = cfb.createWebClient();        
082            String httpMethod = inMessage.getHeader(Exchange.HTTP_METHOD, String.class);
083            Class responseClass = inMessage.getHeader(CxfConstants.CAMEL_CXF_RS_RESPONSE_CLASS, Class.class);
084            Type genericType = inMessage.getHeader(CxfConstants.CAMEL_CXF_RS_RESPONSE_GENERIC_TYPE, Type.class);
085            String path = inMessage.getHeader(Exchange.HTTP_PATH, String.class);
086           
087            if (LOG.isTraceEnabled()) {
088                LOG.trace("HTTP method = " + httpMethod);
089                LOG.trace("path = " + path);
090                LOG.trace("responseClass = " + responseClass);
091            }
092            
093            // set the path
094            if (path != null) {
095                client.path(path);
096            }
097            
098            CxfRsEndpoint cxfRsEndpoint = (CxfRsEndpoint)getEndpoint();
099            // check if there is a query map in the message header
100            Map<String, String> maps = inMessage.getHeader(CxfConstants.CAMEL_CXF_RS_QUERY_MAP, Map.class);
101            if (maps == null) {            
102                maps = cxfRsEndpoint.getParameters();
103            }
104            if (maps != null) {
105                for (Map.Entry<String, String> entry : maps.entrySet()) {
106                    client.query(entry.getKey(), entry.getValue());
107                }            
108            }
109            
110            CxfRsBinding binding = cxfRsEndpoint.getBinding();
111    
112            // set the body
113            Object body = null;
114            if (!"GET".equals(httpMethod)) {
115                // need to check the request object.           
116                body = binding.bindCamelMessageBodyToRequestBody(inMessage, exchange);
117                if (LOG.isTraceEnabled()) {
118                    LOG.trace("Request body = " + body);
119                }
120            }
121            
122            // set headers
123            client.headers(binding.bindCamelHeadersToRequestHeaders(inMessage.getHeaders(),
124                                                                    exchange));
125            
126            // invoke the client
127            Object response = null;
128            if (responseClass == null || Response.class.equals(responseClass)) {
129                response = client.invoke(httpMethod, body);
130            } else if (Collection.class.isAssignableFrom(responseClass)) {
131                if (genericType instanceof ParameterizedType) {
132                    // Get the collection member type first
133                    Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
134                    response = client.invokeAndGetCollection(httpMethod, body, (Class)actualTypeArguments[0]);
135                } else {
136                    throw new CamelException("Can't find the Collection member type");
137                }
138            } else {
139                response = client.invoke(httpMethod, body, responseClass);
140            }
141            
142            // set response
143            if (exchange.getPattern().isOutCapable()) {     
144                if (LOG.isTraceEnabled()) {
145                    LOG.trace("Response body = " + response);
146                }            
147                exchange.getOut().setBody(binding.bindResponseToCamelBody(response, exchange));
148                exchange.getOut().setHeaders(binding.bindResponseHeadersToCamelHeaders(response, exchange));
149            }
150        }
151    
152        protected void invokeProxyClient(Exchange exchange) throws Exception {
153            Message inMessage = exchange.getIn();
154            Object[] varValues = inMessage.getHeader(CxfConstants.CAMEL_CXF_RS_VAR_VALUES, Object[].class);
155            String methodName = inMessage.getHeader(CxfConstants.OPERATION_NAME, String.class);
156            Client target = null;
157            if (varValues == null) {
158                target = cfb.create();
159            } else {
160                target = cfb.createWithValues(varValues);
161            }    
162            // find out the method which we want to invoke
163            JAXRSServiceFactoryBean sfb = cfb.getServiceFactory();
164            sfb.getResourceClasses();
165            Object[] parameters = inMessage.getBody(Object[].class);
166            // get the method
167            Method method = findRightMethod(sfb.getResourceClasses(), methodName, getParameterTypes(parameters));
168            // Will send out the message to
169            // Need to deal with the sub resource class
170            Object response = method.invoke(target, parameters);
171            if (exchange.getPattern().isOutCapable()) {
172                exchange.getOut().setBody(response);
173            }
174        }
175    
176        private Method findRightMethod(List<Class<?>> resourceClasses, String methodName, Class[] parameterTypes) throws NoSuchMethodException {        
177            Method answer = null;
178            for (Class<?> clazz : resourceClasses) {
179                try {
180                    answer = clazz.getMethod(methodName, parameterTypes);
181                } catch (NoSuchMethodException ex) {
182                    // keep looking 
183                } catch (SecurityException ex) {
184                    // keep looking
185                }
186                if (answer != null) {
187                    return answer;
188                }
189            }
190            throw new NoSuchMethodException("Can find the method " + methodName 
191                + "withe these parameter " + arrayToString(parameterTypes));
192        }
193        
194        
195        private Class<?>[] getParameterTypes(Object[] objects) {
196            Class<?>[] answer = new Class[objects.length];
197            int i = 0;
198            for (Object obj : objects) {
199                answer[i] = obj.getClass();
200                i++;
201            }
202            return answer;
203        }
204        
205        private String arrayToString(Object[] array) {
206            StringBuilder buffer = new StringBuilder("[");
207            for (Object obj : array) {
208                if (buffer.length() > 2) {
209                    buffer.append(",");
210                }
211                buffer.append(obj.toString());
212            }
213            buffer.append("]");
214            return buffer.toString();
215        }
216    
217    }