001 
002 /*
003  *  JScripter Simulation 1.0 - For Java To Script
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 org.jscripter.sim;
021 
022 import java.io.ByteArrayInputStream;
023 import java.io.IOException;
024 import java.io.InputStream;
025 import java.io.OutputStream;
026 import java.io.UnsupportedEncodingException;
027 import java.net.HttpURLConnection;
028 import java.net.URL;
029 import java.net.URLConnection;
030 import java.net.URLEncoder;
031 import java.util.Iterator;
032 import java.util.LinkedHashMap;
033 import java.util.List;
034 import java.util.Map;
035 
036 import javax.xml.parsers.DocumentBuilderFactory;
037 
038 import org.w3c.dom.Document;
039 
040 import js.*;
041 import js.core.*;
042 import js.user.JsXMLHttpRequest;
043 
044 class SimXMLHttpRequest extends JsXMLHttpRequest
045 {
046     private final ObjectLike obj;
047     private String responseHeaders;
048     private byte[] responseBytes;
049     private Map<String, List<String>> responseHeadersMap;
050     private Map<String, String> requestHeadersMap;
051 
052     private boolean async;
053     private boolean sent;
054     private URLConnection conn;
055     private String userAgent = DEFAULT_USERAGENT;
056     private String postCharset = DEFAULT_AJAX_CHARSET;
057 
058     private URL url;
059     protected String method;
060 
061     public SimXMLHttpRequest() {
062         super(null);
063         obj = Js.object();
064 
065         obj.var(abort, new Function<Void>() {
066             @Override
067             protected Void function(Object jsthis, Call<Void> callee) {
068                 ((SimXMLHttpRequest)((Var<?>)jsthis).var()).abort();
069                 return null;
070             }
071         }.var());
072         obj.var(getAllResponseHeaders, new Function<String>() {
073             @Override
074             protected String function(Object jsthis, Call<String> callee) {
075                 return ((SimXMLHttpRequest)((Var<?>)jsthis).var()).getAllResponseHeaders();
076             }
077         }.var());
078         obj.var(getResponseHeader, new Function<String>() {
079             @Override
080             protected String function(Object jsthis, Call<String> callee) {
081                 return ((SimXMLHttpRequest)((Var<?>)jsthis).var()).getResponseHeader(
082                         Js.toString(callee.arguments.get(0))
083                 );
084             }
085         }.var());
086         obj.var(open, new Function<Void>() {
087             @Override
088             protected Void function(Object jsthis, Call<Void> callee) {
089                 ((SimXMLHttpRequest)((Var<?>)jsthis).var()).open(
090                         Js.toString(callee.arguments.get(0)),
091                         Js.toString(callee.arguments.get(1)),
092                         Js.be(callee.arguments.get(2)),
093                         Js.toString(callee.arguments.get(3)),
094                         Js.toString(callee.arguments.get(4))
095                 );
096                 return null;
097             }
098         }.var());
099         obj.var(send, new Function<Void>() {
100             @Override
101             protected Void function(Object jsthis, Call<Void> callee) {
102                 ((SimXMLHttpRequest)((Var<?>)jsthis).var()).send(
103                         Js.toString(callee.arguments.get(0))
104                 );
105                 return null;
106             }
107         }.var());
108         obj.var(setRequestHeader, new Function<Void>() {
109             @Override
110             protected Void function(Object jsthis, Call<Void> callee) {
111                 ((SimXMLHttpRequest)((Var<?>)jsthis).var()).setRequestHeader(
112                         Js.toString(callee.arguments.get(0)),
113                         Js.toString(callee.arguments.get(1))
114                 );
115                 return null;
116             }
117         }.var());
118 
119         requestHeadersMap = new LinkedHashMap<String, String>();
120         setRequestHeader("X-Requested-With", "XMLHttpRequest");
121         setRequestHeader("Accept",
122         "text/javascript, text/html, application/xml, application/json, text/xml, */*");
123     }
124 
125     public static final String DEFAULT_USERAGENT = "Mozilla/4.0 (compatible; MSIE 6.0;) SimXMLHttpRequest 1.0";
126     public static final String DEFAULT_AJAX_CHARSET = "UTF-8";
127     public static final String DEFAULT_HTTP_CHARSET = "ISO-8859-1";
128     public static final String DEFAULT_REQUEST_METHOD = "POST";
129 
130     @Override
131     public void setRequestHeader(String key, String value) {
132         this.requestHeadersMap.put(key, value);
133     }
134 
135     @Override
136     public void open(
137             String method, String url, Boolean async, String userName, String password) {
138         try {
139             URL urlObj = new URL(null, url);
140             open(method, urlObj, async, userName, password);
141         } catch (IOException e) {
142             e.printStackTrace();
143         }
144     }
145 
146     private void open(final String method, final URL url, boolean async,
147             final String userName, final String password) throws IOException {  
148         abort();
149         URLConnection c = url.openConnection();
150         synchronized (this) {
151             this.conn = c;
152             this.async = async;
153             this.method = method;
154             this.url = url;
155         }
156         changeState(OPEN, 0, null, null);
157     }
158 
159     @Override
160     public void open(String method, String url) {
161         open(method, url, true, null, null);
162     }
163 
164     @Override
165     public void open(String method, String url, Boolean async) {
166         open(method, url, async, null, null);
167     }
168 
169     @Override
170     public void send(final Object body) {
171         if (async) {
172             new Thread(getClass().getName() + "-" + url.getHost()) {
173                 public void run() {
174                     try {
175                         sendSync(Js.toString(body));
176                     } catch (Throwable t) {
177                         throw new RuntimeException(t);
178                     }
179                 }
180             }.start();
181         } else {
182             try {
183                 sendSync(Js.toString(body));
184             } catch (Throwable t) {
185                 throw new RuntimeException(t);
186             }
187         }
188     }
189 
190     @Override
191     public synchronized String getResponseHeader(String headerName) {
192         return responseHeadersMap == null ? null :
193             responseHeadersMap.get(headerName).toString();
194     }
195 
196     @Override
197     public synchronized String getAllResponseHeaders() {
198         return this.responseHeaders;
199     }  
200 
201     @Override
202     public void abort() {
203         URLConnection c = null;
204         synchronized (this) {
205             c = this.conn;
206         }
207         if (c instanceof HttpURLConnection) {
208             ((HttpURLConnection) c).disconnect();
209         } else if (c != null) {
210             try {
211                 c.getInputStream().close();
212             } catch (IOException ioe) {
213                 ioe.printStackTrace();
214             }
215         }
216     }
217 
218     protected void sendSync(String content) throws IOException {
219         if (sent){
220             return ;
221         }
222         try {
223             URLConnection c;
224             synchronized(this) {
225                 c = conn;
226             }
227             if (c == null) {
228                 return;
229             }
230             sent = true;
231             initConnectionRequestHeader(c);
232             int istatus;
233             String istatusText;
234             InputStream err;
235             if (c instanceof HttpURLConnection) {
236                 HttpURLConnection hc = (HttpURLConnection) c;
237                 String method = this.method == null ? DEFAULT_REQUEST_METHOD : this.method;
238 
239                 method = method.toUpperCase();
240                 hc.setRequestMethod(method);
241                 if ("POST".equals(method) && content != null) {
242                     hc.setDoOutput(true);
243                     byte[] contentBytes = content.getBytes(postCharset);
244                     hc.setFixedLengthStreamingMode(contentBytes.length);
245                     OutputStream out = hc.getOutputStream();
246                     try {
247                         out.write(contentBytes);
248                     } finally {
249                         out.flush();
250                     }
251                 }
252                 istatus = hc.getResponseCode();
253                 istatusText = hc.getResponseMessage();
254                 err = hc.getErrorStream();
255             } else {
256                 istatus = 0;
257                 istatusText = "";
258                 err = null;
259             }
260             synchronized (this) {
261                 responseHeaders = SimUtil.getConnectionResponseHeaders(c);
262                 responseHeadersMap = c.getHeaderFields();
263             }
264             changeState(SENT, istatus, istatusText, null);
265             InputStream in = err == null ? c.getInputStream() : err;
266             int contentLength = c.getContentLength();
267 
268             changeState(RECEIVING, istatus, istatusText, null);
269             byte[] bytes = SimUtil.loadStream(in, contentLength == -1 ? 4096 : contentLength);
270             changeState(LOADED, istatus, istatusText, bytes);
271         } finally {
272             synchronized(this) {
273                 conn = null;
274                 sent = false;
275             }
276         }
277     }
278 
279     protected void changeState(int rs, int st, String stxt, byte[] bytes) {
280         synchronized(this) {
281             var(readyState, rs);
282             var(status, st);
283             var(statusText, stxt);
284             this.responseBytes = bytes;
285             if (rs == LOADED) {
286                 var(responseText, getResponseText());
287             }
288         }
289         JsFunction<?> on = onreadystatechange.with(this);
290         if (Js.be(on)) {
291             on.invoke();
292         }
293     }
294 
295     private synchronized String getResponseText() {
296         byte[] bytes = this.responseBytes;
297         String encoding = SimUtil.getCharset(conn);
298         if (encoding == null) {
299             encoding = postCharset;
300         }
301         if (encoding == null) {
302             encoding = DEFAULT_HTTP_CHARSET;
303         }
304         try {
305             return bytes == null ? null : new String(bytes, encoding);
306         } catch (UnsupportedEncodingException uee) {
307             try {
308                 return new String(bytes, DEFAULT_HTTP_CHARSET);
309             } catch (UnsupportedEncodingException uee2) {
310                 return null;
311             }
312         }
313     }
314 
315     synchronized Document getResponseXML() {
316         byte[] bytes =  this.responseBytes;
317         if (bytes == null) {
318             return null;
319         }
320         InputStream in =  new ByteArrayInputStream(bytes);
321         try {
322             return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
323         } catch (Exception err) {
324             return null;
325         }
326     }
327 
328     protected String encode(Object str) {
329         try {
330             return URLEncoder.encode(String.valueOf(str), postCharset);
331         } catch (UnsupportedEncodingException e) {
332             return String.valueOf(str);
333         }
334     }
335 
336     protected void initConnectionRequestHeader(URLConnection c) {
337         c.setRequestProperty("User-Agent", userAgent);
338         Iterator<String> keyItor = this.requestHeadersMap.keySet().iterator();
339         while (keyItor.hasNext()) {
340             String key = keyItor.next();
341             String value = requestHeadersMap.get(key);
342             c.setRequestProperty(key, value);
343         }
344     }
345 
346     @Override
347     public final JsXMLHttpRequest var() {
348         return this;
349     }
350 
351     @Override
352     public final String typeof() {
353         return "object";
354     }
355 
356     @Override
357     public final boolean undefined() {
358         return false;
359     }
360 
361     @Override
362     public final String toString() {
363         return "[object SimXMLHttpRequest]";
364     }
365 
366     @Override
367     public final JsXMLHttpRequest valueOf() {
368         return this;
369     }
370 
371     @Override
372     public final boolean delete() {
373         return false;
374     }
375 
376     @Override
377     public final boolean delete(Mid mid) {
378         if (undefined()) {
379             return false;
380         }
381         if (obj.delete(mid)) {
382             return true;
383         }
384         JsObject p = JsFunction.prototype.with(this);
385         if (Js.be(p) && Js.be(p.var(mid))) {
386             obj.var(mid, null);
387             return true;
388         }
389         return false;
390     }
391 
392     @Override
393     public final Object var(Mid mid) {
394         return obj.var(mid);
395     }
396 
397     public final <T> T var(Mid mid, T v) {
398         return obj.var(mid, v);
399     }
400 
401     @Override
402     protected final <T> T call(JsFunction.Member<T> member) {
403         return member.with(this).call(this);
404     }
405     @Override
406     protected final <T> T call(JsFunction.Member<T> member, Object arg) {
407         return member.with(this).call(this, arg);
408     }
409     @Override
410     protected final <T> T call(JsFunction.Member<T> member, Vars<?> args) {
411         return member.with(this).call(this, args);
412     }
413 }