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