# HG changeset patch # User Jaroslav Tulach # Date 1365432690 -7200 # Node ID 6448c284fe21182599725603bdca010764040a3f # Parent 935e11372aa66676e2b0760564ec030b268fc61e Support for JSONP diff -r 935e11372aa6 -r 6448c284fe21 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypes.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypes.java Mon Apr 08 15:14:29 2013 +0200 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypes.java Mon Apr 08 16:51:30 2013 +0200 @@ -73,7 +73,32 @@ private static Object getProperty(Object object, String property) { return null; } + + public static String createJSONP(Object[] jsonResult, Runnable whenDone) { + int h = whenDone.hashCode(); + String name; + for (;;) { + name = "jsonp" + Integer.toHexString(h); + if (defineIfUnused(name, jsonResult, whenDone)) { + return name; + } + h++; + } + } + @JavaScriptBody(args = { "name", "arr", "run" }, body = + "if (window[name]) return false;\n " + + "window[name] = function(data) {\n " + + " arr[0] = data;\n" + + " run.run__V();\n" + + " delete window[name];\n" + + "};" + + "return true;\n" + ) + private static boolean defineIfUnused(String name, Object[] arr, Runnable run) { + return true; + } + @JavaScriptBody(args = { "url", "arr", "callback" }, body = "" + "var request = new XMLHttpRequest();\n" + "request.open('GET', url, true);\n" @@ -89,11 +114,33 @@ + "};" + "request.send();" ) - public static void loadJSON( + private static void loadJSON( String url, Object[] jsonResult, Runnable whenDone ) { } + public static void loadJSON( + String url, Object[] jsonResult, Runnable whenDone, String jsonp + ) { + if (jsonp == null) { + loadJSON(url, jsonResult, whenDone); + } else { + loadJSONP(url, jsonp); + } + } + + @JavaScriptBody(args = { "url", "jsonp" }, body = + "var scrpt = window.document.createElement('script');\n " + + "scrpt.setAttribute('src', url);\n " + + "scrpt.setAttribute('id', jsonp);\n " + + "scrpt.setAttribute('type', 'text/javascript');\n " + + "var body = document.getElementsByTagName('body')[0];\n " + + "body.appendChild(scrpt);\n" + ) + private static void loadJSONP(String url, String jsonp) { + + } + public static void extractJSON(Object jsonObject, String[] props, Object[] values) { for (int i = 0; i < props.length; i++) { values[i] = getProperty(jsonObject, props[i]); diff -r 935e11372aa6 -r 6448c284fe21 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Mon Apr 08 15:14:29 2013 +0200 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Mon Apr 08 16:51:30 2013 +0200 @@ -901,13 +901,26 @@ String n = e.getSimpleName().toString(); body.append("public void ").append(n).append("("); StringBuilder assembleURL = new StringBuilder(); + String jsonpVarName = null; { String sep = ""; + boolean skipJSONP = onR.jsonp().isEmpty(); for (String p : findParamNames(e, onR.url(), assembleURL)) { + if (!skipJSONP && p.equals(onR.jsonp())) { + skipJSONP = true; + jsonpVarName = p; + continue; + } body.append(sep); body.append("String ").append(p); sep = ", "; } + if (!skipJSONP) { + err().printMessage(Diagnostic.Kind.ERROR, + "Name of jsonp attribute ('" + onR.jsonp() + + "') is not used in url attribute '" + onR.url() + "'" + ); + } } body.append(") {\n"); body.append(" final Object[] result = { null };\n"); @@ -944,9 +957,14 @@ " }\n" + " }\n" ); + body.append(" ProcessResult pr = new ProcessResult();\n"); + if (jsonpVarName != null) { + body.append(" String ").append(jsonpVarName). + append(" = org.apidesign.bck2brwsr.htmlpage.ConvertTypes.createJSONP(result, pr);\n"); + } body.append(" org.apidesign.bck2brwsr.htmlpage.ConvertTypes.loadJSON(\n "); body.append(assembleURL); - body.append(", result, new ProcessResult()\n );\n"); + body.append(", result, pr, ").append(jsonpVarName).append("\n );\n"); // body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("("); // body.append(wrapParams(e, null, className, "ev", "data")); // body.append(");\n"); diff -r 935e11372aa6 -r 6448c284fe21 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnReceive.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnReceive.java Mon Apr 08 15:14:29 2013 +0200 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnReceive.java Mon Apr 08 16:51:30 2013 +0200 @@ -38,4 +38,17 @@ * @return the (possibly parametrized) url to connect to */ String url(); + + /** Support for JSONP requires + * a callback from the server generated page to a function defined in the + * system. The name of such function is usually specified as a property + * (of possibly different names). By defining the jsonp attribute + * one turns on the JSONP + * transmission and specifies the name of the property. The property should + * also be used in the {@link #url()} attribute on appropriate place. + * + * @return name of a property to carry the name of JSONP + * callback function. + */ + String jsonp() default ""; } diff -r 935e11372aa6 -r 6448c284fe21 javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/JSONTest.java --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/JSONTest.java Mon Apr 08 15:14:29 2013 +0200 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/JSONTest.java Mon Apr 08 16:51:30 2013 +0200 @@ -171,6 +171,34 @@ assert "Sitar".equals(p.getFirstName()) : "Expecting Sitar: " + p.getFirstName(); // assert Sex.MALE.equals(p.getSex()) : "Expecting MALE: " + p.getSex(); } + + @OnReceive(url="/{url}?callme={me}", jsonp = "me") + static void fetchViaJSONP(Person p, JSONik model) { + model.setFetched(p); + } + + @Http(@Http.Resource( + content = "$0({'firstName': 'Mitar', 'sex': 'MALE'})", + path="/person.json", + mimeType = "application/javascript", + parameters = { "callme" } + )) + @BrwsrTest public void loadAndParseJSONP() throws InterruptedException { + if (js == null) { + js = new JSONik(); + js.applyBindings(); + + js.fetchViaJSONP("person.json"); + } + + Person p = js.getFetched(); + if (p == null) { + throw new InterruptedException(); + } + + assert "Mitar".equals(p.getFirstName()) : "Unexpected: " + p.getFirstName(); + // assert Sex.MALE.equals(p.getSex()) : "Expecting MALE: " + p.getSex(); + } @Http(@Http.Resource( content = "{'firstName': 'Sitar', 'sex': 'MALE'}", diff -r 935e11372aa6 -r 6448c284fe21 javaquery/demo-twitter/src/main/java/org/apidesign/bck2brwsr/demo/twitter/TwitterClient.java --- a/javaquery/demo-twitter/src/main/java/org/apidesign/bck2brwsr/demo/twitter/TwitterClient.java Mon Apr 08 15:14:29 2013 +0200 +++ b/javaquery/demo-twitter/src/main/java/org/apidesign/bck2brwsr/demo/twitter/TwitterClient.java Mon Apr 08 16:51:30 2013 +0200 @@ -57,7 +57,7 @@ public static final class TwttrQr { } - @OnReceive(url="{url}") + @OnReceive(url="{root}/search.json?{query}&callback={me}", jsonp="me") static void queryTweets(TwitterModel page, TwitterQuery q) { page.getCurrentTweets().clear(); page.getCurrentTweets().addAll(q.getResults()); @@ -67,7 +67,7 @@ static void refreshTweets(TwitterModel model) { Tweeters people = model.getActiveTweeters(); StringBuilder sb = new StringBuilder(); - sb.append("http://search.twitter.com/search.json?rpp=25&q="); + sb.append("rpp=25&q="); String sep = ""; for (String p : people.getUserNames()) { sb.append(sep); @@ -75,7 +75,7 @@ sb.append(p); sep = " OR "; } - model.queryTweets(sb.toString()); + model.queryTweets("http://search.twitter.com", sb.toString()); } private static Tweeters tweeters(String listName, String... userNames) { diff -r 935e11372aa6 -r 6448c284fe21 javaquery/demo-twitter/src/test/java/org/apidesign/bck2brwsr/demo/twitter/TwitterProtocolTest.java --- a/javaquery/demo-twitter/src/test/java/org/apidesign/bck2brwsr/demo/twitter/TwitterProtocolTest.java Mon Apr 08 15:14:29 2013 +0200 +++ b/javaquery/demo-twitter/src/test/java/org/apidesign/bck2brwsr/demo/twitter/TwitterProtocolTest.java Mon Apr 08 16:51:30 2013 +0200 @@ -29,9 +29,10 @@ public class TwitterProtocolTest { private TwitterModel page; @Http(@Http.Resource( - path = "/test.tweet", + path = "/search.json", mimeType = "application/json", - content = "{\"completed_in\":0.04,\"max_id\":320055706885689344,\"max_id_str\"" + parameters = {"callback"}, + content = "$0({\"completed_in\":0.04,\"max_id\":320055706885689344,\"max_id_str\"" + ":\"320055706885689344\",\"page\":1,\"query\":\"from%3AJaroslavTulach\",\"refresh_url\":" + "\"?since_id=320055706885689344&q=from%3AJaroslavTulach\"," + "\"results\":[{\"created_at\":\"Fri, 05 Apr 2013 06:10:01 +0000\"," @@ -68,13 +69,13 @@ + "\"profile_image_url_https\":\"https:\\/\\/si0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\"," + "\"source\":\"<a href="http:\\/\\/twitter.com\\/">web<\\/a>\",\"text\":" + "\"Math proofs without words. Ingenious: http:\\/\\/t.co\\/sz7yVbfpGw\"}],\"results_per_page\":100," - + "\"since_id\":0,\"since_id_str\":\"0\"}" + + "\"since_id\":0,\"since_id_str\":\"0\"})" )) @BrwsrTest public void readFromTwttr() throws InterruptedException { if (page == null) { page = new TwitterModel(); page.applyBindings(); - page.queryTweets("/test.tweet"); + page.queryTweets("", "q=xyz"); } if (page.getCurrentTweets().isEmpty()) { diff -r 935e11372aa6 -r 6448c284fe21 rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java --- a/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java Mon Apr 08 15:14:29 2013 +0200 +++ b/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java Mon Apr 08 16:51:30 2013 +0200 @@ -178,7 +178,15 @@ LOG.log(Level.INFO, "Serving HttpResource for {0}", request.getRequestURI()); response.setContentType(r.httpType); r.httpContent.reset(); - copyStream(r.httpContent, response.getOutputStream(), null); + String[] params = null; + if (r.parameters.length != 0) { + params = new String[r.parameters.length]; + for (int i = 0; i < r.parameters.length; i++) { + params[i] = request.getParameter(r.parameters[i]); + } + } + + copyStream(r.httpContent, response.getOutputStream(), null, params); } } } @@ -316,7 +324,7 @@ } if (ch == '$' && params.length > 0) { int cnt = is.read() - '0'; - if (cnt == 'U' - '0') { + if (baseURL != null && cnt == 'U' - '0') { os.write(baseURL.getBytes("UTF-8")); } else { if (cnt >= 0 && cnt < params.length) { diff -r 935e11372aa6 -r 6448c284fe21 rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java --- a/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java Mon Apr 08 15:14:29 2013 +0200 +++ b/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java Mon Apr 08 16:51:30 2013 +0200 @@ -55,11 +55,11 @@ /** HTTP resource to be available during execution. An invocation may * perform an HTTP query and obtain a resource relative to the page. */ - public void addHttpResource(String relativePath, String mimeType, InputStream content) { - if (relativePath == null || mimeType == null || content == null) { + public void addHttpResource(String relativePath, String mimeType, String[] parameters, InputStream content) { + if (relativePath == null || mimeType == null || content == null || parameters == null) { throw new NullPointerException(); } - resources.add(new Resource(content, mimeType, relativePath)); + resources.add(new Resource(content, mimeType, relativePath, parameters)); } /** Invokes the associated method. @@ -100,12 +100,16 @@ final InputStream httpContent; final String httpType; final String httpPath; + final String[] parameters; - Resource(InputStream httpContent, String httpType, String httpPath) { + Resource(InputStream httpContent, String httpType, String httpPath, + String[] parameters + ) { httpContent.mark(Integer.MAX_VALUE); this.httpContent = httpContent; this.httpType = httpType; this.httpPath = httpPath; + this.parameters = parameters; } } } diff -r 935e11372aa6 -r 6448c284fe21 rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/Http.java --- a/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/Http.java Mon Apr 08 15:14:29 2013 +0200 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/Http.java Mon Apr 08 16:51:30 2013 +0200 @@ -53,5 +53,10 @@ String resource() default ""; /** mime type of the resource */ String mimeType(); + /** query parameters. Can be referenced from the {@link #content} as + * $0, $1, etc. The values will be extracted + * from URL parameters of the request. + */ + String[] parameters() default {}; } } diff -r 935e11372aa6 -r 6448c284fe21 rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/Bck2BrwsrCase.java --- a/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/Bck2BrwsrCase.java Mon Apr 08 15:14:29 2013 +0200 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/Bck2BrwsrCase.java Mon Apr 08 16:51:30 2013 +0200 @@ -65,10 +65,10 @@ for (Http.Resource r : http) { if (!r.content().isEmpty()) { InputStream is = new ByteArrayInputStream(r.content().getBytes("UTF-8")); - c.addHttpResource(r.path(), r.mimeType(), is); + c.addHttpResource(r.path(), r.mimeType(), r.parameters(), is); } else { InputStream is = m.getDeclaringClass().getResourceAsStream(r.resource()); - c.addHttpResource(r.path(), r.mimeType(), is); + c.addHttpResource(r.path(), r.mimeType(), r.parameters(), is); } } }