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    package org.apache.camel.component.cxf;
018    
019    import java.io.InputStream;
020    import java.util.Collection;
021    import java.util.HashMap;
022    import java.util.Iterator;
023    import java.util.List;
024    import java.util.Map;
025    
026    import javax.xml.namespace.QName;
027    import javax.xml.ws.Holder;
028    import javax.xml.ws.handler.MessageContext.Scope;
029    
030    import org.apache.camel.Exchange;
031    import org.apache.camel.RuntimeCamelException;
032    import org.apache.camel.impl.DefaultProducer;
033    import org.apache.camel.util.ObjectHelper;
034    import org.apache.commons.logging.Log;
035    import org.apache.commons.logging.LogFactory;
036    import org.apache.cxf.binding.soap.model.SoapHeaderInfo;
037    import org.apache.cxf.endpoint.Client;
038    import org.apache.cxf.jaxws.context.WrappedMessageContext;
039    import org.apache.cxf.message.ExchangeImpl;
040    import org.apache.cxf.message.Message;
041    import org.apache.cxf.service.model.BindingMessageInfo;
042    import org.apache.cxf.service.model.BindingOperationInfo;
043    
044    /**
045     * CxfProducer binds a Camel exchange to a CXF exchange, acts as a CXF 
046     * client, and sends the request to a CXF to a server.  Any response will 
047     * be bound to Camel exchange. 
048     *
049     * @version $Revision: 19607 $
050     */
051    public class CxfProducer extends DefaultProducer {
052        private static final Log LOG = LogFactory.getLog(CxfProducer.class);
053        private Client client;
054    
055        /**
056         * Constructor to create a CxfProducer.  It will create a CXF client
057         * object.
058         * 
059         * @param endpoint a CxfEndpoint that creates this producer
060         * @throws Exception any exception thrown during the creation of a 
061         * CXF client
062         */
063        public CxfProducer(CxfEndpoint endpoint) throws Exception {
064            super(endpoint);
065            client = endpoint.createClient();
066        }
067    
068        /**
069         * This processor binds Camel exchange to a CXF exchange and
070         * invokes the CXF client.
071         */
072        public void process(Exchange camelExchange) throws Exception {
073            
074            if (LOG.isTraceEnabled()) {
075                LOG.trace("Process exchange: " + camelExchange);
076            }
077            
078            // create CXF exchange
079            ExchangeImpl cxfExchange = new ExchangeImpl();
080            
081            // get CXF binding
082            CxfEndpoint endpoint = (CxfEndpoint)getEndpoint();
083            CxfBinding binding = endpoint.getCxfBinding();
084            
085            // create invocation context
086            WrappedMessageContext requestContext = new WrappedMessageContext(
087                    new HashMap<String, Object>(), null, Scope.APPLICATION);
088            Map<String, Object> responseContext = new HashMap<String, Object>();
089            
090            
091            // set data format mode in exchange
092            DataFormat dataFormat = endpoint.getDataFormat();
093            camelExchange.setProperty(CxfConstants.DATA_FORMAT_PROPERTY, dataFormat);   
094            if (LOG.isTraceEnabled()) {
095                LOG.trace("Set Camel Exchange property: " + DataFormat.class.getName() 
096                        + "=" + dataFormat);
097            }
098            
099            // set data format mode in the request context
100            requestContext.put(DataFormat.class.getName(), dataFormat);
101    
102            // don't let CXF ClientImpl close the input stream 
103            if (dataFormat == DataFormat.MESSAGE) {
104                cxfExchange.put(Client.KEEP_CONDUIT_ALIVE, true);
105                if (LOG.isTraceEnabled()) {
106                    LOG.trace("Set CXF Exchange property: " + Client.KEEP_CONDUIT_ALIVE  
107                            + "=" + true);
108                }
109            }
110            
111            // get binding operation info
112            BindingOperationInfo boi = getBindingOperationInfo(camelExchange);
113            ObjectHelper.notNull(boi, "BindingOperationInfo");
114            
115            // keep the message wrapper in PAYLOAD mode
116            if (dataFormat == DataFormat.PAYLOAD && boi.isUnwrapped()) {
117                boi = boi.getWrappedOperation();
118                cxfExchange.put(BindingOperationInfo.class, boi);
119                
120            } 
121            
122            // store the original boi in the exchange
123            camelExchange.setProperty(BindingOperationInfo.class.getName(), boi);
124            if (LOG.isTraceEnabled()) {
125                LOG.trace("Set exchange property: BindingOperationInfo: " + boi);
126            }
127    
128            // Unwrap boi before passing it to make a client call
129            if (dataFormat != DataFormat.PAYLOAD && !endpoint.isWrapped() && boi != null) {
130                if (boi.isUnwrappedCapable()) {
131                    boi = boi.getUnwrappedOperation();
132                    if (LOG.isTraceEnabled()) {
133                        LOG.trace("Unwrapped BOI " + boi);
134                    }
135                }
136            }
137         
138            // bind the request CXF exchange
139            binding.populateCxfRequestFromExchange(cxfExchange, camelExchange, 
140                    requestContext);
141            
142            // Remove protocol headers from scopes.  Otherwise, response headers can be
143            // overwritten by request headers when SOAPHandlerInterceptor tries to create
144            // a wrapped message context by the copyScoped() method.
145            requestContext.getScopes().remove(Message.PROTOCOL_HEADERS);
146            
147            Map<String, Object> invocationContext = new HashMap<String, Object>();
148            invocationContext.put(Client.RESPONSE_CONTEXT, responseContext);
149            invocationContext.put(Client.REQUEST_CONTEXT, requestContext.getWrappedMap());
150    
151            // send the CXF request
152            client.invoke(boi, getParams(endpoint, camelExchange), 
153                    invocationContext, cxfExchange);
154            
155            // bind the CXF response to Camel exchange
156            if (!boi.getOperationInfo().isOneWay()) {
157                // copy the InMessage header to OutMessage header
158                camelExchange.getOut().getHeaders().putAll(camelExchange.getIn().getHeaders());
159                binding.populateExchangeFromCxfResponse(camelExchange, cxfExchange,
160                        responseContext);
161            }
162        }
163        
164        private void checkParameterSize(CxfEndpoint endpoint, Exchange exchange, Object[] parameters) {
165            BindingOperationInfo boi = getBindingOperationInfo(exchange);
166            if (boi == null) {
167                throw new RuntimeCamelException("Can't find the binding operation information from camel exchange");
168            }
169            if (!endpoint.isWrapped() && boi != null) {
170                if (boi.isUnwrappedCapable()) {
171                    boi = boi.getUnwrappedOperation();
172                }
173            }
174            int experctMessagePartsSize = boi.getInput().getMessageParts().size();
175            
176            if (parameters.length < experctMessagePartsSize) {
177                throw new IllegalArgumentException("Get the wrong parameter size to invoke the out service, Experct size "
178                                                   + experctMessagePartsSize + ", Parameter size " + parameters.length);
179            }
180            
181            if (parameters.length > experctMessagePartsSize) {
182                // need to check the holder parameters        
183                int holdersSize = 0;            
184                for (Object parameter : parameters) {
185                    if (parameter instanceof Holder) {
186                        holdersSize++;
187                    } 
188                }
189                // need to check the soap header information
190                int soapHeadersSize = 0; 
191                BindingMessageInfo bmi =  boi.getInput();
192                if (bmi != null) {
193                    List<SoapHeaderInfo> headers = bmi.getExtensors(SoapHeaderInfo.class);
194                    if (headers != null) {
195                        soapHeadersSize = headers.size();
196                    }
197                }
198              
199                if (holdersSize + experctMessagePartsSize + soapHeadersSize < parameters.length) {
200                    throw new IllegalArgumentException("Get the wrong parameter size to invoke the out service, Experct size "
201                                                       + (experctMessagePartsSize + holdersSize + soapHeadersSize) + ", Parameter size " + parameters.length);
202                }
203            }
204        }
205    
206        /**
207         * Get the parameters for the web service operation
208         */
209        private Object[] getParams(CxfEndpoint endpoint, Exchange exchange) {
210          
211            Object[] params = null;
212            if (endpoint.getDataFormat() == DataFormat.POJO) {
213                List<?> list = exchange.getIn().getBody(List.class);
214                if (list != null) {
215                    params = list.toArray();
216                } else {
217                    // maybe we can iterate the body and that way create a list for the parameters
218                    // then end users do not need to trouble with List
219                    Iterator it = exchange.getIn().getBody(Iterator.class);
220                    if (it != null && it.hasNext()) {
221                        list = exchange.getContext().getTypeConverter().convertTo(List.class, it);
222                        if (list != null) {
223                            params = list.toArray();
224                        }
225                    }
226                    if (params == null) {
227                        // no we could not then use the body as single parameter
228                        params = new Object[1];
229                        params[0] = exchange.getIn().getBody();
230                    }
231                }
232                checkParameterSize(endpoint, exchange, params);
233                
234            } else if (endpoint.getDataFormat() == DataFormat.PAYLOAD) {
235                params = new Object[1];
236                // TODO: maybe it should be mandatory body?
237                params[0] = exchange.getIn().getBody(CxfPayload.class);
238            } else if (endpoint.getDataFormat() == DataFormat.MESSAGE) {
239                params = new Object[1];
240                // TODO: maybe it should be mandatory body?
241                params[0] = exchange.getIn().getBody(InputStream.class);
242            }
243    
244            if (LOG.isTraceEnabled()) {
245                if (params instanceof Object[]) {
246                    for (int i = 0; i < params.length; i++) {
247                        LOG.trace("params[" + i + "] = " + params[i]);
248                    }
249                } else {
250                    LOG.trace("params = " + params);
251                }
252            }
253            
254            return params;
255        }
256    
257        /**
258         * Get operation name from header and use it to lookup and return a 
259         * {@link BindingOperationInfo}.
260         */
261        private BindingOperationInfo getBindingOperationInfo(Exchange ex) {
262            CxfEndpoint endpoint = (CxfEndpoint)this.getEndpoint();
263            BindingOperationInfo answer = null;
264            String lp = ex.getIn().getHeader(CxfConstants.OPERATION_NAME, String.class);
265            if (lp == null) {
266                lp = endpoint.getDefaultOperationName();
267            }
268            if (lp == null) {
269                if (LOG.isDebugEnabled()) {
270                    LOG.debug("Try to find a default operation.  You should set '" 
271                            + CxfConstants.OPERATION_NAME + "' in header.");
272                }
273                Collection<BindingOperationInfo> bois = 
274                    client.getEndpoint().getEndpointInfo().getBinding().getOperations();
275                
276                Iterator<BindingOperationInfo> iter = bois.iterator(); 
277                if (iter.hasNext()) {
278                    answer = iter.next();
279                }
280                
281            } else {
282                String ns = ex.getIn().getHeader(CxfConstants.OPERATION_NAMESPACE, String.class);
283                if (ns == null) {
284                    ns = endpoint.getDefaultOperationNamespace();
285                }
286                if (ns == null) {
287                    ns = client.getEndpoint().getService().getName().getNamespaceURI();
288                    if (LOG.isTraceEnabled()) {
289                        LOG.trace("Operation namespace not in header.  Set it to: " + ns);
290                    }
291                }            
292    
293                QName qname = new QName(ns, lp);
294    
295                if (LOG.isTraceEnabled()) {
296                    LOG.trace("Operation qname = " + qname.toString());
297                }
298                
299                answer = client.getEndpoint().getEndpointInfo().getBinding().getOperation(qname);
300            }
301            return answer;
302        }
303        
304        public Client getClient() {
305            return client;
306        }
307    
308    }