View Javadoc

1   /*
2    * $Id: AjaxConnector.java 19939 2010-10-15 15:34:05Z 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  package org.mule.transport.ajax.embedded;
11  
12  import org.mule.api.MuleContext;
13  import org.mule.api.MuleException;
14  import org.mule.api.construct.FlowConstruct;
15  import org.mule.api.endpoint.EndpointBuilder;
16  import org.mule.api.endpoint.ImmutableEndpoint;
17  import org.mule.api.endpoint.InboundEndpoint;
18  import org.mule.api.lifecycle.InitialisationException;
19  import org.mule.api.transport.MessageReceiver;
20  import org.mule.api.transport.ReplyToHandler;
21  import org.mule.transport.ajax.AjaxMessageReceiver;
22  import org.mule.transport.ajax.AjaxMuleMessageFactory;
23  import org.mule.transport.ajax.AjaxReplyToHandler;
24  import org.mule.transport.ajax.BayeuxAware;
25  import org.mule.transport.ajax.container.MuleAjaxServlet;
26  import org.mule.transport.ajax.i18n.AjaxMessages;
27  import org.mule.transport.servlet.JarResourceServlet;
28  import org.mule.transport.servlet.MuleServletContextListener;
29  import org.mule.transport.servlet.jetty.JettyHttpsConnector;
30  import org.mule.util.StringUtils;
31  
32  import java.net.URL;
33  import java.util.Map;
34  
35  import javax.servlet.Servlet;
36  
37  import org.mortbay.cometd.AbstractBayeux;
38  import org.mortbay.cometd.continuation.ContinuationCometdServlet;
39  import org.mortbay.jetty.AbstractConnector;
40  import org.mortbay.jetty.Connector;
41  import org.mortbay.jetty.handler.ContextHandlerCollection;
42  import org.mortbay.jetty.nio.SelectChannelConnector;
43  import org.mortbay.jetty.servlet.Context;
44  import org.mortbay.jetty.servlet.DefaultServlet;
45  import org.mortbay.jetty.servlet.ServletHolder;
46  
47  /**
48   * Creates an 'embedded' Ajax server using Jetty and allows Mule to receiver and send events
49   * to browsers. The browser will need to use the <pre>mule.js</pre> class to publish and
50   * subscribe events.
51   *
52   * Note that a {@link @RESOURCE_BASE_PROPERTY} can be set on the ajax endpoint that provides the location of any web application resources such
53   * as html pages
54   */
55  public class AjaxConnector extends JettyHttpsConnector implements BayeuxAware
56  {
57      public static final String PROTOCOL = "ajax";
58  
59      public static final String CHANNEL_PROPERTY = "channel";
60  
61      public static final String AJAX_PATH_SPEC = "/ajax/*";
62  
63      public static final String COMETD_CLIENT = "cometd.client";
64  
65      /**
66       * This is the key that's used to retrieve the reply to destination from a {@link Map} that's
67       * passed into {@link AjaxMuleMessageFactory}.
68       */
69      public static final String REPLYTO_PARAM = "replyTo";
70  
71      private URL serverUrl;
72  
73      /**
74       * The client side poll timeout in milliseconds (default 0). How long a client
75       * will wait between reconnects
76       */
77      private int interval = INT_VALUE_NOT_SET;
78  
79      /**
80       * The max client side poll timeout in milliseconds (default 30000). A client
81       * will be removed if a connection is not received in this time.
82       */
83      private int maxInterval = INT_VALUE_NOT_SET;
84  
85      /**
86       * The client side poll timeout if multiple connections are detected from the
87       * same browser (default 1500).
88       */
89      private int multiFrameInterval = INT_VALUE_NOT_SET;
90  
91      /**
92       * 0=none, 1=info, 2=debug
93       */
94      private int logLevel = INT_VALUE_NOT_SET;
95  
96      /**
97       * The server side poll timeout in milliseconds (default 250000). This is how long
98       * the server will hold a reconnect request before responding.
99       */
100     private int timeout = INT_VALUE_NOT_SET;
101 
102     /**
103      * If "true" (default) then the server will accept JSON wrapped in a comment and
104      * will generate JSON wrapped in a comment. This is a defence against Ajax Hijacking.
105      */
106     private boolean jsonCommented = true;
107 
108     /**
109      * TODO SUPPORT FILTERS
110      * the location of a JSON file describing {@link org.cometd.DataFilter} instances to be installed
111      */
112     private String filters;
113 
114     /**
115      * If true, the current request is made available via the
116      * {@link AbstractBayeux#getCurrentRequest()} method
117      */
118     private boolean requestAvailable = true;
119 
120     /**
121      * true if published messages are delivered directly to subscribers (default).
122      * If false, a message copy is created with only supported fields (default true).
123      */
124     private boolean directDeliver = true;
125 
126     /**
127      * The number of message refs at which the a single message response will be
128      * cached instead of being generated for every client delivered to. Done to optimize
129      * a single message being sent to multiple clients.
130      */
131     private int refsThreshold = INT_VALUE_NOT_SET;
132 
133     private ContinuationCometdServlet servlet;
134 
135     private String resourceBase;
136 
137     public AjaxConnector(MuleContext context)
138     {
139         super(context);
140         unregisterSupportedProtocol("http");
141         unregisterSupportedProtocol("https");
142         unregisterSupportedProtocol("jetty-ssl");
143         unregisterSupportedProtocol("jetty");
144         setInitialStateStopped(true);
145     }
146 
147     @Override
148     public String getProtocol()
149     {
150         return PROTOCOL;
151     }
152 
153     public URL getServerUrl()
154     {
155         return serverUrl;
156     }
157 
158     public void setServerUrl(URL serverUrl)
159     {
160         this.serverUrl = serverUrl;
161     }
162 
163     @Override
164     protected void doInitialise() throws InitialisationException
165     {
166         if (serverUrl==null)
167         {
168             throw new InitialisationException(AjaxMessages.serverUrlNotDefined(), this);
169         }
170         super.doInitialise();
171         try
172         {
173             createEmbeddedServer();
174         }
175         catch (Exception e)
176         {
177             throw new InitialisationException(e, this);
178         }
179     }
180 
181     @Override
182     protected void doStart() throws MuleException
183     {
184         super.doStart();
185         for (MessageReceiver receiver : receivers.values())
186         {
187             ((AjaxMessageReceiver)receiver).setBayeux(getBayeux());
188         }
189     }
190 
191     @Override
192     protected void validateSslConfig() throws InitialisationException
193     {
194         if (serverUrl.getProtocol().equals("https"))
195         {
196             super.validateSslConfig();
197         }
198     }
199 
200     @Override
201     public ReplyToHandler getReplyToHandler(ImmutableEndpoint endpoint)
202     {
203         return new AjaxReplyToHandler(getDefaultResponseTransformers(endpoint), this);
204     }
205 
206     void createEmbeddedServer() throws MuleException
207     {
208         Connector connector = createJettyConnector();
209 
210         connector.setPort(serverUrl.getPort());
211         connector.setHost(serverUrl.getHost());
212         
213         getHttpServer().addConnector(connector);
214         EndpointBuilder builder = muleContext.getRegistry().lookupEndpointFactory().getEndpointBuilder(serverUrl.toString());
215 
216         servlet = (ContinuationCometdServlet)createServlet(connector, builder.buildInboundEndpoint());
217     }
218 
219     @Override
220     public Servlet createServlet(Connector connector, ImmutableEndpoint endpoint)
221     {
222         ContinuationCometdServlet ajaxServlet = new MuleAjaxServlet();
223 
224         String path = endpoint.getEndpointURI().getPath();
225         if (StringUtils.isBlank(path))
226         {
227             path = ROOT;
228         }
229 
230         ContextHandlerCollection handlerCollection = new ContextHandlerCollection();
231         Context root = new Context(handlerCollection, ROOT, Context.NO_SECURITY);
232         root.setConnectorNames(new String[]{connector.getName()});
233         root.addEventListener(new MuleServletContextListener(muleContext, getName()));
234 
235         if (!ROOT.equals(path))
236         {
237             Context resourceContext = new Context(handlerCollection, path, Context.NO_SECURITY);
238             populateContext(resourceContext);
239 
240         }
241         else
242         {
243             populateContext(root);
244         }
245 
246         //Add ajax to root
247         ServletHolder holder = new ServletHolder();
248         holder.setServlet(ajaxServlet);
249         root.addServlet(holder, AJAX_PATH_SPEC);
250 
251         if (getInterval() != INT_VALUE_NOT_SET) holder.setInitParameter("interval", Integer.toString(getInterval()));
252         holder.setInitParameter("JSONCommented", Boolean.toString(isJsonCommented()));
253         if (getLogLevel() != INT_VALUE_NOT_SET) holder.setInitParameter("logLevel", Integer.toString(getLogLevel()));
254         if (getMaxInterval() != INT_VALUE_NOT_SET) holder.setInitParameter("maxInterval", Integer.toString(getMaxInterval()));
255         if (getMultiFrameInterval() != INT_VALUE_NOT_SET) holder.setInitParameter("multiFrameInterval", (Integer.toString(getMultiFrameInterval())));
256         if (getTimeout() != INT_VALUE_NOT_SET) holder.setInitParameter("timeout", Integer.toString(getTimeout()));
257         if (getRefsThreshold() != INT_VALUE_NOT_SET) holder.setInitParameter("refsThreshold", Integer.toString(getRefsThreshold()));
258         holder.setInitParameter("requestAvailable", Boolean.toString(isRequestAvailable()));
259 
260 
261         this.getHttpServer().addHandler(handlerCollection);
262         return ajaxServlet;
263     }
264 
265     protected void populateContext(Context context)
266     {
267         context.addServlet(DefaultServlet.class, ROOT);
268         context.addServlet(JarResourceServlet.class, JarResourceServlet.DEFAULT_PATH_SPEC);
269 
270         String base = getResourceBase();
271         if (base != null)
272         {
273             context.setResourceBase(base);
274         }
275     }
276 
277     @Override
278     protected AbstractConnector createJettyConnector()
279     {
280         if (serverUrl.getProtocol().equals("https"))
281         {
282             return super.createJettyConnector();
283         }
284         else
285         {
286             return new SelectChannelConnector();
287         }
288     }
289 
290     public AbstractBayeux getBayeux( )
291     {
292         return servlet.getBayeux();
293     }
294 
295     public void setBayeux(AbstractBayeux bayeux)
296     {
297         //Ignore
298     }
299 
300     @Override
301     protected MessageReceiver createReceiver(FlowConstruct flowConstruct, InboundEndpoint endpoint) throws Exception
302     {
303         MessageReceiver receiver = getServiceDescriptor().createMessageReceiver(this, flowConstruct, endpoint);
304         //If the connector has not started yet, the Bayeux object will still be null
305         ((AjaxMessageReceiver) receiver).setBayeux(getBayeux());
306         return receiver;
307     }
308 
309     public String getResourceBase()
310     {
311         return resourceBase;
312     }
313 
314     public void setResourceBase(String resourceBase)
315     {
316         this.resourceBase = resourceBase;
317     }
318 
319     public int getInterval()
320     {
321         return interval;
322     }
323 
324     public void setInterval(int interval)
325     {
326         this.interval = interval;
327     }
328 
329     public int getMaxInterval()
330     {
331         return maxInterval;
332     }
333 
334     public void setMaxInterval(int maxInterval)
335     {
336         this.maxInterval = maxInterval;
337     }
338 
339     public int getMultiFrameInterval()
340     {
341         return multiFrameInterval;
342     }
343 
344     public void setMultiFrameInterval(int multiFrameInterval)
345     {
346         this.multiFrameInterval = multiFrameInterval;
347     }
348 
349     public int getLogLevel()
350     {
351         return logLevel;
352     }
353 
354     public void setLogLevel(int logLevel)
355     {
356         this.logLevel = logLevel;
357     }
358 
359     public int getTimeout()
360     {
361         return timeout;
362     }
363 
364     public void setTimeout(int timeout)
365     {
366         this.timeout = timeout;
367     }
368 
369     public boolean isJsonCommented()
370     {
371         return jsonCommented;
372     }
373 
374     public void setJsonCommented(boolean jsonCommented)
375     {
376         this.jsonCommented = jsonCommented;
377     }
378 
379     public String getFilters()
380     {
381         return filters;
382     }
383 
384     public void setFilters(String filters)
385     {
386         this.filters = filters;
387     }
388 
389     public boolean isRequestAvailable()
390     {
391         return requestAvailable;
392     }
393 
394     public void setRequestAvailable(boolean requestAvailable)
395     {
396         this.requestAvailable = requestAvailable;
397     }
398 
399     public boolean isDirectDeliver()
400     {
401         return directDeliver;
402     }
403 
404     public void setDirectDeliver(boolean directDeliver)
405     {
406         this.directDeliver = directDeliver;
407     }
408 
409     public int getRefsThreshold()
410     {
411         return refsThreshold;
412     }
413 
414     public void setRefsThreshold(int refsThreshold)
415     {
416         this.refsThreshold = refsThreshold;
417     }
418 }