View Javadoc

1   /*
2    * $Id: JettyHttpConnector.java 20413 2010-12-01 01:50:19Z dandiep $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.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.servlet.jetty;
12  
13  import org.mule.api.MuleContext;
14  import org.mule.api.MuleException;
15  import org.mule.api.MuleRuntimeException;
16  import org.mule.api.config.MuleProperties;
17  import org.mule.api.construct.FlowConstruct;
18  import org.mule.api.context.notification.MuleContextNotificationListener;
19  import org.mule.api.endpoint.ImmutableEndpoint;
20  import org.mule.api.endpoint.InboundEndpoint;
21  import org.mule.api.lifecycle.InitialisationException;
22  import org.mule.api.lifecycle.LifecycleException;
23  import org.mule.api.transport.MessageReceiver;
24  import org.mule.api.transport.ReplyToHandler;
25  import org.mule.config.i18n.CoreMessages;
26  import org.mule.context.notification.MuleContextNotification;
27  import org.mule.context.notification.NotificationException;
28  import org.mule.transport.AbstractConnector;
29  import org.mule.transport.servlet.JarResourceServlet;
30  import org.mule.transport.servlet.MuleReceiverServlet;
31  import org.mule.transport.servlet.MuleServletContextListener;
32  import org.mule.util.IOUtils;
33  import org.mule.util.StringUtils;
34  
35  import java.io.InputStream;
36  import java.util.ArrayList;
37  import java.util.HashMap;
38  import java.util.List;
39  
40  import javax.servlet.Servlet;
41  import javax.servlet.http.HttpServlet;
42  
43  import edu.emory.mathcs.backport.java.util.Arrays;
44  
45  import org.mortbay.jetty.Connector;
46  import org.mortbay.jetty.Server;
47  import org.mortbay.jetty.handler.ContextHandlerCollection;
48  import org.mortbay.jetty.nio.SelectChannelConnector;
49  import org.mortbay.jetty.servlet.Context;
50  import org.mortbay.jetty.servlet.ServletHolder;
51  import org.mortbay.xml.XmlConfiguration;
52  
53  /**
54   * The <code>JettyConnector</code> can be using to embed a Jetty server to receive requests on an
55   * http inound endpoint. One server is created for each connector declared, many Jetty endpoints
56   * can share the same connector.
57   */
58  public class JettyHttpConnector extends AbstractConnector
59  {
60      public static final String ROOT = "/";
61  
62      public static final String JETTY = "jetty";
63  
64      private Server httpServer;
65  
66      private String configFile;
67  
68      private JettyReceiverServlet receiverServlet;
69  
70      private boolean useContinuations = false;
71  
72      private String resourceBase;
73  
74      private WebappsConfiguration webappsConfiguration;
75  
76      protected HashMap<String, ConnectorHolder> holders = new HashMap<String, ConnectorHolder>();
77  
78      private WebAppDeployer deployer;
79  
80      public JettyHttpConnector(MuleContext context)
81      {
82          super(context);
83          registerSupportedProtocol("http");
84          registerSupportedProtocol(JETTY);
85          setInitialStateStopped(true);
86      }
87  
88      public String getProtocol()
89      {
90          return JETTY;
91      }
92  
93      @Override
94      protected void doInitialise() throws InitialisationException
95      {
96          httpServer = new Server();
97          
98          if (webappsConfiguration != null)
99          {
100             deployer = new WebAppDeployer();
101             deployer.setWebAppDir(webappsConfiguration.getDirectory());
102             deployer.setExtract(true);
103             deployer.setParentLoaderPriority(false);
104             deployer.setServerClasses(webappsConfiguration.getServerClasses());
105             deployer.setSystemClasses(webappsConfiguration.getSystemClasses());
106 
107             org.mortbay.jetty.AbstractConnector connector = createJettyConnector();
108             connector.setHost(webappsConfiguration.getHost());
109             connector.setPort(webappsConfiguration.getPort());
110             deployer.setContexts(httpServer);
111 
112             httpServer.addConnector(connector);
113             httpServer.addLifeCycle(deployer);
114         }
115         
116         initialiseFromConfigFile();
117 
118         try
119         {
120             muleContext.registerListener(new MuleContextNotificationListener<MuleContextNotification>(){
121                 public void onNotification(MuleContextNotification notification)
122                 {
123                     if (notification.getAction() == MuleContextNotification.CONTEXT_STARTED)
124                     {
125                         //We delay starting until the context has been started since we need the MuleAjaxServlet to initialise first
126                         setInitialStateStopped(false);
127                         try
128                         {
129                             start();
130                         }
131                         catch (MuleException e)
132                         {
133                             throw new MuleRuntimeException(CoreMessages.failedToStart(getName()), e);
134                         }
135                     }
136                 }
137             });
138         }
139         catch (NotificationException e)
140         {
141             throw new InitialisationException(e, this);
142         }
143     }
144 
145     @SuppressWarnings("unchecked")
146     protected void initialiseFromConfigFile() throws InitialisationException
147     {
148         if (configFile != null)
149         {
150             try
151             {
152                 InputStream is = IOUtils.getResourceAsStream(configFile, getClass());
153                 XmlConfiguration config = new XmlConfiguration(is);
154 
155                 String appHome =
156                     muleContext.getRegistry().lookupObject(MuleProperties.APP_HOME_DIRECTORY_PROPERTY);
157                 if (appHome == null)
158                 {
159                     // Mule IDE sets app.home as part of the launch config it creates
160                     appHome = System.getProperty(MuleProperties.APP_HOME_DIRECTORY_PROPERTY);
161                 }
162 
163                 if (appHome != null)
164                 {
165                     config.getProperties().put(MuleProperties.APP_HOME_DIRECTORY_PROPERTY, appHome);
166                 }
167                 
168                 config.configure(httpServer);
169             }
170             catch (Exception e)
171             {
172                 throw new InitialisationException(e, this);
173             }
174         }
175     }
176 
177     /**
178      * Template method to dispose any resources associated with this receiver. There
179      * is not need to dispose the connector as this is already done by the framework
180      */
181     @Override
182     protected void doDispose()
183     {
184         holders.clear();
185     }
186 
187     @Override
188     protected void doStart() throws MuleException
189     {
190         try
191         {
192             httpServer.start();
193             
194             if (deployer != null)
195             {
196                 deployer.start();
197             }
198             
199             for (ConnectorHolder<?, ?> contextHolder : holders.values())
200             {
201                 contextHolder.start();
202             }
203         }
204         catch (Exception e)
205         {
206             throw new LifecycleException(CoreMessages.failedToStart("Jetty Http Receiver"), e, this);
207         }
208     }
209 
210     @Override
211     protected void doStop() throws MuleException
212     {
213         try
214         {
215             httpServer.stop();
216             
217             if (deployer != null)
218             {
219                 deployer.stop();
220             }
221             
222             for (ConnectorHolder<?, ?> connectorRef : holders.values())
223             {
224                 connectorRef.stop();
225             }
226         }
227         catch (Exception e)
228         {
229             throw new LifecycleException(CoreMessages.failedToStop("Jetty Http Receiver"), e, this);
230         }
231     }
232 
233     /**
234      * Template method where any connections should be made for the connector
235      *
236      * @throws Exception
237      */
238     @Override
239     protected void doConnect() throws Exception
240     {
241         //do nothing
242     }
243 
244     /**
245      * Template method where any connected resources used by the connector should be
246      * disconnected
247      *
248      * @throws Exception
249      */
250     @Override
251     protected void doDisconnect() throws Exception
252     {
253         //do nothing
254     }
255 
256      @Override
257     protected MessageReceiver createReceiver(FlowConstruct flowConstruct, InboundEndpoint endpoint) throws Exception
258     {
259         MessageReceiver receiver = super.createReceiver(flowConstruct, endpoint);
260         registerJettyEndpoint(receiver, endpoint);
261         return receiver;
262     }
263 
264     protected org.mortbay.jetty.AbstractConnector createJettyConnector()
265     {
266         return new SelectChannelConnector();
267     }
268 
269     public void unregisterListener(MessageReceiver receiver) throws MuleException
270     {
271         String connectorKey = getHolderKey(receiver.getEndpoint());
272 
273         synchronized (this)
274         {
275             ConnectorHolder connectorRef = holders.get(connectorKey);
276             if (connectorRef != null)
277             {
278                 if (!connectorRef.isReferenced())
279                 {
280                     getHttpServer().removeConnector(connectorRef.getConnector());
281                     holders.remove(connectorKey);
282                     connectorRef.stop();
283                 }
284             }
285         }
286     }
287 
288     public Server getHttpServer()
289     {
290         return httpServer;
291     }
292 
293     public String getConfigFile()
294     {
295         return configFile;
296     }
297 
298     public void setConfigFile(String configFile)
299     {
300         this.configFile = configFile;
301     }
302 
303     public JettyReceiverServlet getReceiverServlet()
304     {
305         return receiverServlet;
306     }
307 
308     public void setReceiverServlet(JettyReceiverServlet receiverServlet)
309     {
310         this.receiverServlet = receiverServlet;
311     }
312 
313     @Override
314     public ReplyToHandler getReplyToHandler(ImmutableEndpoint endpoint)
315     {
316         if (isUseContinuations())
317         {
318             return new JettyContinuationsReplyToHandler(getDefaultResponseTransformers(endpoint), muleContext);
319         }
320         return super.getReplyToHandler(endpoint);
321     }
322 
323     public boolean isUseContinuations()
324     {
325         return useContinuations;
326     }
327 
328     public void setUseContinuations(boolean useContinuations)
329     {
330         this.useContinuations = useContinuations;
331     }
332 
333     ConnectorHolder<? extends MuleReceiverServlet, ? extends JettyHttpMessageReceiver> registerJettyEndpoint(MessageReceiver receiver, InboundEndpoint endpoint) throws MuleException
334     {
335         // Make sure that there is a connector for the requested endpoint.
336         String connectorKey = getHolderKey(endpoint);
337 
338         ConnectorHolder holder;
339 
340         synchronized (this)
341         {
342             holder = holders.get(connectorKey);
343             if (holder == null)
344             {
345                 Connector connector = createJettyConnector();
346 
347                 connector.setPort(endpoint.getEndpointURI().getPort());
348                 connector.setHost(endpoint.getEndpointURI().getHost());
349                 if ("localhost".equalsIgnoreCase(endpoint.getEndpointURI().getHost()))
350                 {
351                     logger.warn("You use localhost interface! It means that no external connections will be available."
352                             + " Don't you want to use 0.0.0.0 instead (all network interfaces)?");
353                 }
354                 getHttpServer().addConnector(connector);
355 
356                 holder = createContextHolder(connector, receiver.getEndpoint(), receiver);
357                 holders.put(connectorKey, holder);
358                 if(isStarted())
359                 {
360                     holder.start();
361                 }
362             }
363             else
364             {
365                 holder.addReceiver(receiver);
366             }
367         }
368         return holder;
369     }
370 
371     protected ConnectorHolder createContextHolder(Connector connector, InboundEndpoint endpoint, MessageReceiver receiver)
372     {
373         return new MuleReceiverConnectorHolder(connector, (JettyReceiverServlet) createServlet(connector, endpoint), (JettyHttpMessageReceiver)receiver);
374     }
375 
376     protected Servlet createServlet(Connector connector, ImmutableEndpoint endpoint)
377     {
378         HttpServlet servlet;
379         if (getReceiverServlet() == null)
380         {
381             if(isUseContinuations())
382             {
383                 servlet = new JettyContinuationsReceiverServlet();
384             }
385             else
386             {
387                 servlet = new JettyReceiverServlet();
388             }
389         }
390         else
391         {
392             servlet = getReceiverServlet();
393         }
394 
395         String path = endpoint.getEndpointURI().getPath();
396         if(StringUtils.isBlank(path))
397         {
398             path = ROOT;
399         }
400 
401         ContextHandlerCollection handlerCollection = new ContextHandlerCollection();
402         Context context = new Context(handlerCollection, ROOT, Context.NO_SECURITY);
403         context.setConnectorNames(new String[]{connector.getName()});
404         context.addEventListener(new MuleServletContextListener(muleContext, getName()));
405 
406         if (resourceBase != null)
407         {
408             Context resourceContext = new Context(handlerCollection, path, Context.NO_SECURITY);
409             resourceContext.setResourceBase(resourceBase);
410         }
411 
412         context.addServlet(JarResourceServlet.class, JarResourceServlet.DEFAULT_PATH_SPEC);
413 
414         ServletHolder holder = new ServletHolder();
415         holder.setServlet(servlet);
416         context.addServlet(holder, "/*");
417         getHttpServer().addHandler(handlerCollection);
418         return servlet;
419     }
420 
421     protected String getHolderKey(ImmutableEndpoint endpoint)
422     {
423         return endpoint.getProtocol() + ":" + endpoint.getEndpointURI().getHost() + ":" + endpoint.getEndpointURI().getPort();
424     }
425 
426     public class MuleReceiverConnectorHolder extends AbstractConnectorHolder<JettyReceiverServlet, JettyHttpMessageReceiver>
427     {
428         List<MessageReceiver> messageReceivers = new ArrayList<MessageReceiver>();
429 
430         public MuleReceiverConnectorHolder(Connector connector, JettyReceiverServlet servlet, JettyHttpMessageReceiver receiver)
431         {
432             super(connector, servlet, receiver);
433             addReceiver(receiver);
434         }
435 
436         public boolean isReferenced()
437         {
438             return messageReceivers.size() > 0;
439         }
440 
441         public void addReceiver(JettyHttpMessageReceiver receiver)
442         {
443             messageReceivers.add(receiver);
444             if(started)
445             {
446                 getServlet().addReceiver(receiver);
447             }
448         }
449 
450         public void removeReceiver(JettyHttpMessageReceiver receiver)
451         {
452             messageReceivers.remove(receiver);
453             getServlet().removeReceiver(receiver);
454         }
455 
456         @Override
457         public void start() throws MuleException
458         {
459             super.start();
460             
461             for (MessageReceiver receiver : messageReceivers)
462             {
463                 servlet.addReceiver(receiver);
464             }
465         }
466 
467         @Override
468         public void stop() throws MuleException
469         {
470             super.stop();
471 
472             for (MessageReceiver receiver : messageReceivers)
473             {
474                 servlet.removeReceiver(receiver);
475             }
476         }
477     }
478 
479     public String getResourceBase()
480     {
481         return resourceBase;
482     }
483 
484     public void setResourceBase(String resourceBase)
485     {
486         this.resourceBase = resourceBase;
487     }
488 
489     public WebappsConfiguration getWebappsConfiguration()
490     {
491         return webappsConfiguration;
492     }
493 
494     public void setWebappsConfiguration(WebappsConfiguration webappsConfiguration)
495     {
496         this.webappsConfiguration = webappsConfiguration;
497     }
498 }