View Javadoc

1   /*
2    * $Id: JettyHttpConnector.java 22858 2011-09-05 08:52:34Z dirk.olmes $
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.ClassUtils;
33  import org.mule.util.IOUtils;
34  import org.mule.util.StringMessageUtils;
35  import org.mule.util.StringUtils;
36  
37  import java.io.File;
38  import java.io.InputStream;
39  import java.net.URL;
40  import java.util.ArrayList;
41  import java.util.HashMap;
42  import java.util.List;
43  
44  import javax.servlet.Servlet;
45  import javax.servlet.http.HttpServlet;
46  
47  import org.mortbay.jetty.Connector;
48  import org.mortbay.jetty.Handler;
49  import org.mortbay.jetty.Server;
50  import org.mortbay.jetty.annotations.Configuration;
51  import org.mortbay.jetty.handler.ContextHandlerCollection;
52  import org.mortbay.jetty.nio.SelectChannelConnector;
53  import org.mortbay.jetty.servlet.Context;
54  import org.mortbay.jetty.servlet.ServletHolder;
55  import org.mortbay.jetty.webapp.WebAppContext;
56  import org.mortbay.jetty.webapp.WebInfConfiguration;
57  import org.mortbay.log.Log;
58  import org.mortbay.xml.XmlConfiguration;
59  
60  /**
61   * The <code>JettyConnector</code> can be using to embed a Jetty server to receive requests on an
62   * http inound endpoint. One server is created for each connector declared, many Jetty endpoints
63   * can share the same connector.
64   */
65  public class JettyHttpConnector extends AbstractConnector
66  {
67      public static final String ROOT = "/";
68  
69      public static final String JETTY = "jetty";
70  
71      private Server httpServer;
72  
73      private String configFile;
74  
75      private JettyReceiverServlet receiverServlet;
76  
77      private boolean useContinuations = false;
78  
79      private String resourceBase;
80  
81      private WebappsConfiguration webappsConfiguration;
82  
83      protected HashMap<String, ConnectorHolder> holders = new HashMap<String, ConnectorHolder>();
84  
85      private WebAppDeployer deployer;
86  
87      public JettyHttpConnector(MuleContext context)
88      {
89          super(context);
90          setupJettyLogging();
91          registerSupportedProtocol("http");
92          registerSupportedProtocol(JETTY);
93          setInitialStateStopped(true);
94      }
95  
96      protected void setupJettyLogging()
97      {
98          if ((Log.getLog() instanceof JettyLogger) == false)
99          {
100             Log.setLog(new JettyLogger());
101         }
102     }
103 
104     @Override
105     public String getProtocol()
106     {
107         return JETTY;
108     }
109 
110     @Override
111     protected void doInitialise() throws InitialisationException
112     {
113         httpServer = new Server()
114         {
115             @Override
116             public void addHandler(Handler handler)
117             {
118                 final Connector c = getServer().getConnectors()[0];
119                 if (handler instanceof WebAppContext)
120                 {
121                     final WebAppContext webapp = (WebAppContext) handler;
122                     final String msg = String.format("Will deploy a web app at %s:/%s%s%s",
123                                                      "http", c.getHost(),
124                                                      c.getPort() == 80 ? StringUtils.EMPTY : ":" + c.getPort(),
125                                                      webapp.getContextPath());
126 
127                     final File workDir = new File(muleContext.getConfiguration().getWorkingDirectory(),
128                                                   "_exploded_wars" + webapp.getContextPath());
129                     workDir.mkdirs();
130                     webapp.setTempDirectory(workDir);
131                     // TODO extract to a better constant
132                     webapp.setAttribute("muleContext", muleContext);
133 
134                     if (logger.isInfoEnabled())
135                     {
136                         logger.info(StringMessageUtils.getBoilerPlate(msg, '*', 70));
137                     }
138                 }
139                 super.addHandler(handler);
140             }
141         };
142 
143         if (webappsConfiguration != null)
144         {
145             deployer = new WebAppDeployer();
146             String webAppDir = webappsConfiguration.getDirectory();
147             if (StringUtils.isBlank(webAppDir))
148             {
149                 // if none specified, resolve defaults dynamically
150                 final String appDir = muleContext.getRegistry().get(MuleProperties.APP_HOME_DIRECTORY_PROPERTY);
151                 webAppDir = appDir + "/webapps";
152             }
153 
154             if (configFile == null)
155             {
156                 // override only if user hasn't specified one (turn off file-mapped buffer for
157                 // static files to avoid resource locking, makes webapp resources editable on the fly)
158                 final URL muleDefaults = ClassUtils.getResource("org/mule/transport/jetty/webdefault.xml", getClass());
159                 deployer.setDefaultsDescriptor(muleDefaults.toExternalForm());
160             }
161             deployer.setWebAppDir(webAppDir);
162             deployer.setExtract(true);
163             deployer.setParentLoaderPriority(false);
164             deployer.setServerClasses(webappsConfiguration.getServerClasses());
165             deployer.setSystemClasses(webappsConfiguration.getSystemClasses());
166 
167             org.mortbay.jetty.AbstractConnector jettyConnector = createJettyConnector();
168             jettyConnector.setHost(webappsConfiguration.getHost());
169             jettyConnector.setPort(webappsConfiguration.getPort());
170             deployer.setContexts(httpServer);
171             String[] confClasses = new String[]
172             {
173                 // configures webapp's classloader as a child of a Mule app classloader
174                 WebInfConfiguration.class.getName(),
175                 // just to get jetty going, we don't need java ee bindings. inherits annotation processing
176                 DummyJndiConfiguration.class.getName()
177             };
178             deployer.setConfigurationClasses(confClasses);
179 
180             httpServer.addConnector(jettyConnector);
181             httpServer.addLifeCycle(deployer);
182         }
183 
184         initialiseFromConfigFile();
185 
186         try
187         {
188             muleContext.registerListener(new MuleContextNotificationListener<MuleContextNotification>(){
189                 @Override
190                 public void onNotification(MuleContextNotification notification)
191                 {
192                     if (notification.getAction() == MuleContextNotification.CONTEXT_STARTED)
193                     {
194                         //We delay starting until the context has been started since we need the MuleAjaxServlet to initialise first
195                         setInitialStateStopped(false);
196                         try
197                         {
198                             start();
199                             // update the agent displaying webapp urls to the user
200                             final JettyWebappServerAgent agent = (JettyWebappServerAgent) muleContext.getRegistry().lookupAgent(JettyWebappServerAgent.NAME);
201                             if (agent != null)
202                             {
203                                 agent.onJettyConnectorStarted(JettyHttpConnector.this);
204                             }
205                         }
206                         catch (MuleException e)
207                         {
208                             throw new MuleRuntimeException(CoreMessages.failedToStart(getName()), e);
209                         }
210                     }
211                 }
212             });
213         }
214         catch (NotificationException e)
215         {
216             throw new InitialisationException(e, this);
217         }
218     }
219 
220     @SuppressWarnings("unchecked")
221     protected void initialiseFromConfigFile() throws InitialisationException
222     {
223         if (configFile == null)
224         {
225             return;
226         }
227         try
228         {
229             InputStream is = IOUtils.getResourceAsStream(configFile, getClass());
230             XmlConfiguration config = new XmlConfiguration(is);
231 
232             String appHome =
233                 muleContext.getRegistry().lookupObject(MuleProperties.APP_HOME_DIRECTORY_PROPERTY);
234             if (appHome == null)
235             {
236                 // Mule IDE sets app.home as part of the launch config it creates
237                 appHome = System.getProperty(MuleProperties.APP_HOME_DIRECTORY_PROPERTY);
238             }
239 
240             if (appHome != null)
241             {
242                 config.getProperties().put(MuleProperties.APP_HOME_DIRECTORY_PROPERTY, appHome);
243             }
244 
245             config.configure(httpServer);
246         }
247         catch (Exception e)
248         {
249             throw new InitialisationException(e, this);
250         }
251     }
252 
253     /**
254      * Template method to dispose any resources associated with this receiver. There
255      * is not need to dispose the connector as this is already done by the framework
256      */
257     @Override
258     protected void doDispose()
259     {
260         holders.clear();
261     }
262 
263     @Override
264     protected void doStart() throws MuleException
265     {
266         try
267         {
268             httpServer.start();
269 
270             if (deployer != null)
271             {
272                 deployer.start();
273             }
274 
275             for (ConnectorHolder<?, ?> contextHolder : holders.values())
276             {
277                 contextHolder.start();
278             }
279         }
280         catch (Exception e)
281         {
282             throw new LifecycleException(CoreMessages.failedToStart("Jetty Http Receiver"), e, this);
283         }
284     }
285 
286     @Override
287     protected void doStop() throws MuleException
288     {
289         try
290         {
291             httpServer.stop();
292 
293             if (deployer != null)
294             {
295                 deployer.stop();
296             }
297 
298             for (ConnectorHolder<?, ?> connectorRef : holders.values())
299             {
300                 connectorRef.stop();
301             }
302         }
303         catch (Exception e)
304         {
305             throw new LifecycleException(CoreMessages.failedToStop("Jetty Http Receiver"), e, this);
306         }
307     }
308 
309     /**
310      * Template method where any connections should be made for the connector
311      *
312      * @throws Exception
313      */
314     @Override
315     protected void doConnect() throws Exception
316     {
317         //do nothing
318     }
319 
320     /**
321      * Template method where any connected resources used by the connector should be
322      * disconnected
323      *
324      * @throws Exception
325      */
326     @Override
327     protected void doDisconnect() throws Exception
328     {
329         //do nothing
330     }
331 
332      @Override
333     protected MessageReceiver createReceiver(FlowConstruct flowConstruct, InboundEndpoint endpoint) throws Exception
334     {
335         MessageReceiver receiver = super.createReceiver(flowConstruct, endpoint);
336         registerJettyEndpoint(receiver, endpoint);
337         return receiver;
338     }
339 
340     protected org.mortbay.jetty.AbstractConnector createJettyConnector()
341     {
342         return new SelectChannelConnector();
343     }
344 
345     public void unregisterListener(MessageReceiver receiver) throws MuleException
346     {
347         String connectorKey = getHolderKey(receiver.getEndpoint());
348 
349         synchronized (this)
350         {
351             ConnectorHolder connectorRef = holders.get(connectorKey);
352             if (connectorRef != null)
353             {
354                 if (!connectorRef.isReferenced())
355                 {
356                     getHttpServer().removeConnector(connectorRef.getConnector());
357                     holders.remove(connectorKey);
358                     connectorRef.stop();
359                 }
360             }
361         }
362     }
363 
364     public Server getHttpServer()
365     {
366         return httpServer;
367     }
368 
369     public String getConfigFile()
370     {
371         return configFile;
372     }
373 
374     public void setConfigFile(String configFile)
375     {
376         this.configFile = configFile;
377     }
378 
379     public JettyReceiverServlet getReceiverServlet()
380     {
381         return receiverServlet;
382     }
383 
384     public void setReceiverServlet(JettyReceiverServlet receiverServlet)
385     {
386         this.receiverServlet = receiverServlet;
387     }
388 
389     @Override
390     public ReplyToHandler getReplyToHandler(ImmutableEndpoint endpoint)
391     {
392         if (isUseContinuations())
393         {
394             return new JettyContinuationsReplyToHandler(muleContext);
395         }
396         return super.getReplyToHandler(endpoint);
397     }
398 
399     public boolean isUseContinuations()
400     {
401         return useContinuations;
402     }
403 
404     public void setUseContinuations(boolean useContinuations)
405     {
406         this.useContinuations = useContinuations;
407     }
408 
409     ConnectorHolder<? extends MuleReceiverServlet, ? extends JettyHttpMessageReceiver> registerJettyEndpoint(MessageReceiver receiver, InboundEndpoint endpoint) throws MuleException
410     {
411         // Make sure that there is a connector for the requested endpoint.
412         String connectorKey = getHolderKey(endpoint);
413 
414         ConnectorHolder holder;
415 
416         synchronized (this)
417         {
418             holder = holders.get(connectorKey);
419             if (holder == null)
420             {
421                 Connector connector = createJettyConnector();
422 
423                 connector.setPort(endpoint.getEndpointURI().getPort());
424                 String host = endpoint.getEndpointURI().getHost();
425                 if ("localhost".equalsIgnoreCase(host))
426                 {
427                     // We default to binding the port on all local network interfaces if localhost is set.  This
428                     // is the same behaviour as all other socket connectors in Mule
429                     host = "0.0.0.0";
430                 }
431                 connector.setHost(host);
432                 getHttpServer().addConnector(connector);
433 
434                 holder = createContextHolder(connector, receiver.getEndpoint(), receiver);
435                 holders.put(connectorKey, holder);
436                 if(isStarted())
437                 {
438                     holder.start();
439                 }
440             }
441             else
442             {
443                 holder.addReceiver(receiver);
444             }
445         }
446         return holder;
447     }
448 
449     protected ConnectorHolder createContextHolder(Connector connector, InboundEndpoint endpoint, MessageReceiver receiver)
450     {
451         return new MuleReceiverConnectorHolder(connector, (JettyReceiverServlet) createServlet(connector, endpoint), (JettyHttpMessageReceiver)receiver);
452     }
453 
454     protected Servlet createServlet(Connector connector, ImmutableEndpoint endpoint)
455     {
456         HttpServlet servlet;
457         if (getReceiverServlet() == null)
458         {
459             if(isUseContinuations())
460             {
461                 servlet = new JettyContinuationsReceiverServlet();
462             }
463             else
464             {
465                 servlet = new JettyReceiverServlet();
466             }
467         }
468         else
469         {
470             servlet = getReceiverServlet();
471         }
472 
473         String path = endpoint.getEndpointURI().getPath();
474         if(StringUtils.isBlank(path))
475         {
476             path = ROOT;
477         }
478 
479         ContextHandlerCollection handlerCollection = new ContextHandlerCollection();
480         Context context = new Context(handlerCollection, ROOT, Context.NO_SECURITY);
481         context.setConnectorNames(new String[]{connector.getName()});
482         context.addEventListener(new MuleServletContextListener(muleContext, getName()));
483 
484         if (resourceBase != null)
485         {
486             Context resourceContext = new Context(handlerCollection, path, Context.NO_SECURITY);
487             resourceContext.setResourceBase(resourceBase);
488         }
489 
490         context.addServlet(JarResourceServlet.class, JarResourceServlet.DEFAULT_PATH_SPEC);
491 
492         ServletHolder holder = new ServletHolder();
493         holder.setServlet(servlet);
494         context.addServlet(holder, "/*");
495         getHttpServer().addHandler(handlerCollection);
496         return servlet;
497     }
498 
499     protected String getHolderKey(ImmutableEndpoint endpoint)
500     {
501         return endpoint.getProtocol() + ":" + endpoint.getEndpointURI().getHost() + ":" + endpoint.getEndpointURI().getPort();
502     }
503 
504     public class MuleReceiverConnectorHolder extends AbstractConnectorHolder<JettyReceiverServlet, JettyHttpMessageReceiver>
505     {
506         List<MessageReceiver> messageReceivers = new ArrayList<MessageReceiver>();
507 
508         public MuleReceiverConnectorHolder(Connector connector, JettyReceiverServlet servlet, JettyHttpMessageReceiver receiver)
509         {
510             super(connector, servlet, receiver);
511             addReceiver(receiver);
512         }
513 
514         @Override
515         public boolean isReferenced()
516         {
517             return messageReceivers.size() > 0;
518         }
519 
520         @Override
521         public void addReceiver(JettyHttpMessageReceiver receiver)
522         {
523             messageReceivers.add(receiver);
524             if(started)
525             {
526                 getServlet().addReceiver(receiver);
527             }
528         }
529 
530         @Override
531         public void removeReceiver(JettyHttpMessageReceiver receiver)
532         {
533             messageReceivers.remove(receiver);
534             getServlet().removeReceiver(receiver);
535         }
536 
537         @Override
538         public void start() throws MuleException
539         {
540             super.start();
541 
542             for (MessageReceiver receiver : messageReceivers)
543             {
544                 servlet.addReceiver(receiver);
545             }
546         }
547 
548         @Override
549         public void stop() throws MuleException
550         {
551             super.stop();
552 
553             for (MessageReceiver receiver : messageReceivers)
554             {
555                 servlet.removeReceiver(receiver);
556             }
557         }
558     }
559 
560     public String getResourceBase()
561     {
562         return resourceBase;
563     }
564 
565     public void setResourceBase(String resourceBase)
566     {
567         this.resourceBase = resourceBase;
568     }
569 
570     public WebappsConfiguration getWebappsConfiguration()
571     {
572         return webappsConfiguration;
573     }
574 
575     public void setWebappsConfiguration(WebappsConfiguration webappsConfiguration)
576     {
577         this.webappsConfiguration = webappsConfiguration;
578     }
579 
580     /**
581      * A helper method to differentiate between jetty-based connectors which can host full wars and ones which can't.
582      */
583     public boolean canHostFullWars()
584     {
585         return true;
586     }
587 
588     /**
589      * A helper class to let jetty startup, we don't bind java ee objects like java:comp/UserTransaction.
590      */
591     public static class DummyJndiConfiguration extends Configuration
592     {
593 
594         public DummyJndiConfiguration() throws ClassNotFoundException
595         {
596         }
597 
598         @Override
599         public void bindUserTransaction() throws Exception
600         {
601             // no-op
602         }
603 
604         @Override
605         protected void lockCompEnv() throws Exception
606         {
607             // no-op
608         }
609     }
610 }