View Javadoc

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