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.lang.reflect.Proxy;
020    import java.util.Collection;
021    import java.util.List;
022    import java.util.concurrent.atomic.AtomicBoolean;
023    
024    import javax.xml.ws.WebServiceProvider;
025    
026    import org.w3c.dom.Element;
027    
028    import org.apache.camel.CamelContext;
029    import org.apache.camel.CamelException;
030    import org.apache.camel.Consumer;
031    import org.apache.camel.Processor;
032    import org.apache.camel.Producer;
033    import org.apache.camel.Service;
034    import org.apache.camel.component.cxf.feature.MessageDataFormatFeature;
035    import org.apache.camel.component.cxf.feature.PayLoadDataFormatFeature;
036    import org.apache.camel.component.cxf.util.CxfEndpointUtils;
037    import org.apache.camel.impl.DefaultEndpoint;
038    import org.apache.camel.spi.HeaderFilterStrategy;
039    import org.apache.camel.spi.HeaderFilterStrategyAware;
040    import org.apache.camel.spring.SpringCamelContext;
041    import org.apache.camel.util.ObjectHelper;
042    import org.apache.commons.logging.Log;
043    import org.apache.commons.logging.LogFactory;
044    import org.apache.cxf.Bus;
045    import org.apache.cxf.BusFactory;
046    import org.apache.cxf.common.classloader.ClassLoaderUtils;
047    import org.apache.cxf.common.util.ClassHelper;
048    import org.apache.cxf.endpoint.Client;
049    import org.apache.cxf.endpoint.ClientImpl;
050    import org.apache.cxf.endpoint.Endpoint;
051    import org.apache.cxf.feature.LoggingFeature;
052    import org.apache.cxf.frontend.ClientFactoryBean;
053    import org.apache.cxf.frontend.ClientProxy;
054    import org.apache.cxf.frontend.ClientProxyFactoryBean;
055    import org.apache.cxf.frontend.ServerFactoryBean;
056    import org.apache.cxf.headers.Header;
057    import org.apache.cxf.jaxws.JaxWsClientFactoryBean;
058    import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
059    import org.apache.cxf.jaxws.JaxWsServerFactoryBean;
060    import org.apache.cxf.message.Attachment;
061    import org.apache.cxf.message.Message;
062    import org.apache.cxf.message.MessageContentsList;
063    import org.apache.cxf.service.model.BindingOperationInfo;
064    import org.apache.cxf.service.model.MessagePartInfo;
065    import org.springframework.context.ApplicationContext;
066    
067    /**
068     * Defines the <a href="http://camel.apache.org/cxf.html">CXF Endpoint</a>.
069     * It contains a list of properties for CXF endpoint including {@link DataFormat}, 
070     * {@link CxfBinding}, and {@link HeaderFilterStrategy}.  The default DataFormat 
071     * mode is {@link DataFormat#POJO}.  
072     *
073     * @version $Revision: 20459 $
074     */
075    public class CxfEndpoint extends DefaultEndpoint implements HeaderFilterStrategyAware, Service {
076        
077        private static final Log LOG = LogFactory.getLog(CxfEndpoint.class);
078    
079        private String wsdlURL;
080        private String serviceClass;
081        private String portName;
082        private String serviceName;
083        private String defaultOperationName;
084        private String defaultOperationNamespace;
085        private DataFormat dataFormat = DataFormat.POJO;
086        // This is for invoking the CXFClient with wrapped parameters of unwrapped parameters
087        private boolean isWrapped;
088        // This is for marshal or unmarshal message with the document-literal wrapped or unwrapped style
089        private Boolean wrappedStyle;
090        private boolean inOut = true;
091        private Bus bus;
092        private CxfBinding cxfBinding;
093        private HeaderFilterStrategy headerFilterStrategy;
094        private AtomicBoolean getBusHasBeenCalled = new AtomicBoolean(false);
095        private boolean isSetDefaultBus;
096        private boolean loggingFeatureEnabled;
097    
098        public CxfEndpoint(String remaining, CxfComponent cxfComponent) {
099            super(remaining, cxfComponent);
100        }
101        
102        public CxfEndpoint(String remaining, CamelContext context) {
103            super(remaining, context);
104        }
105    
106        public Producer createProducer() throws Exception {
107            return new CxfProducer(this);
108        }
109    
110        public Consumer createConsumer(Processor processor) throws Exception {
111            return new CxfConsumer(this, processor);
112        }
113        
114        public boolean isSingleton() {
115            return true;
116        }
117        
118        /**
119         * Populate server factory bean
120         */
121        protected void setupServerFactoryBean(ServerFactoryBean sfb, Class<?> cls) {
122            
123            // address
124            sfb.setAddress(getEndpointUri());
125            
126            // service class
127            sfb.setServiceClass(cls); 
128    
129            // wsdl url
130            if (getWsdlURL() != null) {
131                sfb.setWsdlURL(getWsdlURL());
132            }
133            
134            // service  name qname
135            if (getServiceName() != null) {
136                sfb.setServiceName(CxfEndpointUtils.getQName(getServiceName()));
137            }
138            
139            // port qname
140            if (getPortName() != null) {
141                sfb.setEndpointName(CxfEndpointUtils.getQName(getPortName()));
142            }
143    
144            // apply feature here
145            if (!CxfEndpointUtils.hasAnnotation(cls, WebServiceProvider.class)) {
146                if (getDataFormat() == DataFormat.PAYLOAD) {
147                    sfb.getFeatures().add(new PayLoadDataFormatFeature());
148                } else if (getDataFormat() == DataFormat.MESSAGE) {
149                    sfb.getFeatures().add(new MessageDataFormatFeature());
150                }
151            } else {
152                if (LOG.isDebugEnabled()) {
153                    LOG.debug("Ignore DataFormat mode " + getDataFormat() 
154                            + " since SEI class is annotated with WebServiceProvider");
155                }
156            }
157            
158            if (loggingFeatureEnabled) {
159                sfb.getFeatures().add(new LoggingFeature());
160            }
161    
162            if (getDataFormat() == DataFormat.PAYLOAD) {
163                sfb.setDataBinding(new HybridSourceDataBinding());
164            }
165            
166            // set the document-literal wrapped style
167            if (getWrappedStyle() != null) {
168                sfb.getServiceFactory().setWrapped(getWrappedStyle());
169            }
170            
171            sfb.setBus(getBus());
172            sfb.setStart(false);
173        }
174        
175        /**
176         * 
177         * Create a client factory bean object.  Notice that the serviceClass <b>must</b> be
178         * an interface.
179         */
180        protected ClientProxyFactoryBean createClientFactoryBean(Class<?> cls) throws CamelException {
181            if (CxfEndpointUtils.hasWebServiceAnnotation(cls)) {
182                return new JaxWsProxyFactoryBean(new JaxWsClientFactoryBean() {
183                    @Override
184                    protected Client createClient(Endpoint ep) {
185                        return new CamelCxfClientImpl(getBus(), ep);
186                    }
187                });
188                
189            } else {
190                return new ClientProxyFactoryBean(new ClientFactoryBean() {
191                    @Override
192                    protected Client createClient(Endpoint ep) {
193                        return new CamelCxfClientImpl(getBus(), ep);
194                    }
195                });
196            }
197        }
198        
199        /**
200         * 
201         * Create a client factory bean object without serviceClass interface.
202         */
203        protected ClientFactoryBean createClientFactoryBean() {
204            return new ClientFactoryBean(new WSDLServiceFactoryBean()) {
205                            
206                @Override
207                protected Client createClient(Endpoint ep) {
208                    return new CamelCxfClientImpl(getBus(), ep);
209                }
210                
211                @Override
212                protected void initializeAnnotationInterceptors(Endpoint ep, Class<?> cls) {
213                    // Do nothing here
214                }
215                
216            };
217        }
218    
219        protected Bus doGetBus() {
220            BusFactory busFactory = BusFactory.newInstance();
221            // need to check if the camelContext is SpringCamelContext and
222            // update the bus configuration with the applicationContext
223            // which SpringCamelContext holds
224            if (getCamelContext() instanceof SpringCamelContext) {
225                SpringCamelContext springCamelContext = (SpringCamelContext)getCamelContext();
226                ApplicationContext applicationContext = springCamelContext.getApplicationContext();
227                busFactory = new org.apache.cxf.bus.spring.SpringBusFactory(applicationContext);
228            }
229            return busFactory.createBus();
230            
231        }
232        
233        /**
234         * 
235         * Populate a client factory bean
236         */
237        protected void setupClientFactoryBean(ClientProxyFactoryBean factoryBean, Class<?> cls) {       
238            // service class
239            factoryBean.setServiceClass(cls);
240            
241            // address
242            factoryBean.setAddress(getEndpointUri());
243    
244            // wsdl url
245            if (getWsdlURL() != null) {
246                factoryBean.setWsdlURL(getWsdlURL());
247            }
248            
249            // service name qname
250            if (getServiceName() != null) {
251                factoryBean.setServiceName(CxfEndpointUtils.getQName(getServiceName()));
252            }
253            
254            // port name qname
255            if (getPortName() != null) {
256                factoryBean.setEndpointName(CxfEndpointUtils.getQName(getPortName()));
257            }
258    
259            // apply feature here
260            if (getDataFormat() == DataFormat.MESSAGE) {
261                factoryBean.getFeatures().add(new MessageDataFormatFeature());
262            } else if (getDataFormat() == DataFormat.PAYLOAD) {
263                factoryBean.getFeatures().add(new PayLoadDataFormatFeature());
264                factoryBean.setDataBinding(new HybridSourceDataBinding());
265            }
266            
267            if (loggingFeatureEnabled) {
268                factoryBean.getFeatures().add(new LoggingFeature());
269            }
270            
271            // set the document-literal wrapped style
272            if (getWrappedStyle() != null) {
273                factoryBean.getServiceFactory().setWrapped(getWrappedStyle());
274            }
275            
276            factoryBean.setBus(getBus());
277            
278        }
279    
280        protected void setupClientFactoryBean(ClientFactoryBean factoryBean) {       
281            // address
282            factoryBean.setAddress(getEndpointUri());
283    
284            // wsdl url
285            if (getWsdlURL() != null) {
286                factoryBean.setWsdlURL(getWsdlURL());
287            }
288            
289            // service name qname
290            if (getServiceName() != null) {
291                factoryBean.setServiceName(CxfEndpointUtils.getQName(getServiceName()));
292            }
293            
294            // port name qname
295            if (getPortName() != null) {
296                factoryBean.setEndpointName(CxfEndpointUtils.getQName(getPortName()));
297            }
298    
299            // apply feature here
300            if (getDataFormat() == DataFormat.MESSAGE) {
301                factoryBean.getFeatures().add(new MessageDataFormatFeature());
302            } else if (getDataFormat() == DataFormat.PAYLOAD) {
303                factoryBean.getFeatures().add(new PayLoadDataFormatFeature());
304                factoryBean.setDataBinding(new HybridSourceDataBinding());
305            }
306            
307            if (loggingFeatureEnabled) {
308                factoryBean.getFeatures().add(new LoggingFeature());
309            }
310            
311            // set the document-literal wrapped style
312            if (getWrappedStyle() != null) {
313                factoryBean.getServiceFactory().setWrapped(getWrappedStyle());
314            }
315            
316            factoryBean.setBus(getBus());        
317        }
318        
319        // Package private methods
320        // -------------------------------------------------------------------------
321     
322        /**
323         * Create a CXF client object
324         */
325        Client createClient() throws Exception {
326    
327            // get service class
328            if (getDataFormat().equals(DataFormat.POJO)) { 
329                ObjectHelper.notEmpty(getServiceClass(), CxfConstants.SERVICE_CLASS);      
330            }
331            
332            Class<?> cls = null;
333            if (getServiceClass() != null) {
334                cls = ClassLoaderUtils.loadClass(getServiceClass(), getClass());
335                // create client factory bean
336                ClientProxyFactoryBean factoryBean = createClientFactoryBean(cls);
337                // setup client factory bean
338                setupClientFactoryBean(factoryBean, cls);
339                return ((ClientProxy)Proxy.getInvocationHandler(factoryBean.create())).getClient();
340            } else {            
341                checkName(portName, "endpoint/port name");
342                checkName(serviceName, "service name");
343                ClientFactoryBean factoryBean = createClientFactoryBean();
344                // setup client factory bean
345                setupClientFactoryBean(factoryBean);
346                return factoryBean.create();
347            }
348            
349        }
350        
351        void checkName(String value, String name) {
352            if (ObjectHelper.isEmpty(value)) {
353                LOG.warn("The " + name + "is empty, cxf will try to load the first one in wsdl for you");
354            }
355        }
356    
357        /**
358         * Create a CXF server factory bean
359         */
360        ServerFactoryBean createServerFactoryBean() throws Exception {
361    
362            Class<?> cls = null;
363            if (getDataFormat() == DataFormat.POJO || getServiceClass() != null) { 
364                // get service class
365                ObjectHelper.notEmpty(getServiceClass(), CxfConstants.SERVICE_CLASS);      
366                cls = ClassLoaderUtils.loadClass(getServiceClass(), getClass());
367            }
368            
369            // create server factory bean
370            // Shouldn't use CxfEndpointUtils.getServerFactoryBean(cls) as it is for
371            // CxfSoapComponent
372            ServerFactoryBean answer = null;
373            
374            if (cls == null) {
375                ObjectHelper.notNull(portName, "Please provide endpoint/port name");
376                ObjectHelper.notNull(serviceName, "Please provide service name");
377                answer = new ServerFactoryBean(new WSDLServiceFactoryBean());
378            } else if (CxfEndpointUtils.hasWebServiceAnnotation(cls)) {
379                answer = new JaxWsServerFactoryBean();
380            } else {
381                answer = new ServerFactoryBean();
382            }
383            
384            // setup server factory bean
385            setupServerFactoryBean(answer, cls);
386            return answer;
387        }
388            
389        // Properties
390        // -------------------------------------------------------------------------
391    
392        public DataFormat getDataFormat() {
393            return dataFormat;
394        }
395    
396        public void setDataFormat(DataFormat format) {
397            dataFormat = format;
398        }
399    
400        public String getWsdlURL() {
401            return wsdlURL;
402        }
403    
404        public void setWsdlURL(String url) {
405            wsdlURL = url;
406        }
407        
408        public String getServiceClass() {        
409            return serviceClass;
410        }
411          
412        public void setServiceClass(String className) {
413            serviceClass = className;
414        }
415        
416        public void setServiceClass(Object instance) {
417            serviceClass = ClassHelper.getRealClass(instance).getName();
418        }
419    
420        public void setServiceName(String service) {
421            serviceName = service;
422        }
423    
424        public String getServiceName() {
425            return serviceName;
426        }
427        
428        public String getPortName() {
429            return portName;
430        }
431        
432        public void setPortName(String port) {
433            portName = port;
434        }
435        
436        public String getDefaultOperationName() {
437            return defaultOperationName;
438        }
439        
440        public void setDefaultOperationName(String name) {
441            defaultOperationName = name;
442        }
443        
444        public String getDefaultOperationNamespace() {
445            return defaultOperationNamespace;
446        }
447        
448        public void setDefaultOperationNamespace(String namespace) {
449            defaultOperationNamespace = namespace;
450        }
451    
452        public boolean isInOut() {
453            return inOut;
454        }
455    
456        public void setInOut(boolean inOut) {
457            this.inOut = inOut;
458        }
459    
460        public boolean isWrapped() {
461            return isWrapped;
462        }
463    
464        public void setWrapped(boolean wrapped) {
465            isWrapped = wrapped;
466        }
467        
468        public Boolean getWrappedStyle() {
469            return wrappedStyle;
470        }
471        
472        public void setWrappedStyle(Boolean wrapped) {
473            wrappedStyle = wrapped;
474        }
475    
476        public void setCxfBinding(CxfBinding cxfBinding) {
477            this.cxfBinding = cxfBinding;
478        }
479    
480        public CxfBinding getCxfBinding() {
481            return cxfBinding;
482        }
483    
484        public void setHeaderFilterStrategy(HeaderFilterStrategy headerFilterStrategy) {
485            this.headerFilterStrategy = headerFilterStrategy;
486            if (cxfBinding instanceof HeaderFilterStrategyAware) {
487                ((HeaderFilterStrategyAware)cxfBinding)
488                    .setHeaderFilterStrategy(headerFilterStrategy);
489            }
490        }
491    
492        public HeaderFilterStrategy getHeaderFilterStrategy() {
493            return headerFilterStrategy;
494        }
495    
496        public void setBus(Bus bus) {
497            this.bus = bus;
498        }
499    
500        public Bus getBus() {
501            if (bus == null) {
502                bus = doGetBus();
503                if (LOG.isDebugEnabled()) {
504                    LOG.debug("Using DefaultBus " + bus);
505                }
506            }
507    
508            if (!getBusHasBeenCalled.getAndSet(true) && isSetDefaultBus) {
509                BusFactory.setDefaultBus(bus);
510                if (LOG.isDebugEnabled()) {
511                    LOG.debug("Set bus " + bus + " as thread default bus");
512                }
513            }
514            return bus;
515        }
516    
517        public void setSetDefaultBus(boolean isSetDefaultBus) {
518            this.isSetDefaultBus = isSetDefaultBus;
519        }
520    
521        public boolean isSetDefaultBus() {
522            return isSetDefaultBus;
523        }
524        
525        public void setLoggingFeatureEnabled(boolean loggingFeatureEnabled) {
526            this.loggingFeatureEnabled = loggingFeatureEnabled;
527        }
528    
529        public boolean isLoggingFeatureEnabled() {
530            return loggingFeatureEnabled;
531        }    
532    
533        public void start() throws Exception {
534            if (headerFilterStrategy == null) {
535                headerFilterStrategy = new CxfHeaderFilterStrategy();
536            }
537            if (cxfBinding == null) {
538                cxfBinding = new DefaultCxfBinding();
539            }
540            if (cxfBinding instanceof HeaderFilterStrategyAware) {
541                ((HeaderFilterStrategyAware)cxfBinding).setHeaderFilterStrategy(getHeaderFilterStrategy());
542            }
543        }
544    
545        public void stop() throws Exception {
546            // noop
547        }
548    
549        /**
550         * We need to override the {@link ClientImpl#setParameters} method
551         * to insert parameters into CXF Message for {@link DataFormat#PAYLOAD}
552         * mode.
553         */
554        class CamelCxfClientImpl extends ClientImpl {
555           
556            public CamelCxfClientImpl(Bus bus, Endpoint ep) {
557                super(bus, ep);
558            }
559            
560            public Bus getBus() {
561                return bus;
562            }
563            
564            @SuppressWarnings("unchecked")
565            @Override
566            protected void setParameters(Object[] params, Message message) {
567                
568                Object attachements = message.get(CxfConstants.CAMEL_CXF_ATTACHMENTS);
569                if (attachements != null) {
570                    message.setAttachments((Collection<Attachment>)attachements);
571                    message.remove(CxfConstants.CAMEL_CXF_ATTACHMENTS);
572                }
573                
574                if (DataFormat.PAYLOAD == message.get(DataFormat.class)) {
575                    
576                    CxfPayload<?> payload = (CxfPayload<?>)params[0];
577                    List<Element> elements = payload.getBody();
578                    
579                    BindingOperationInfo boi = message.get(BindingOperationInfo.class);
580                    MessageContentsList content = new MessageContentsList();
581                    int i = 0;
582                    
583                    for (MessagePartInfo partInfo : boi.getOperationInfo().getInput().getMessageParts()) {
584                        if (elements.size() > i && partInfo.getConcreteName().getLocalPart()
585                            .equals(elements.get(i).getLocalName())) {
586                            content.put(partInfo, elements.get(i++));
587                        }
588                    }
589    
590                    message.setContent(List.class, content);
591                    message.put(Header.HEADER_LIST, payload.getHeaders());
592                } else {
593                    super.setParameters(params, message);
594                }
595                
596                message.remove(DataFormat.class);
597            }
598        }
599    }