001 
002 /*
003  *  JScripter Standard 1.0 - To Script In Java
004  *  Copyright (C) 2008-2011  J.J.Liu<jianjunliu@126.com> <http://www.jscripter.org>
005  *  
006  *  This program is free software: you can redistribute it and/or modify
007  *  it under the terms of the GNU Affero General Public License as published by
008  *  the Free Software Foundation, either version 3 of the License, or
009  *  (at your option) any later version.
010  *  
011  *  This program is distributed in the hope that it will be useful,
012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014  *  GNU Affero General Public License for more details.
015  *  
016  *  You should have received a copy of the GNU Affero General Public License
017  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
018  */
019 
020 package jsx.http;
021 
022 import js.Function;
023 import js.Id;
024 import js.Initializer;
025 import js.Js;
026 import js.ObjectLike;
027 import js.user.JsXMLHttpRequest;
028 import jsx.Configurable;
029 import jsx.Source;
030 import jsx.Timeout;
031 import jsx.client.Browser;
032 import jsx.core.Variables;
033 import jsx.http.event.HTTPAborted;
034 import jsx.http.event.HTTPLoaded;
035 import jsx.http.event.HTTPOpen;
036 import jsx.http.event.HTTPReceiving;
037 import jsx.http.event.HTTPSent;
038 import jsx.http.event.HTTPState;
039 
040 /**
041  * <p>Facilitates the administration of asynchronous HTTP connections and transmissions 
042  * eliminating browser dependencies.</p>
043  * <p>Note that, an {@link Ajax} object is an event source and therefore {@link Configurable}. 
044  * An object of this class fires HTTP events of types extended from {@link HTTPState}. 
045  * This class is quite intuitive and handy for creating Ajax applications in Java manner.</p>
046  * <p>A use of this class involves three steps in a logical sense:
047  * <ol>
048  * <li>Create an object of this class with the default constructor {@link Ajax#Ajax()}.</li>
049  * <li>Add necessary event listeners to the created object preparing to receive data from 
050  * the {@link JsXMLHttpRequest} object obtained with its {@link Ajax#http()} method.</li>
051  * <li>Call {@link Ajax#get(String, String, String)}, {@link Ajax#get(String)}, {@link Ajax#post(String, Object)} 
052  * or {@link Ajax#post(String, Object, String, String)} to send request asynchronously.</li>
053  * </ol></p>
054  * 
055  * @author <a href="mailto:jianjunliu@126.com">J.J.Liu (Jianjun Liu)</a> at <a href="http://www.jscripter.org" target="_blank">http://www.jscripter.org</a>
056  * @see Http
057  */
058 public final class Ajax extends Source
059 {
060     private final static Id<Number> TIMEOUT = new Id<Number>();
061     private final static Id<JsXMLHttpRequest> HTTP = new Id<JsXMLHttpRequest>();
062 
063     /**
064      * <p>The default constructor to create an {@link Ajax} object with the underlying 
065      * asynchronous HTTP request created and high level event mechanism set up.</p>
066      * @since 1.0
067      */
068     public Ajax() {
069         super(new Initializer().var());
070         init(this, !Browser.isIE);
071     }
072     /**
073      * <p>The typical constructor which simply invokes the typical constructor of the 
074      * superclass passing the specified initializing object as argument.</p>
075      * @param ini The initializing object that can also be created with an object literal. 
076      * @since 1.0
077      */
078     protected Ajax(ObjectLike ini) {
079         super(ini);
080     }
081 
082     private static final void init(final Ajax ajax, boolean init) {
083         if (init) {
084             ini(ajax).var(HTTP, Http.create());
085             Http.onReadyStateChange(ajax.http(), new Function<Void>() {
086                 @Override
087                 protected Void function(Object jsthis, Call<Void> callee) {
088                     JsXMLHttpRequest http = ajax.http();
089                     switch (Http.readyState(http).intValue())
090                     {
091                         case (int)JsXMLHttpRequest.OPEN :
092                             setTimeout(ajax);
093                             ajax.fire(new HTTPOpen());
094                             break;
095                         case (int)JsXMLHttpRequest.SENT :
096                             ajax.fire(new HTTPSent());
097                             break;
098                         case (int)JsXMLHttpRequest.RECEIVING :
099                             ajax.fire(new HTTPReceiving());
100                             break;
101                         case (int)JsXMLHttpRequest.LOADED :
102                             ajax.fire(new HTTPLoaded());
103                             break;
104                     }
105                     return null;
106                 }
107             }.var());
108         }
109     }
110 
111     /**
112      * <p>Gets the underlying HTTP request object.</p>
113      * @return The underlying HTTP request object. This object is returned for retrieving 
114      * its data properties only. Use the object with caution, not to break {@link Ajax}.
115      * @since 1.0
116      */
117     public final JsXMLHttpRequest http() {
118         return ini(this).var(HTTP);
119     }
120 
121     private static final JsXMLHttpRequest open(Ajax ajax, String method, String url) {
122         init(ajax, Browser.isIE);
123         JsXMLHttpRequest http = ajax.http();
124         Http.open(http, method, url);
125         return http;
126     }
127 
128     private static final JsXMLHttpRequest open(
129             Ajax ajax, String method, String url, String username, String password) {
130         init(ajax, Browser.isIE);
131         JsXMLHttpRequest http = ajax.http();
132         Http.open(http, method, url, true, username, password);
133         return http;
134     }
135 
136     private static final void send(JsXMLHttpRequest http, Object body) {
137         if (Http.isOpen(http)) {
138             Http.send(http, body);
139         }
140     }
141 
142     /**
143      * <p>Opens the underlying HTTP request if it is not opened yet and sends an HTTP-GET 
144      * to the specified URL through it.</p>
145      * <p>Note that, this method returns immediately with nothing. Get the actual responses 
146      * in the event listeners that should have been registered with the current event source 
147      * object.</p>
148      * @param url The URL that is the subject of the request. Most browsers impose a 
149      * same-origin security policy and require that this URL have the same host name and 
150      * port as the document that contains the script. Relative URLs are resolved in the 
151      * normal way, using the URL of the document that contains the script.
152      * @since 1.0
153      */
154     public final void get(String url) {
155         send(open(this, "GET", url), null);
156     }
157 
158     /**
159      * <p>Opens the underlying HTTP request if it is not opened yet and sends an HTTP-GET 
160      * to the specified URL through it with explicit credentials.</p>
161      * <p>Note that, this method returns immediately with nothing. Get the actual responses 
162      * in the event listeners that should have been registered with the current event source 
163      * object.</p>
164      * @param url The URL that is the subject of the request. Most browsers impose a 
165      * same-origin security policy and require that this URL have the same host name and 
166      * port as the document that contains the script. Relative URLs are resolved in the 
167      * normal way, using the URL of the document that contains the script.
168      * @param username An optional argument specifying authorization user name for use 
169      * with URLs that require authorization. If specified, it overrides the user name 
170      * specified in the URL itself.
171      * @param password An optional argument specifying authorization password for use 
172      * with URLs that require authorization. If specified, it overrides the password 
173      * specified in the URL itself.
174      * @since 1.0
175      */
176     public final void get(String url, String username, String password) {
177         send(open(this, "GET", url, username, password), null);
178     }
179 
180     /**
181      * <p>Opens the underlying HTTP request if it is not opened yet and sends an HTTP-HEAD 
182      * to the specified URL through it.</p>
183      * <p>Note that, this method returns immediately with nothing. Get the actual responses 
184      * in the event listeners that should have been registered with the current event source 
185      * object.</p>
186      * @param url The URL that is the subject of the request. Most browsers impose a 
187      * same-origin security policy and require that this URL have the same host name and 
188      * port as the document that contains the script. Relative URLs are resolved in the 
189      * normal way, using the URL of the document that contains the script.
190      * @since 1.0
191      */
192     public final void head(String url) {
193         send(open(this, "HEAD", url), null);
194     }
195 
196     /**
197      * <p>Opens the underlying HTTP request if it is not opened yet and sends an HTTP-HEAD 
198      * to the specified URL through it with explicit credentials.</p>
199      * <p>Note that, this method returns immediately with nothing. Get the actual responses 
200      * in the event listeners that should have been registered with the current event source 
201      * object.</p>
202      * @param url The URL that is the subject of the request. Most browsers impose a 
203      * same-origin security policy and require that this URL have the same host name and 
204      * port as the document that contains the script. Relative URLs are resolved in the 
205      * normal way, using the URL of the document that contains the script.
206      * @param username An optional argument specifying authorization user name for use 
207      * with URLs that require authorization. If specified, it overrides the user name 
208      * specified in the URL itself.
209      * @param password An optional argument specifying authorization password for use 
210      * with URLs that require authorization. If specified, it overrides the password 
211      * specified in the URL itself.
212      * @since 1.0
213      */
214     public final void head(String url, String username, String password) {
215         send(open(this, "HEAD", url, username, password), null);
216     }
217 
218     /**
219      * <p>Opens the underlying HTTP request if it is not opened yet and sends an HTTP-POST 
220      * to the specified URL through it.</p>
221      * <p>Note that, this method returns immediately with nothing. Get the actual responses 
222      * in the event listeners that should have been registered with the current event source 
223      * object.</p>
224      * @param url The URL that is the subject of the request. Most browsers impose a 
225      * same-origin security policy and require that this URL have the same host name and 
226      * port as the document that contains the script. Relative URLs are resolved in the 
227      * normal way, using the URL of the document that contains the script.
228      * @param body Specifies the body of the POST request, as a string or 
229      * {@link js.user.JsDocument} object.
230      * @since 1.0
231      */
232     public final void post(String url, Object body) {
233         send(open(this, "POST", url), body);
234     }
235 
236     /**
237      * <p>Opens the underlying HTTP request if it is not opened yet and sends an HTTP-POST 
238      * to the specified URL through it with explicit credentials.</p>
239      * <p>Note that, this method returns immediately with nothing. Get the actual responses 
240      * in the event listeners that should have been registered with the current event source 
241      * object.</p>
242      * @param url The URL that is the subject of the request. Most browsers impose a 
243      * same-origin security policy and require that this URL have the same host name and 
244      * port as the document that contains the script. Relative URLs are resolved in the 
245      * normal way, using the URL of the document that contains the script.
246      * @param body Specifies the body of the POST request, as a string or 
247      * {@link js.user.JsDocument} object.
248      * @param username An optional argument specifying authorization user name for use 
249      * with URLs that require authorization. If specified, it overrides the user name 
250      * specified in the URL itself.
251      * @param password An optional argument specifying authorization password for use 
252      * with URLs that require authorization. If specified, it overrides the password 
253      * specified in the URL itself.
254      * @since 1.0
255      */
256     public final void post(String url, Object body, String username, String password) {
257         send(open(this, "POST", url, username, password), body);
258     }
259 
260     /**
261      * <p>Cancels the underlying HTTP request of the current {@link Ajax} object, closing 
262      * connections and stopping any pending network activity.</p>
263      * <p>This method resets the {@link JsXMLHttpRequest} object to a {@link JsXMLHttpRequest#readyState} 
264      * of {@link JsXMLHttpRequest#UNINITIALIZED} and aborts any pending network activity. 
265      * You might call this method, for example, if a request has taken too long, and the 
266      * response is no longer necessary.</p>
267      * <p>This method fires an {@link HTTPAborted} event from the current {@link Ajax} 
268      * object if the underlying HTTP request had been open but not loaded and has had 
269      * to be aborted.</p>
270      * @since 1.0
271      * @see Http#abort(JsXMLHttpRequest)
272      * @see JsXMLHttpRequest#abort()
273      */
274     public final void abort() {
275         JsXMLHttpRequest http = http();
276         int rs = Http.readyState(http).intValue();
277         if (rs > JsXMLHttpRequest.UNINITIALIZED && rs < JsXMLHttpRequest.LOADED) {
278             Http.abort(http);
279             if (Http.readyState(http).intValue() == JsXMLHttpRequest.UNINITIALIZED) {
280                 fire(new HTTPAborted());
281             }
282         }
283     }
284 
285     private final static Id<Timeout> TIMER = new Id<Timeout>();
286 
287     /**
288      * <p>Sets a timeout in milliseconds to the current {@link Ajax} object so that it will 
289      * be aborted and fire an {@link HTTPAborted} event when its underlying HTTP request 
290      * has been waited for a longer time than the specified limit.</p>
291      * <p>Setting a timeout of <tt>null</tt> or 0 means no limitation. The previous timeout 
292      * can be canceled by call this method again with a timeout of <tt>null</tt> or 0.</p>
293      * @param ajax The current {@link Ajax} object.
294      * @param ms Timeout limit in milliseconds.
295      * @since 1.0
296      */
297     public static final void setTimeout(Ajax ajax, Number ms) {
298         ini(ajax).var(TIMEOUT, ms);
299     }
300 
301     private static final void setTimeout(final Ajax ajax) {
302         if (Variables.defined(ini(ajax).var(TIMEOUT))) {
303             if (Js.not(ini(ajax).var(TIMER))) {
304                 ini(ajax).var(TIMER, new Timeout() {
305                     @Override
306                     public void run() {
307                         ajax.abort();
308                     }
309                 });
310             }
311             ini(ajax).var(TIMER).set(ini(ajax).var(TIMEOUT));
312         }
313     }
314 
315     /**
316      * <p>Returns the wrapped event source.</p>
317      * <p>The event queuing and dispatching mechanism in the superclass tries to dispatch 
318      * an event fired from an event source also to its wrapped source by calling this method, 
319      * if this object does wrap another valid source.</p>
320      * <p>An {@link Ajax} object wraps nothing and this method simply returns <tt>null</tt> 
321      * to make the high level event mechanism work.</p>
322      * @return <tt>null</tt>.
323      * @since 1.0
324      */
325     @Override
326     protected final Ajax unwrap() {
327         return null;
328     }
329 
330     /**
331      * <p>Returns the container source object if this one is graphically contained by 
332      * another graphical event source.</p>
333      * <p>The event queuing and dispatching mechanism of the superclass tries to dispatch 
334      * an event fired from an event source also to its container by calling this method, 
335      * if this source is graphically contained by another valid source and the event wants 
336      * to bubble, that is, its {@link jsx.Source.Event#BUBBLE} configurable property is 
337      * <tt>true</tt>.</p>
338      * <p>An {@link Ajax} object is neither graphical nor containable and this method 
339      * simply returns <tt>null</tt> to make the high level event mechanism work.</p>
340      * @return <tt>null</tt>.
341      * @since 1.0
342      */
343     @Override
344     protected final Source getParent() {
345         return null;
346     }
347 }