View Javadoc

1   /*
2    * $Id: AjaxConnector.java 20385 2010-11-29 20:25:26Z dfeist $
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     public AjaxConnector(MuleContext context)
136     {
137         super(context);
138         unregisterSupportedProtocol("http");
139         unregisterSupportedProtocol("https");
140         unregisterSupportedProtocol("jetty-ssl");
141         unregisterSupportedProtocol("jetty");
142         setInitialStateStopped(true);
143     }
144 
145     @Override
146     public String getProtocol()
147     {
148         return PROTOCOL;
149     }
150 
151     public URL getServerUrl()
152     {
153         return serverUrl;
154     }
155 
156     public void setServerUrl(URL serverUrl)
157     {
158         this.serverUrl = serverUrl;
159     }
160 
161     @Override
162     protected void doInitialise() throws InitialisationException
163     {
164         if (serverUrl==null)
165         {
166             throw new InitialisationException(AjaxMessages.serverUrlNotDefined(), this);
167         }
168         super.doInitialise();
169         try
170         {
171             createEmbeddedServer();
172         }
173         catch (Exception e)
174         {
175             throw new InitialisationException(e, this);
176         }
177     }
178 
179     @Override
180     protected void doStart() throws MuleException
181     {
182         super.doStart();
183         for (MessageReceiver receiver : receivers.values())
184         {
185             ((AjaxMessageReceiver)receiver).setBayeux(getBayeux());
186         }
187     }
188 
189     @Override
190     protected void validateSslConfig() throws InitialisationException
191     {
192         if (serverUrl.getProtocol().equals("https"))
193         {
194             super.validateSslConfig();
195         }
196     }
197 
198     @Override
199     public ReplyToHandler getReplyToHandler(ImmutableEndpoint endpoint)
200     {
201         return new AjaxReplyToHandler(getDefaultResponseTransformers(endpoint), this);
202     }
203 
204     void createEmbeddedServer() throws MuleException
205     {
206         Connector connector = createJettyConnector();
207 
208         connector.setPort(serverUrl.getPort());
209         connector.setHost(serverUrl.getHost());
210         
211         getHttpServer().addConnector(connector);
212         EndpointBuilder builder = muleContext.getEndpointFactory().getEndpointBuilder(serverUrl.toString());
213 
214         servlet = (ContinuationCometdServlet)createServlet(connector, builder.buildInboundEndpoint());
215     }
216 
217     @Override
218     public Servlet createServlet(Connector connector, ImmutableEndpoint endpoint)
219     {
220         ContinuationCometdServlet ajaxServlet = new MuleAjaxServlet();
221 
222         String path = endpoint.getEndpointURI().getPath();
223         if (StringUtils.isBlank(path))
224         {
225             path = ROOT;
226         }
227 
228         ContextHandlerCollection handlerCollection = new ContextHandlerCollection();
229         Context root = new Context(handlerCollection, ROOT, Context.NO_SECURITY);
230         root.setConnectorNames(new String[]{connector.getName()});
231         root.addEventListener(new MuleServletContextListener(muleContext, getName()));
232 
233         if (!ROOT.equals(path))
234         {
235             Context resourceContext = new Context(handlerCollection, path, Context.NO_SECURITY);
236             populateContext(resourceContext);
237 
238         }
239         else
240         {
241             populateContext(root);
242         }
243 
244         //Add ajax to root
245         ServletHolder holder = new ServletHolder();
246         holder.setServlet(ajaxServlet);
247         root.addServlet(holder, AJAX_PATH_SPEC);
248 
249         if (getInterval() != INT_VALUE_NOT_SET) holder.setInitParameter("interval", Integer.toString(getInterval()));
250         holder.setInitParameter("JSONCommented", Boolean.toString(isJsonCommented()));
251         if (getLogLevel() != INT_VALUE_NOT_SET) holder.setInitParameter("logLevel", Integer.toString(getLogLevel()));
252         if (getMaxInterval() != INT_VALUE_NOT_SET) holder.setInitParameter("maxInterval", Integer.toString(getMaxInterval()));
253         if (getMultiFrameInterval() != INT_VALUE_NOT_SET) holder.setInitParameter("multiFrameInterval", (Integer.toString(getMultiFrameInterval())));
254         if (getTimeout() != INT_VALUE_NOT_SET) holder.setInitParameter("timeout", Integer.toString(getTimeout()));
255         if (getRefsThreshold() != INT_VALUE_NOT_SET) holder.setInitParameter("refsThreshold", Integer.toString(getRefsThreshold()));
256         holder.setInitParameter("requestAvailable", Boolean.toString(isRequestAvailable()));
257 
258 
259         this.getHttpServer().addHandler(handlerCollection);
260         return ajaxServlet;
261     }
262 
263     protected void populateContext(Context context)
264     {
265         context.addServlet(DefaultServlet.class, ROOT);
266         context.addServlet(JarResourceServlet.class, JarResourceServlet.DEFAULT_PATH_SPEC);
267 
268         String base = getResourceBase();
269         if (base != null)
270         {
271             context.setResourceBase(base);
272         }
273     }
274 
275     @Override
276     protected AbstractConnector createJettyConnector()
277     {
278         if (serverUrl.getProtocol().equals("https"))
279         {
280             return super.createJettyConnector();
281         }
282         else
283         {
284             return new SelectChannelConnector();
285         }
286     }
287 
288     public AbstractBayeux getBayeux( )
289     {
290         return servlet.getBayeux();
291     }
292 
293     public void setBayeux(AbstractBayeux bayeux)
294     {
295         //Ignore
296     }
297 
298     @Override
299     protected MessageReceiver createReceiver(FlowConstruct flowConstruct, InboundEndpoint endpoint) throws Exception
300     {
301         MessageReceiver receiver = getServiceDescriptor().createMessageReceiver(this, flowConstruct, endpoint);
302         //If the connector has not started yet, the Bayeux object will still be null
303         ((AjaxMessageReceiver) receiver).setBayeux(getBayeux());
304         return receiver;
305     }
306 
307 
308     public int getInterval()
309     {
310         return interval;
311     }
312 
313     public void setInterval(int interval)
314     {
315         this.interval = interval;
316     }
317 
318     public int getMaxInterval()
319     {
320         return maxInterval;
321     }
322 
323     public void setMaxInterval(int maxInterval)
324     {
325         this.maxInterval = maxInterval;
326     }
327 
328     public int getMultiFrameInterval()
329     {
330         return multiFrameInterval;
331     }
332 
333     public void setMultiFrameInterval(int multiFrameInterval)
334     {
335         this.multiFrameInterval = multiFrameInterval;
336     }
337 
338     public int getLogLevel()
339     {
340         return logLevel;
341     }
342 
343     public void setLogLevel(int logLevel)
344     {
345         this.logLevel = logLevel;
346     }
347 
348     public int getTimeout()
349     {
350         return timeout;
351     }
352 
353     public void setTimeout(int timeout)
354     {
355         this.timeout = timeout;
356     }
357 
358     public boolean isJsonCommented()
359     {
360         return jsonCommented;
361     }
362 
363     public void setJsonCommented(boolean jsonCommented)
364     {
365         this.jsonCommented = jsonCommented;
366     }
367 
368     public String getFilters()
369     {
370         return filters;
371     }
372 
373     public void setFilters(String filters)
374     {
375         this.filters = filters;
376     }
377 
378     public boolean isRequestAvailable()
379     {
380         return requestAvailable;
381     }
382 
383     public void setRequestAvailable(boolean requestAvailable)
384     {
385         this.requestAvailable = requestAvailable;
386     }
387 
388     public boolean isDirectDeliver()
389     {
390         return directDeliver;
391     }
392 
393     public void setDirectDeliver(boolean directDeliver)
394     {
395         this.directDeliver = directDeliver;
396     }
397 
398     public int getRefsThreshold()
399     {
400         return refsThreshold;
401     }
402 
403     public void setRefsThreshold(int refsThreshold)
404     {
405         this.refsThreshold = refsThreshold;
406     }
407 }