View Javadoc

1   /*
2    * $Id: AxisConnector.java 12238 2008-07-06 22:13:50Z rossmason $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.transport.soap.axis;
12  
13  import org.mule.api.MuleException;
14  import org.mule.api.context.notification.MuleContextNotificationListener;
15  import org.mule.api.context.notification.ServerNotification;
16  import org.mule.api.endpoint.EndpointBuilder;
17  import org.mule.api.endpoint.EndpointURI;
18  import org.mule.api.endpoint.ImmutableEndpoint;
19  import org.mule.api.endpoint.InboundEndpoint;
20  import org.mule.api.lifecycle.InitialisationException;
21  import org.mule.api.service.Service;
22  import org.mule.api.transport.MessageReceiver;
23  import org.mule.component.DefaultJavaComponent;
24  import org.mule.config.ExceptionHelper;
25  import org.mule.config.i18n.CoreMessages;
26  import org.mule.context.notification.MuleContextNotification;
27  import org.mule.endpoint.EndpointURIEndpointBuilder;
28  import org.mule.model.seda.SedaService;
29  import org.mule.object.SingletonObjectFactory;
30  import org.mule.transport.AbstractConnector;
31  import org.mule.transport.service.TransportFactory;
32  import org.mule.transport.servlet.ServletConnector;
33  import org.mule.transport.soap.axis.extensions.MuleConfigProvider;
34  import org.mule.transport.soap.axis.extensions.MuleTransport;
35  import org.mule.transport.soap.axis.extensions.WSDDFileProvider;
36  import org.mule.transport.soap.axis.extensions.WSDDJavaMuleProvider;
37  import org.mule.transport.soap.axis.i18n.AxisMessages;
38  import org.mule.util.ClassUtils;
39  import org.mule.util.MuleUrlStreamHandlerFactory;
40  
41  import java.util.ArrayList;
42  import java.util.HashMap;
43  import java.util.Iterator;
44  import java.util.List;
45  import java.util.Map;
46  
47  import javax.xml.namespace.QName;
48  
49  import org.apache.axis.client.Call;
50  import org.apache.axis.configuration.SimpleProvider;
51  import org.apache.axis.deployment.wsdd.WSDDConstants;
52  import org.apache.axis.deployment.wsdd.WSDDProvider;
53  import org.apache.axis.encoding.TypeMappingRegistryImpl;
54  import org.apache.axis.encoding.ser.BeanDeserializerFactory;
55  import org.apache.axis.encoding.ser.BeanSerializerFactory;
56  import org.apache.axis.handlers.soap.SOAPService;
57  import org.apache.axis.server.AxisServer;
58  import org.apache.axis.wsdl.fromJava.Namespaces;
59  import org.apache.axis.wsdl.fromJava.Types;
60  
61  /**
62   * <code>AxisConnector</code> is used to maintain one or more Services for Axis
63   * server instance.
64   * <p/>
65   * Some of the Axis specific service initialisation code was adapted from the Ivory
66   * project (http://ivory.codehaus.org). Thanks guys :)
67   */
68  public class AxisConnector extends AbstractConnector implements MuleContextNotificationListener
69  {
70      /* Register the AxisFault Exception reader if this class gets loaded */
71      static
72      {
73          ExceptionHelper.registerExceptionReader(new AxisFaultExceptionReader());
74      }
75  
76      public static final QName QNAME_MULE_PROVIDER = new QName(WSDDConstants.URI_WSDD_JAVA, "Mule");
77      public static final QName QNAME_MULE_TYPE_MAPPINGS = new QName("http://www.muleumo.org/ws/mappings",
78              "Mule");
79      public static final String DEFAULT_MULE_NAMESPACE_URI = "http://www.muleumo.org";
80  
81      public static final String DEFAULT_MULE_AXIS_SERVER_CONFIG = "mule-axis-server-config.wsdd";
82      public static final String DEFAULT_MULE_AXIS_CLIENT_CONFIG = "mule-axis-client-config.wsdd";
83      public static final String AXIS_SERVICE_COMPONENT_NAME = "_axisServiceComponent";
84      public static final String AXIS_SERVICE_PROPERTY = "_axisService";
85      public static final String AXIS_CLIENT_CONFIG_PROPERTY = "clientConfig";
86  
87      public static final String SERVICE_PROPERTY_COMPONENT_NAME = "componentName";
88      public static final String SERVICE_PROPERTY_SERVCE_PATH = "servicePath";
89  
90      public static final String AXIS = "axis";
91  
92      // used by dispatcher and receiver
93      public static final String SOAP_METHODS = "soapMethods";
94      public static final String STYLE = "style";
95      public static final String USE = "use";
96  
97      private String serverConfig = DEFAULT_MULE_AXIS_SERVER_CONFIG;
98  
99      private AxisServer axis = null;
100     private SimpleProvider serverProvider = null;
101     private String clientConfig = DEFAULT_MULE_AXIS_CLIENT_CONFIG;
102     private SimpleProvider clientProvider = null;
103 
104     private List beanTypes;
105     private Service axisComponent;
106 
107     //this will store the name of the descriptor of the current connector's AxisServiceComponent
108     //private String specificAxisServiceComponentName;
109 
110     /**
111      * These protocols will be set on client invocations. By default Mule uses it's
112      * own transports rather that Axis's. This is only because it gives us more
113      * flexibility inside Mule and simplifies the code
114      */
115     private Map axisTransportProtocols = null;
116 
117     /**
118      * A store of registered servlet services that need to have their endpoints
119      * re-written with the 'real' http url instead of the servlet:// one. This is
120      * only required to ensure wsdl is generated correctly. I would like a clearer
121      * way of doing this so I can remove this workaround
122      */
123     private List servletServices = new ArrayList();
124 
125     private List supportedSchemes = null;
126 
127     private boolean doAutoTypes = true;
128 
129     private boolean treatMapAsNamedParams = true;
130 
131     public AxisConnector()
132     {
133         super();
134         this.registerProtocols();
135     }
136 
137     protected void registerProtocols()
138     {
139         if (supportedSchemes == null)
140         {
141             // Default supported schemes, these can be restricted
142             // through configuration
143             supportedSchemes = new ArrayList();
144             supportedSchemes.add("http");
145             supportedSchemes.add("https");
146             supportedSchemes.add("servlet");
147             supportedSchemes.add("vm");
148             supportedSchemes.add("jms");
149             supportedSchemes.add("xmpp");
150             supportedSchemes.add("ssl");
151             supportedSchemes.add("tcp");
152             supportedSchemes.add("smtp");
153             supportedSchemes.add("smtps");
154             supportedSchemes.add("pop3");
155             supportedSchemes.add("pop3s");
156             supportedSchemes.add("imap");
157             supportedSchemes.add("imaps");
158         }
159 
160         for (Iterator iterator = supportedSchemes.iterator(); iterator.hasNext();)
161         {
162             String s = (String) iterator.next();
163             registerSupportedProtocol(s);
164         }
165     }
166 
167     protected void doInitialise() throws InitialisationException
168     {
169         axisTransportProtocols = new HashMap();
170         //specificAxisServiceComponentName = AXIS_SERVICE_COMPONENT_NAME + "_" + name;
171 
172         axisTransportProtocols = new HashMap();
173         try
174         {
175             for (Iterator iterator = supportedSchemes.iterator(); iterator.hasNext();)
176             {
177                 String s = (String) iterator.next();
178                 axisTransportProtocols.put(s, MuleTransport.getTransportClass(s));
179                 registerSupportedProtocol(s);
180             }
181             muleContext.registerListener(this);
182         }
183         catch (Exception e)
184         {
185             throw new InitialisationException(e, this);
186         }
187         // TODO DO: call registerSupportedProtocol if axisTransportProtocols are set from external?
188 
189         if (clientProvider == null)
190         {
191             clientProvider = createAxisProvider(clientConfig);
192         }
193         else
194         {
195             if (!DEFAULT_MULE_AXIS_CLIENT_CONFIG.equals(clientConfig))
196             {
197                 logger.warn(AxisMessages.clientProviderAndClientConfigConfigured());
198             }
199         }
200 
201         if (axis == null)
202         {
203             if (serverProvider == null)
204             {
205                 serverProvider = this.createAxisProvider(serverConfig);
206             }
207             else
208             {
209                 if (!DEFAULT_MULE_AXIS_SERVER_CONFIG.equals(serverConfig))
210                 {
211                     logger.warn(AxisMessages.serverProviderAndServerConfigConfigured());
212                 }
213             }
214 
215             // Create the AxisServer
216             axis = new AxisServer(serverProvider);
217             // principle of least surprise: doAutoTypes only has effect on our self-configured AxisServer
218             axis.setOption("axis.doAutoTypes", Boolean.valueOf(doAutoTypes));
219         }
220 
221         // Register the Mule service serverProvider
222         WSDDProvider.registerProvider(QNAME_MULE_PROVIDER, new WSDDJavaMuleProvider(this));
223 
224         try
225         {
226             registerTransportTypes();
227         }
228         catch (ClassNotFoundException e)
229         {
230             throw new InitialisationException(
231                     CoreMessages.cannotLoadFromClasspath(e.getMessage()), e, this);
232         }
233 
234         // Register all our UrlStreamHandlers here so they can be resolved. This is necessary
235         // to make Mule work in situations where modification of system properties at runtime
236         // is not reliable, e.g. when running in maven's surefire test executor.
237         MuleUrlStreamHandlerFactory.registerHandler("jms", new org.mule.transport.soap.axis.transport.jms.Handler());
238         MuleUrlStreamHandlerFactory.registerHandler("pop3", new org.mule.transport.soap.axis.transport.pop3.Handler());
239         MuleUrlStreamHandlerFactory.registerHandler("smtp", new org.mule.transport.soap.axis.transport.smtp.Handler());
240         MuleUrlStreamHandlerFactory.registerHandler("vm", new org.mule.transport.soap.axis.transport.vm.Handler());
241 
242         try
243         {
244             registerTypes((TypeMappingRegistryImpl) axis.getTypeMappingRegistry(), beanTypes);
245         }
246         catch (ClassNotFoundException e)
247         {
248             throw new InitialisationException(e, this);
249         }
250     }
251 
252     protected void registerTransportTypes() throws ClassNotFoundException
253     {
254         // Register Transport handlers
255         // By default these will all be handled by Mule, however some companies may
256         // have their own they wish to use
257         for (Iterator iterator = getAxisTransportProtocols().keySet().iterator(); iterator.hasNext();)
258         {
259             String protocol = (String) iterator.next();
260             Object temp = getAxisTransportProtocols().get(protocol);
261             Class clazz;
262             if (temp instanceof String)
263             {
264                 clazz = ClassUtils.loadClass(temp.toString(), getClass());
265             }
266             else
267             {
268                 clazz = (Class) temp;
269             }
270             Call.setTransportForProtocol(protocol, clazz);
271         }
272     }
273 
274     protected SimpleProvider createAxisProvider(String config) throws InitialisationException
275     {
276         // Use our custom file provider that does not require services to be declared
277         // in the WSDD. This only affects the
278         // client side as the client will fallback to the FileProvider when invoking
279         // a service.
280         WSDDFileProvider fileProvider = new WSDDFileProvider(config);
281         fileProvider.setSearchClasspath(true);
282         /*
283          * Wrap the FileProvider with a SimpleProvider so we can programmatically
284          * configure the Axis server (you can only use wsdd descriptors with the
285          * FileProvider)
286          */
287         return new MuleConfigProvider(fileProvider);
288     }
289 
290     public String getProtocol()
291     {
292         return AXIS;
293     }
294 
295     /**
296      * The method determines the key used to store the receiver against.
297      *
298      * @param component the component for which the endpoint is being registered
299      * @param endpoint  the endpoint being registered for the component
300      * @return the key to store the newly created receiver against. In this case it
301      *         is the component name, which is equivalent to the Axis service name.
302      */
303     protected Object getReceiverKey(Service component, InboundEndpoint endpoint)
304     {
305         if (endpoint.getEndpointURI().getPort() == -1)
306         {
307             return component.getName();
308         }
309         else
310         {
311             return endpoint.getEndpointURI().getAddress() + "/" + component.getName();
312         }
313     }
314 
315     protected void unregisterReceiverWithMuleService(MessageReceiver receiver, EndpointURI ep)
316             throws MuleException
317     {
318         String endpointKey = getCounterEndpointKey(receiver.getEndpointURI());
319 
320         for (Iterator iterator = axisComponent.getInboundRouter().getEndpoints().iterator(); iterator.hasNext();)
321         {
322             ImmutableEndpoint umoEndpoint = (ImmutableEndpoint) iterator.next();
323             if (endpointKey.startsWith(umoEndpoint.getEndpointURI().getAddress()))
324             {
325                 logger.info("Unregistering Axis endpoint: " + endpointKey + " for service: "
326                         + ((AxisMessageReceiver) receiver).getSoapService().getName());
327             }
328             try
329             {
330                 umoEndpoint.getConnector()
331                         .unregisterListener(receiver.getService(), receiver.getEndpoint());
332             }
333             catch (Exception e)
334             {
335                 logger.error("Failed to unregister Axis endpoint: " + endpointKey + " for service: "
336                         + receiver.getService().getName() + ". Error is: "
337                         + e.getMessage(), e);
338             }
339         }
340     }
341 
342     protected void registerReceiverWithMuleService(MessageReceiver receiver, EndpointURI ep)
343             throws MuleException
344     {
345         // If this is the first receiver we need to create the Axis service
346         // component this will be registered with Mule when the Connector starts
347         // See if the axis descriptor has already been added. This allows
348         // developers to override the default configuration, say to increase
349         // the threadpool
350         if (axisComponent == null)
351         {
352             axisComponent = getOrCreateAxisComponent();
353         }
354         else
355         {
356             // Lets unregister the 'template' instance, configure it and
357             // then register again later
358             muleContext.getRegistry().unregisterService(AXIS_SERVICE_PROPERTY + getName());
359         }
360 
361         String serviceName = ((AxisMessageReceiver) receiver).getSoapService().getName();
362         // No determine if the endpointUri requires a new connector to be
363         // registed in the case of http we only need to register the new endpointUri
364         // if the port is different If we're using VM or Jms we just use the resource
365         // info directly without appending a service name
366         String endpoint;
367         String scheme = ep.getScheme().toLowerCase();
368         if (scheme.equals("jms") || scheme.equals("vm"))
369         {
370             endpoint = ep.toString();
371         }
372         else
373         {
374             endpoint = receiver.getEndpointURI().getAddress() + "/" + serviceName;
375         }
376         if (logger.isDebugEnabled())
377         {
378             logger.debug("Modified endpoint with " + scheme + " scheme to " + endpoint);
379         }
380 
381         boolean sync = receiver.getEndpoint().isSynchronous();
382         
383         EndpointBuilder serviceEndpointbuilder = new EndpointURIEndpointBuilder(endpoint, muleContext);
384         serviceEndpointbuilder.setSynchronous(sync);
385         serviceEndpointbuilder.setName(ep.getScheme() + ":" + serviceName);
386         // Set the transformers on the endpoint too
387         serviceEndpointbuilder.setTransformers(receiver.getEndpoint().getTransformers().isEmpty() ? null
388                                                                                                   : receiver.getEndpoint().getTransformers());
389         serviceEndpointbuilder.setResponseTransformers(receiver.getEndpoint().getResponseTransformers().isEmpty() ? null
390                                                                                                                  : receiver.getEndpoint().getResponseTransformers());
391         // set the filter on the axis endpoint on the real receiver endpoint
392         serviceEndpointbuilder.setFilter(receiver.getEndpoint().getFilter());
393         // set the Security filter on the axis endpoint on the real receiver
394         // endpoint
395         serviceEndpointbuilder.setSecurityFilter(receiver.getEndpoint().getSecurityFilter());
396 
397         // TODO Do we really need to modify the existing receiver endpoint? What happens if we don't security,
398         // filters and transformers will get invoked twice?
399         EndpointBuilder receiverEndpointBuilder = new EndpointURIEndpointBuilder(receiver.getEndpoint(),
400             muleContext);
401         // Remove the Axis filter now
402         receiverEndpointBuilder.setFilter(null);
403         // Remove the Axis Receiver Security filter now
404         receiverEndpointBuilder.setSecurityFilter(null);
405 
406         InboundEndpoint serviceEndpoint = muleContext.getRegistry()
407             .lookupEndpointFactory()
408             .getInboundEndpoint(serviceEndpointbuilder);
409 
410         InboundEndpoint receiverEndpoint = muleContext.getRegistry()
411             .lookupEndpointFactory()
412             .getInboundEndpoint(receiverEndpointBuilder);
413 
414         receiver.setEndpoint(receiverEndpoint);
415 
416         
417         axisComponent.getInboundRouter().addEndpoint(serviceEndpoint);
418     }
419 
420     private String getCounterEndpointKey(EndpointURI endpointURI)
421     {
422         StringBuffer endpointKey = new StringBuffer(64);
423 
424         endpointKey.append(endpointURI.getScheme());
425         endpointKey.append("://");
426         endpointKey.append(endpointURI.getHost());
427         if (endpointURI.getPort() > -1)
428         {
429             endpointKey.append(":");
430             endpointKey.append(endpointURI.getPort());
431         }
432         return endpointKey.toString();
433     }
434 
435     // This initialization could be performed in the initialize() method.  Putting it here essentially makes
436     // it a lazy-create/lazy-init
437     // Another option would be to put it in the default-axis-config.xml (MULE-2102) with lazy-init="true" 
438     // but that makes us depend on Spring.
439     // Another consideration is how/when this implicit component gets disposed.
440     protected Service getOrCreateAxisComponent() throws MuleException
441     {
442         Service c = muleContext.getRegistry().lookupService(AXIS_SERVICE_PROPERTY + getName());
443 
444         if (c == null)
445         {
446             // TODO MULE-2228 Simplify this API
447             c = new SedaService();
448             c.setName(AXIS_SERVICE_PROPERTY + getName());
449             c.setModel(muleContext.getRegistry().lookupSystemModel());
450 
451             Map props = new HashMap();
452             props.put(AXIS, axis);
453             SingletonObjectFactory of = new SingletonObjectFactory(AxisServiceComponent.class, props);
454             of.initialise();
455             c.setComponent(new DefaultJavaComponent(of));
456         }
457         return c;
458     }
459 
460     /**
461      * Template method to perform any work when starting the connectoe
462      *
463      * @throws org.mule.api.MuleException if the method fails
464      */
465     protected void doStart() throws MuleException
466     {
467         axis.start();
468     }
469 
470     /**
471      * Template method to perform any work when stopping the connectoe
472      *
473      * @throws org.mule.api.MuleException if the method fails
474      */
475     protected void doStop() throws MuleException
476     {
477         axis.stop();
478         // Model model = muleContext.getRegistry().lookupModel();
479         // model.unregisterComponent(model.getDescriptor(AXIS_SERVICE_COMPONENT_NAME));
480     }
481 
482     protected void doConnect() throws Exception
483     {
484         // template method
485     }
486 
487     protected void doDisconnect() throws Exception
488     {
489         // template method
490     }
491 
492     protected void doDispose()
493     {
494         // template method
495     }
496 
497     public String getServerConfig()
498     {
499         return serverConfig;
500     }
501 
502     public void setServerConfig(String serverConfig)
503     {
504         this.serverConfig = serverConfig;
505     }
506 
507     public List getBeanTypes()
508     {
509         return beanTypes;
510     }
511 
512     public void setBeanTypes(List beanTypes)
513     {
514         this.beanTypes = beanTypes;
515     }
516 
517     public String getClientConfig()
518     {
519         return clientConfig;
520     }
521 
522     public void setClientConfig(String clientConfig)
523     {
524         this.clientConfig = clientConfig;
525     }
526 
527     public AxisServer getAxis()
528     {
529         return axis;
530     }
531 
532     public void setAxis(AxisServer axisServer)
533     {
534         this.axis = axisServer;
535     }
536 
537     public SimpleProvider getServerProvider()
538     {
539         return serverProvider;
540     }
541 
542     public void setServerProvider(SimpleProvider serverProvider)
543     {
544         this.serverProvider = serverProvider;
545     }
546 
547     public SimpleProvider getClientProvider()
548     {
549         return clientProvider;
550     }
551 
552     public void setClientProvider(SimpleProvider clientProvider)
553     {
554         this.clientProvider = clientProvider;
555     }
556 
557     public Map getAxisTransportProtocols()
558     {
559         return axisTransportProtocols;
560     }
561 
562     public void setAxisTransportProtocols(Map axisTransportProtocols)
563     {
564         this.axisTransportProtocols.putAll(axisTransportProtocols);
565     }
566 
567     void addServletService(SOAPService service)
568     {
569         servletServices.add(service);
570     }
571 
572     public List getSupportedSchemes()
573     {
574         return supportedSchemes;
575     }
576 
577     public void setSupportedSchemes(List supportedSchemes)
578     {
579         this.supportedSchemes = supportedSchemes;
580     }
581 
582     public boolean isDoAutoTypes()
583     {
584         return doAutoTypes;
585     }
586 
587     public void setDoAutoTypes(boolean doAutoTypes)
588     {
589         this.doAutoTypes = doAutoTypes;
590     }
591 
592     void registerTypes(TypeMappingRegistryImpl registry, List types) throws ClassNotFoundException
593     {
594         if (types != null)
595         {
596             Class clazz;
597             for (Iterator iterator = types.iterator(); iterator.hasNext();)
598             {
599                 clazz = ClassUtils.loadClass(iterator.next().toString(), getClass());
600                 String localName = Types.getLocalNameFromFullName(clazz.getName());
601                 QName xmlType = new QName(Namespaces.makeNamespace(clazz.getName()), localName);
602 
603                 registry.getDefaultTypeMapping().register(clazz, xmlType,
604                         new BeanSerializerFactory(clazz, xmlType), new BeanDeserializerFactory(clazz, xmlType));
605             }
606         }
607     }
608 
609     public boolean isTreatMapAsNamedParams()
610     {
611         return treatMapAsNamedParams;
612     }
613 
614     public void setTreatMapAsNamedParams(boolean treatMapAsNamedParams)
615     {
616         this.treatMapAsNamedParams = treatMapAsNamedParams;
617     }
618 
619     public void onNotification(ServerNotification notification)
620     {
621         if (notification.getAction() == MuleContextNotification.CONTEXT_STARTED)
622         {
623             // We need to register the Axis service component once the muleContext
624             // starts because when the model starts listeners on components are started, thus
625             // all listener need to be registered for this connector before the Axis service
626             // component is registered.
627             // The implication of this is that to add a new service and a
628             // different http port the model needs to be restarted before the listener is available
629             if (muleContext.getRegistry().lookupService(AXIS_SERVICE_PROPERTY + getName()) == null)
630             {
631                 try
632                 {
633                     // Descriptor might be null if no inbound endpoints have been
634                     // register for the Axis connector
635                     if (axisComponent == null)
636                     {
637                         axisComponent = getOrCreateAxisComponent();
638                     }
639                     muleContext.getRegistry().registerService(axisComponent);
640 
641                     // We have to perform a small hack here to rewrite servlet://
642                     // endpoints with the
643                     // real http:// address
644                     for (Iterator iterator = servletServices.iterator(); iterator.hasNext();)
645                     {
646                         SOAPService service = (SOAPService) iterator.next();
647                         ServletConnector servletConnector = (ServletConnector) TransportFactory.getConnectorByProtocol("servlet");
648                         String url = servletConnector.getServletUrl();
649                         if (url != null)
650                         {
651                             service.getServiceDescription().setEndpointURL(url + "/" + service.getName());
652                         }
653                         else
654                         {
655                             logger.error("The servletUrl property on the ServletConntector has not been set this means that wsdl generation for service '"
656                                     + service.getName() + "' may be incorrect");
657                         }
658                     }
659                     servletServices.clear();
660                 }
661                 catch (MuleException e)
662                 {
663                     handleException(e);
664                 }
665             }
666         }
667     }
668     
669     public boolean isSyncEnabled(String protocol)
670     {
671         protocol = protocol.toLowerCase();
672         if (protocol.equals("http") || protocol.equals("https") || protocol.equals("ssl") || protocol.equals("tcp")
673             || protocol.equals("servlet"))
674         {
675             return true;
676         }
677         else
678         {
679             return super.isSyncEnabled(protocol);
680         }
681     }
682 
683 }