diff -r a7bec1c91507 boot-script/src/test/java/net/java/html/boot/script/ko4j/KnockoutEnvJSTest.java --- a/boot-script/src/test/java/net/java/html/boot/script/ko4j/KnockoutEnvJSTest.java Wed May 27 22:40:01 2015 +0200 +++ b/boot-script/src/test/java/net/java/html/boot/script/ko4j/KnockoutEnvJSTest.java Wed May 27 23:28:35 2015 +0200 @@ -83,10 +83,10 @@ private static Class browserClass; private static Fn.Presenter browserContext; private static URI baseUri; - + public KnockoutEnvJSTest() { } - + @Factory public static Object[] compatibilityTests() throws Exception { try { Class.forName("java.lang.FunctionalInterface"); @@ -94,8 +94,8 @@ // only runs on JDK8 return new Object[0]; } - - + + Class[] arr = testClasses(); for (int i = 0; i < arr.length; i++) { assertEquals( @@ -104,9 +104,9 @@ "All classes loaded by the same classloader" ); } - + baseUri = DynamicHTTP.initServer(); - + final Fn.Presenter p = Scripts.createPresenter(KOCase.JS); InputStream is = KnockoutEnvJSTest.class.getResourceAsStream("env.nashorn.1.2-debug.js"); p.loadScript(new InputStreamReader(is)); @@ -116,14 +116,14 @@ loadClass(KnockoutEnvJSTest.class). loadPage(baseUri.toString()). invoke("initialized"); - + Executors.newSingleThreadExecutor().submit(new Runnable() { @Override public void run() { bb.showAndWait(); } }); - + ClassLoader l = getClassLoader(); List res = new ArrayList(); for (int i = 0; i < arr.length; i++) { @@ -143,20 +143,20 @@ } } } - + private static String skipMsg(String methodName) { final String ver = System.getProperty("java.runtime.version"); // NOI18N if ( ver.startsWith("1.8.0_25") || - ver.startsWith("1.8.0_40") + ver.startsWith("1.8.0_40") ) { return "Broken due to JDK-8047764"; } if ( !"1.8.0_05-b13".equals(ver) && - !"1.8.0_11-b12".equals(ver) + !"1.8.0_11-b12".equals(ver) ) { - // we know that 1.8.0_05 and 1.8.0_11 are broken, + // we know that 1.8.0_05 and 1.8.0_11 are broken, // let's not speculate about anything else return null; } @@ -178,13 +178,13 @@ } return browserClass.getClassLoader(); } - + public static synchronized void initialized(Class browserCls) throws Exception { browserClass = browserCls; browserContext = Fn.activePresenter(); KnockoutEnvJSTest.class.notifyAll(); } - + public static void initialized() throws Exception { Assert.assertSame( KnockoutEnvJSTest.class.getClassLoader(), @@ -194,7 +194,7 @@ KnockoutEnvJSTest.initialized(KnockoutEnvJSTest.class); browserContext = Fn.activePresenter(); } - + @Override public BrwsrCtx createContext() { KO4J fx = new KO4J(browserContext); @@ -216,7 +216,7 @@ } return json; } - + @JavaScriptBody(args = {}, body = "return new Object();") private static native Object createJSON(); @JavaScriptBody(args = { "json", "key", "value" }, body = "json[key] = value;") @@ -232,7 +232,7 @@ private static String findBaseURL() { return baseUri.toString(); } - + @Override public URI prepareURL(String content, String mimeType, String[] parameters) { try { diff -r a7bec1c91507 json-tck/src/main/java/net/java/html/json/tests/JSONTest.java --- a/json-tck/src/main/java/net/java/html/json/tests/JSONTest.java Wed May 27 22:40:01 2015 +0200 +++ b/json-tck/src/main/java/net/java/html/json/tests/JSONTest.java Wed May 27 23:28:35 2015 +0200 @@ -51,11 +51,11 @@ import net.java.html.json.Models; import net.java.html.json.OnReceive; import net.java.html.json.Property; +import static net.java.html.json.tests.Utils.assertEquals; +import static net.java.html.json.tests.Utils.assertNotNull; +import static net.java.html.json.tests.Utils.assertNull; +import static net.java.html.json.tests.Utils.assertTrue; import org.netbeans.html.json.tck.KOTest; -import static net.java.html.json.tests.Utils.assertEquals; -import static net.java.html.json.tests.Utils.assertNull; -import static net.java.html.json.tests.Utils.assertNotNull; -import static net.java.html.json.tests.Utils.assertTrue; /** Need to verify that models produce reasonable JSON objects. * @@ -71,12 +71,12 @@ private JSONik js; private Integer orig; private String url; - + @ModelOperation static void assignFetched(JSONik m, Person p) { m.setFetched(p); } private BrwsrCtx ctx; - + @KOTest public void toJSONInABrowser() throws Throwable { Person p = Models.bind(new Person(), newContext()); p.setSex(Sex.MALE); @@ -89,19 +89,19 @@ } catch (Throwable ex) { throw new IllegalStateException("Can't parse " + p).initCause(ex); } - + Person p2 = Models.fromRaw(newContext(), Person.class, json); - - assertEquals(p2.getFirstName(), p.getFirstName(), + + assertEquals(p2.getFirstName(), p.getFirstName(), "Should be the same: " + p.getFirstName() + " != " + p2.getFirstName()); } - + @KOTest public void toJSONWithEscapeCharactersInABrowser() throws Throwable { Person p = Models.bind(new Person(), newContext()); p.setSex(Sex.MALE); p.setFirstName("/*\n * Copyright (c) 2013"); - + final String txt = p.toString(); Object json; try { @@ -109,19 +109,19 @@ } catch (Throwable ex) { throw new IllegalStateException("Can't parse " + txt).initCause(ex); } - + Person p2 = Models.fromRaw(newContext(), Person.class, json); - + assertEquals(p2.getFirstName(), p.getFirstName(), "Should be the same: " + p.getFirstName() + " != " + p2.getFirstName()); } - + @KOTest public void toJSONWithDoubleSlashInABrowser() throws Throwable { Person p = Models.bind(new Person(), newContext()); p.setSex(Sex.MALE); p.setFirstName("/*\\n * Copyright (c) 2013"); - + final String txt = p.toString(); Object json; try { @@ -129,19 +129,19 @@ } catch (Throwable ex) { throw new IllegalStateException("Can't parse " + txt).initCause(ex); } - + Person p2 = Models.fromRaw(newContext(), Person.class, json); - + assertEquals(p2.getFirstName(), p.getFirstName(), "Should be the same: " + p.getFirstName() + " != " + p2.getFirstName()); } - + @KOTest public void toJSONWithApostrophInABrowser() throws Throwable { Person p = Models.bind(new Person(), newContext()); p.setSex(Sex.MALE); p.setFirstName("Jimmy 'Jim' Rambo"); - + final String txt = p.toString(); Object json; try { @@ -149,15 +149,15 @@ } catch (Throwable ex) { throw new IllegalStateException("Can't parse " + txt).initCause(ex); } - + Person p2 = Models.fromRaw(newContext(), Person.class, json); - + assertEquals(p2.getFirstName(), p.getFirstName(), "Should be the same: " + p.getFirstName() + " != " + p2.getFirstName()); } private static BrwsrCtx onCallback; - + @OnReceive(url="{url}") static void fetch(JSONik model, Person p) { model.setFetched(p); @@ -176,12 +176,12 @@ model.setFetched(p[0]); onCallback = BrwsrCtx.findDefault(model.getClass()); } - + static void setMessage(JSONik m, Exception t) { assertNotNull(t, "Exception provided"); m.setFetchedResponse("Exception"); } - + @OnReceive(url="{url}") static void fetchPeople(JSONik model, People p) { final int size = p.getInfo().size(); @@ -199,7 +199,7 @@ } model.setFetchedCount(sum); } - + @KOTest public void loadAndParseJSON() throws InterruptedException { if (js == null) { url = Utils.prepareURL( @@ -212,18 +212,18 @@ js.setFetched(null); js.fetch(url); } - + Person p = js.getFetched(); if (p == null) { throw new InterruptedException(); } - + assertEquals("Sitar", p.getFirstName(), "Expecting Sitar: " + p.getFirstName()); assertEquals(Sex.MALE, p.getSex(), "Expecting MALE: " + p.getSex()); - + assertEquals(ctx, onCallback, "Context is the same"); } - + @KOTest public void loadAndParsePlainText() throws Exception { if (js == null) { url = Utils.prepareURL( @@ -236,23 +236,23 @@ js.setFetched(null); js.fetchPlain(url); } - + String s = js.getFetchedResponse(); if (s == null) { throw new InterruptedException(); } - + assertTrue(s.contains("Sitar"), "The text contains Sitar value: " + s); assertTrue(s.contains("MALE"), "The text contains MALE value: " + s); - + Person p = Models.parse(ctx, Person.class, new ByteArrayInputStream(s.getBytes())); - + assertEquals("Sitar", p.getFirstName(), "Expecting Sitar: " + p.getFirstName()); assertEquals(Sex.MALE, p.getSex(), "Expecting MALE: " + p.getSex()); - + assertEquals(ctx, onCallback, "Same context"); } - + @KOTest public void loadAndParsePlainTextOnArray() throws Exception { if (js == null) { url = Utils.prepareURL( @@ -265,60 +265,60 @@ js.setFetched(null); js.fetchPlain(url); } - + String s = js.getFetchedResponse(); if (s == null) { throw new InterruptedException(); } - + assertTrue(s.contains("Sitar"), "The text contains Sitar value: " + s); assertTrue(s.contains("MALE"), "The text contains MALE value: " + s); - + Person p = Models.parse(ctx, Person.class, new ByteArrayInputStream(s.getBytes())); - + assertEquals("Sitar", p.getFirstName(), "Expecting Sitar: " + p.getFirstName()); assertEquals(Sex.MALE, p.getSex(), "Expecting MALE: " + p.getSex()); - + assertEquals(ctx, onCallback, "Same context"); } - + @OnReceive(url="{url}?callme={me}", jsonp = "me") static void fetchViaJSONP(JSONik model, Person p) { model.setFetched(p); } - + @KOTest public void loadAndParseJSONP() throws InterruptedException, Exception { if (js == null) { url = Utils.prepareURL( - JSONTest.class, "$0({'firstName': 'Mitar', 'sex': 'MALE'})", + JSONTest.class, "$0({'firstName': 'Mitar', 'sex': 'MALE'})", "application/javascript", "callme" ); orig = scriptElements(); assertTrue(orig > 0, "There should be some scripts on the page"); - + js = Models.bind(new JSONik(), newContext()); js.applyBindings(); js.setFetched(null); js.fetchViaJSONP(url); } - + Person p = js.getFetched(); if (p == null) { throw new InterruptedException(); } - + assertEquals("Mitar", p.getFirstName(), "Unexpected: " + p.getFirstName()); assertEquals(Sex.MALE, p.getSex(), "Expecting MALE: " + p.getSex()); - + int now = scriptElements(); - + assertEquals(orig, now, "The set of elements is unchanged. Delta: " + (now - orig)); } - - - + + + @OnReceive(url="{url}", method = "PUT", data = Person.class) static void putPerson(JSONik model, String reply) { model.setFetchedCount(1); @@ -328,13 +328,13 @@ @KOTest public void putPeopleUsesRightMethod() throws InterruptedException, Exception { if (js == null) { url = Utils.prepareURL( - JSONTest.class, "$0\n$1", + JSONTest.class, "$0\n$1", "text/plain", "http.method", "http.requestBody" ); orig = scriptElements(); assertTrue(orig > 0, "There should be some scripts on the page"); - + js = Models.bind(new JSONik(), newContext()); js.applyBindings(); @@ -342,7 +342,7 @@ p.setFirstName("Jarda"); js.putPerson(url, p); } - + int cnt = js.getFetchedCount(); if (cnt == 0) { throw new InterruptedException(); @@ -356,186 +356,231 @@ } else { msg = res; } - + assertEquals("PUT", res, "Server was queried with PUT method: " + js.getFetchedResponse()); - + assertTrue(msg.contains("Jarda"), "Data transferred to the server: " + msg); } - + private static int scriptElements() throws Exception { return ((Number)Utils.executeScript( - JSONTest.class, + JSONTest.class, "return window.document.getElementsByTagName('script').length;")).intValue(); } private static Object parseJSON(String s) throws Exception { return Utils.executeScript( - JSONTest.class, + JSONTest.class, "return window.JSON.parse(arguments[0]);", s); } - + @KOTest public void loadAndParseJSONSentToArray() throws InterruptedException { if (js == null) { url = Utils.prepareURL( - JSONTest.class, "{'firstName': 'Sitar', 'sex': 'MALE'}", + JSONTest.class, "{'firstName': 'Sitar', 'sex': 'MALE'}", "application/json" ); - + js = Models.bind(new JSONik(), newContext()); js.applyBindings(); js.setFetched(null); js.fetchArray(url); } - + Person p = js.getFetched(); if (p == null) { throw new InterruptedException(); } - + assertEquals("Sitar", p.getFirstName(), "Expecting Sitar: " + p.getFirstName()); assertEquals(Sex.MALE, p.getSex(), "Expecting MALE: " + p.getSex()); } - + @KOTest public void loadAndParseJSONArraySingle() throws InterruptedException { if (js == null) { url = Utils.prepareURL( - JSONTest.class, "[{'firstName': 'Gitar', 'sex': 'FEMALE'}]", + JSONTest.class, "[{'firstName': 'Gitar', 'sex': 'FEMALE'}]", "application/json" ); js = Models.bind(new JSONik(), newContext()); js.applyBindings(); - + js.setFetched(null); js.fetch(url); } - + Person p = js.getFetched(); if (p == null) { throw new InterruptedException(); } - + assertEquals("Gitar", p.getFirstName(), "Expecting Gitar: " + p.getFirstName()); assertEquals(Sex.FEMALE, p.getSex(), "Expecting FEMALE: " + p.getSex()); } - + @KOTest public void loadAndParseArrayInPeople() throws InterruptedException { if (js == null) { url = Utils.prepareURL( - JSONTest.class, "{'info':[{'firstName': 'Gitar', 'sex': 'FEMALE'}]}", + JSONTest.class, "{'info':[{'firstName': 'Gitar', 'sex': 'FEMALE'}]}", "application/json" ); js = Models.bind(new JSONik(), newContext()); js.applyBindings(); - + js.fetchPeople(url); } - + if (0 == js.getFetchedCount()) { throw new InterruptedException(); } assertEquals(js.getFetchedCount(), 1, "One person loaded: " + js.getFetchedCount()); - + Person p = js.getFetched(); - + assertNotNull(p, "We should get our person back: " + p); assertEquals("Gitar", p.getFirstName(), "Expecting Gitar: " + p.getFirstName()); assertEquals(Sex.FEMALE, p.getSex(), "Expecting FEMALE: " + p.getSex()); } - + + @KOTest public void loadAndParseArrayInPeopleWithHeaders() throws InterruptedException { + if (js == null) { + url = Utils.prepareURL( + JSONTest.class, "{'info':[{'firstName': '$0$1$2$3$4', 'sex': 'FEMALE'}]}", + "application/json", + "http.header.Easy", + "http.header.H-a!r*d^e.r", + "http.header.Repeat-ed", + "http.header.Repeat*ed", + "http.header.Same-URL" + ); + js = Models.bind(new JSONik(), newContext()); + js.applyBindings(); + + js.fetchPeopleWithHeaders(url, "easy", "harder", "rep"); + } + + if (0 == js.getFetchedCount()) { + throw new InterruptedException(); + } + + assertEquals(js.getFetchedCount(), 1, "One person loaded: " + js.getFetchedCount()); + + Person p = js.getFetched(); + + assertNotNull(p, "We should get our person back: " + p); + assertEquals("easyharderreprep" + url, p.getFirstName(), "Expecting header mess: " + p.getFirstName()); + assertEquals(Sex.FEMALE, p.getSex(), "Expecting FEMALE: " + p.getSex()); + } + + @OnReceive(url="{url}", headers={ + "Easy: {easy}", + "H-a!r*d^e.r: {harder}", + "Repeat-ed: {rep}", + "Repeat*ed: {rep}", + "Same-URL: {url}" + }) + static void fetchPeopleWithHeaders(JSONik model, People p) { + final int size = p.getInfo().size(); + if (size > 0) { + model.setFetched(p.getInfo().get(0)); + } + model.setFetchedCount(size); + } + @KOTest public void loadAndParseArrayOfIntegers() throws InterruptedException { if (js == null) { url = Utils.prepareURL( - JSONTest.class, "{'age':[1, 2, 3]}", + JSONTest.class, "{'age':[1, 2, 3]}", "application/json" ); js = Models.bind(new JSONik(), newContext()); js.applyBindings(); - + js.fetchPeopleAge(url); } - + if (0 == js.getFetchedCount()) { throw new InterruptedException(); } assertEquals(js.getFetchedCount(), 6, "1 + 2 + 3 is " + js.getFetchedCount()); } - + @OnReceive(url="{url}") static void fetchPeopleSex(JSONik model, People p) { model.setFetchedCount(1); model.getFetchedSex().addAll(p.getSex()); } - + @KOTest public void loadAndParseArrayOfEnums() throws InterruptedException { if (js == null) { url = Utils.prepareURL( - JSONTest.class, "{'sex':['FEMALE', 'MALE', 'MALE']}", + JSONTest.class, "{'sex':['FEMALE', 'MALE', 'MALE']}", "application/json" ); js = Models.bind(new JSONik(), newContext()); js.applyBindings(); - + js.fetchPeopleSex(url); } - + if (0 == js.getFetchedCount()) { throw new InterruptedException(); } assertEquals(js.getFetchedCount(), 1, "Loaded"); - + assertEquals(js.getFetchedSex().size(), 3, "Three values " + js.getFetchedSex()); assertEquals(js.getFetchedSex().get(0), Sex.FEMALE, "Female first " + js.getFetchedSex()); assertEquals(js.getFetchedSex().get(1), Sex.MALE, "male 2nd " + js.getFetchedSex()); assertEquals(js.getFetchedSex().get(2), Sex.MALE, "male 3rd " + js.getFetchedSex()); } - + @KOTest public void loadAndParseJSONArray() throws InterruptedException { if (js == null) { url = Utils.prepareURL( JSONTest.class, "[{'firstName': 'Gitar', 'sex': 'FEMALE'}," + "{'firstName': 'Peter', 'sex': 'MALE'}" - + "]", + + "]", "application/json" ); js = Models.bind(new JSONik(), newContext()); js.applyBindings(); js.setFetched(null); - + js.fetchArray(url); } - - + + Person p = js.getFetched(); if (p == null) { throw new InterruptedException(); } - + assertEquals(js.getFetchedCount(), 2, "We got two values: " + js.getFetchedCount()); assertEquals("Gitar", p.getFirstName(), "Expecting Gitar: " + p.getFirstName()); assertEquals(Sex.FEMALE, p.getSex(), "Expecting FEMALE: " + p.getSex()); } - + @KOTest public void loadError() throws InterruptedException { if (js == null) { js = Models.bind(new JSONik(), newContext()); js.applyBindings(); js.setFetched(null); - + js.fetchArray("http://127.0.0.1:54253/does/not/exist.txt"); } - - + + if (js.getFetchedResponse() == null) { throw new InterruptedException(); } assertEquals("Exception", js.getFetchedResponse(), "Response " + js.getFetchedResponse()); } - + @Model(className = "NameAndValue", properties = { @Property(name = "name", type = String.class), @Property(name = "value", type = long.class), @@ -543,7 +588,7 @@ }) static class NandV { } - + @KOTest public void parseNullNumber() throws Exception { String txt = "{ \"name\":\"M\" }"; ByteArrayInputStream is = new ByteArrayInputStream(txt.getBytes("UTF-8")); @@ -566,12 +611,12 @@ err = null; prev = null; } - + String str = "{ \"sex\" : \"unknown\" }"; ByteArrayInputStream is = new ByteArrayInputStream(str.getBytes("UTF-8")); Person p = Models.parse(newContext(), Person.class, is); assertNull(p.getSex(), "Wrong sex means null, but was: " + p.getSex()); - + if (err != null) { assertTrue(err.toString().contains("unknown") && err.toString().contains("Sex"), "Expecting error: " + err.toString()); } @@ -584,9 +629,9 @@ } } - + private static BrwsrCtx newContext() { return Utils.newContext(JSONTest.class); } - + } diff -r a7bec1c91507 json/src/main/java/net/java/html/json/OnReceive.java --- a/json/src/main/java/net/java/html/json/OnReceive.java Wed May 27 22:40:01 2015 +0200 +++ b/json/src/main/java/net/java/html/json/OnReceive.java Wed May 27 23:28:35 2015 +0200 @@ -48,7 +48,7 @@ import java.lang.annotation.Target; /** Static methods in classes annotated by {@link Model} - * can be marked by this annotation to establish a + * can be marked by this annotation to establish a * JSON * communication point. The first argument should be the * associated {@link Model} class. The second argument can @@ -57,7 +57,7 @@ * {@link java.util.List} of such classes. * The associated model class then gets new method to invoke a network * connection asynchronously. Example follows: - * + * *
  * {@link Model @Model}(className="MyModel", properties={
  *   {@link Property @Property}(name = "people", type=Person.class, array=true)
@@ -73,18 +73,18 @@
  *       return firstName + " " + lastName;
  *     }
  *   }
- *
+ *
  *   {@link OnReceive @OnReceive}(url = "{protocol}://your.server.com/person/{name}")
  *   static void getANewPerson(MyModel m, Person p) {
  *     System.out.println("Adding " + p.getFullName() + '!');
  *     m.getPeople().add(p);
  *   }
- *
+ *
  *   // the above will generate method getANewPerson in class MyModel.
  *   // with protocol and name arguments
  *   // which asynchronously contacts the server and in case of success calls
  *   // your {@link OnReceive @OnReceive} with parsed in data
- *
+ *
  *   {@link Function @Function}
  *   static void requestSmith(MyModel m) {
  *     m.getANewPerson("http", "Smith");
@@ -92,7 +92,7 @@
  * }
  * 
* When the server returns { "firstName" : "John", "lastName" : "Smith" } - * the system will print a message Adding John Smith!. It is not + * the system will print a message Adding John Smith!. It is not * necessary to fully describe the server message - enumerate only the fields * in the response you are interested in. The others will be discarded. So, * if the server { "firstName" : "John", "lastName" : "Smith", "age" : 33 } @@ -105,7 +105,7 @@ *

* Visit an on-line demo * to see REST access via {@link OnReceive} annotation. - * + * * @author Jaroslav Tulach * @since 0.3 */ @@ -113,59 +113,90 @@ @Target(ElementType.METHOD) public @interface OnReceive { /** The URL to connect to. Can contain variable names surrounded by '{' and '}'. - * Those parameters will then become variables of the associated method. - * + * Those names will then become parameters of the associated method. + * * @return the (possibly parametrized) url to connect to */ String url(); - + + /** Specifies HTTP request headers. Array of header lines + * can contain variable names surrounded by '{' and '}'. + * Those names will then become parameters of the associated method + * (in addition to those added by {@link #url()} specification) + * and can only be used with plain JSON(P) requests. + * Headers are currently not supported by the + * WebSockets protocol. + * A sample follows. If you want to transmit X-Birthday header, + * you can do it like this: + *

+     * {@code @}{@link OnReceive}(url="http://your.server.org", headers = {
+     *   "X-Birthday: {dayOfBirth}"
+     * })
+     * static void knowingTheBirth({@link Model YourModel} model) {
+     *   // handle the reply
+     * }
+ * a method knowingTheBirth is generated in + * YourModel class with the dayOfBirth argument + * which can be called like this: + *
+     * yourModel.knowingTheBirth("10. 12. 1973");
+     * 
+ * + * @return array of header lines - each line should be plain text with + * a header name, followed by ":" and value usually specified as + * '{' and '}' surrounded variable. The line shouldn't contain + * newline or other control characters + * @since 1.2 + */ + String[] headers() default {}; + /** 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 + * 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 ""; - + /** The model class to be send to the server as JSON data. * By default no data are sent. However certain {@link #method() transport methods} - * (like "PUT" and "POST") require the + * (like "PUT" and "POST") require the * data to be specified. - * + * * @return name of a class generated using {@link Model @Model} annotation * @since 0.3 */ Class data() default Object.class; - + /** The HTTP transfer method to use. Defaults to "GET". - * Other typical methods include "HEAD", + * Other typical methods include "HEAD", * "DELETE", "POST", "PUT". * The last two mentioned methods require {@link #data()} to be specified. *

- * When {@link #jsonp() JSONP} transport is requested, the method + * When {@link #jsonp() JSONP} transport is requested, the method * has to be "GET". *

* Since version 0.5 one can specify "WebSocket" * as the communication method. - * + * * @return name of the HTTP transfer method * @since 0.3 */ String method() default "GET"; - - /** Name of a method in this class which should be called in case of - * an error. The method has to be non-private and take one model and - * one {@link Exception} + + /** Name of a method in this class which should be called in case of + * an error. The method has to be non-private and take one model and + * one {@link Exception} * parameter. If this method is not specified, the exception is just * printed to console. - * + * * @return name of method in this class * @since 0.5 */ - public String onError() default ""; + public String onError() default ""; } diff -r a7bec1c91507 json/src/main/java/org/netbeans/html/json/impl/JSON.java --- a/json/src/main/java/org/netbeans/html/json/impl/JSON.java Wed May 27 22:40:01 2015 +0200 +++ b/json/src/main/java/org/netbeans/html/json/impl/JSON.java Wed May 27 23:28:35 2015 +0200 @@ -71,24 +71,24 @@ return t == null ? EmptyTech.EMPTY : t; } - static Transfer findTransfer(BrwsrCtx c) { + public static Transfer findTransfer(BrwsrCtx c) { Transfer t = Contexts.find(c, Transfer.class); return t == null ? EmptyTech.EMPTY : t; } - static WSTransfer findWSTransfer(BrwsrCtx c) { + public static WSTransfer findWSTransfer(BrwsrCtx c) { WSTransfer t = Contexts.find(c, WSTransfer.class); return t == null ? EmptyTech.EMPTY : t; } - + public static void extract(BrwsrCtx c, Object value, String[] props, Object[] values) { Transfer t = findTransfer(c); t.extract(value, props, values); } - + private static Object getProperty(BrwsrCtx c, Object obj, String prop) { if (prop == null) return obj; - + String[] arr = { prop }; Object[] val = { null }; extract(c, obj, arr, val); @@ -146,15 +146,15 @@ Object o = t.toModel(aClass, data); return aClass.cast(o); } - + public static boolean isSame(int a, int b) { return a == b; } - + public static boolean isSame(double a, double b) { return a == b; } - + public static boolean isSame(Object a, Object b) { if (a == b) { return true; @@ -164,7 +164,7 @@ } return a.equals(b); } - + public static int hashPlus(Object o, int h) { return o == null ? h : h ^ o.hashCode(); } @@ -193,7 +193,7 @@ } if (Byte.class == type) { val = val instanceof Number ? ((Number)val).byteValue() : 0; - } + } if (Double.class == type) { val = val instanceof Number ? ((Number)val).doubleValue() : Double.NaN; } @@ -202,11 +202,11 @@ } return type.cast(val); } - + static boolean isNumeric(Object val) { return ((val instanceof Integer) || (val instanceof Long) || (val instanceof Short) || (val instanceof Byte)); } - + public static String stringValue(Object val) { if (val instanceof Boolean) { return ((Boolean)val ? "true" : "false"); @@ -222,7 +222,7 @@ } return (String)val; } - + public static Number numberValue(Object val) { if (val instanceof String) { try { @@ -250,7 +250,7 @@ } return (Character)val; } - + public static Boolean boolValue(Object val) { if (val instanceof String) { return Boolean.parseBoolean((String)val); @@ -258,10 +258,10 @@ if (val instanceof Number) { return numberValue(val).doubleValue() != 0.0; } - + return Boolean.TRUE.equals(val); } - + public static Object find(Object object, Bindings model) { if (object == null) { return null; @@ -288,7 +288,7 @@ final Bindings b = PropertyBindingAccessor.getBindings(proto, true); return b == null ? null : b.koData(); } - + private static Proto findProto(Object object) { Proto.Type type = JSON.findType(object.getClass()); if (type == null) { @@ -301,7 +301,7 @@ public static Object find(Object object) { return find(object, null); } - + public static void applyBindings(Object object, String id) { final Proto proto = findProto(object); if (proto == null) { @@ -309,31 +309,17 @@ } proto.applyBindings(id); } - - public static void loadJSON( - BrwsrCtx c, RcvrJSON callback, - String urlBefore, String urlAfter, String method, - Object data - ) { - JSONCall call = PropertyBindingAccessor.createCall(c, callback, urlBefore, urlAfter, method, data); - Transfer t = findTransfer(c); - t.loadJSON(call); - } - public static WS openWS( - BrwsrCtx c, RcvrJSON r, String url, Object data - ) { - WS ws = WSImpl.create(findWSTransfer(c), r); - ws.send(c, url, data); - return ws; - } - + public static abstract class WS { private WS() { } - - public abstract void send(BrwsrCtx ctx, String url, Object model); + + public abstract void send(BrwsrCtx ctx, String headers, String url, Object model); + public static WS create(WSTransfer t, RcvrJSON r) { + return new WSImpl(t, r); + } } - + private static final class WSImpl extends WS { private final WSTransfer trans; @@ -345,19 +331,15 @@ this.trans = trans; this.rcvr = rcvr; } - - static WS create(WSTransfer t, RcvrJSON r) { - return new WSImpl(t, r); - } @Override - public void send(BrwsrCtx ctx, String url, Object data) { + public void send(BrwsrCtx ctx, String headers, String url, Object data) { Socket s = socket; if (s == null) { if (data != null) { throw new IllegalStateException("WebSocket is not opened yet. Call with null data, was: " + data); } - JSONCall call = PropertyBindingAccessor.createCall(ctx, rcvr, url, null, "WebSocket", null); + JSONCall call = PropertyBindingAccessor.createCall(ctx, rcvr, headers, url, null, "WebSocket", null); socket = trans.open(url, call); prevURL = url; return; @@ -373,12 +355,12 @@ + " Close the socket by calling it will null data first!" ); } - JSONCall call = PropertyBindingAccessor.createCall(ctx, rcvr, prevURL, null, "WebSocket", data); + JSONCall call = PropertyBindingAccessor.createCall(ctx, rcvr, headers, prevURL, null, "WebSocket", data); trans.send(s, call); } - + } - + private static final Map> modelTypes; static { modelTypes = new HashMap>(); @@ -386,11 +368,11 @@ public static void register(Class c, Proto.Type type) { modelTypes.put(c, type); } - + public static boolean isModel(Class clazz) { - return findType(clazz) != null; + return findType(clazz) != null; } - + static Proto.Type findType(Class clazz) { for (int i = 0; i < 2; i++) { Proto.Type from = modelTypes.get(clazz); @@ -402,7 +384,7 @@ } return null; } - + public static Model bindTo(Model model, BrwsrCtx c) { Proto.Type from = (Proto.Type) findType(model.getClass()); if (from == null) { @@ -410,8 +392,8 @@ } return PropertyBindingAccessor.clone(from, model, c); } - - public static T readStream(BrwsrCtx c, Class modelClazz, InputStream data, Collection collectTo) + + public static T readStream(BrwsrCtx c, Class modelClazz, InputStream data, Collection collectTo) throws IOException { Transfer tr = findTransfer(c); Object rawJSON = tr.toJSON((InputStream)data); @@ -468,7 +450,7 @@ // ignore and try again } } - + private static final class EmptyTech implements Technology, Transfer, WSTransfer { private static final EmptyTech EMPTY = new EmptyTech(); @@ -540,5 +522,5 @@ public void close(Void socket) { } } - + } diff -r a7bec1c91507 json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java --- a/json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java Wed May 27 22:40:01 2015 +0200 +++ b/json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java Wed May 27 23:28:35 2015 +0200 @@ -1132,15 +1132,31 @@ body.append(" * to open the connection (even if not required). Call with non-null data to\n"); body.append(" * send messages to server. Call again with null data to close the socket.\n"); body.append(" */\n"); + if (onR.headers().length > 0) { + error("WebSocket spec does not support headers", e); + } } body.append(" public void ").append(n).append("("); StringBuilder urlBefore = new StringBuilder(); StringBuilder urlAfter = new StringBuilder(); + StringBuilder headers = new StringBuilder(); String jsonpVarName = null; { String sep = ""; boolean skipJSONP = onR.jsonp().isEmpty(); - for (String p : findParamNames(e, onR.url(), onR.jsonp(), urlBefore, urlAfter)) { + Set receiveParams = new LinkedHashSet(); + findParamNames(receiveParams, e, onR.url(), onR.jsonp(), urlBefore, urlAfter); + for (String headerLine : onR.headers()) { + if (headerLine.contains("\r") || headerLine.contains("\n")) { + error("Header line cannot contain line separator", e); + } + findParamNames(receiveParams, e, headerLine, null, headers); + headers.append("+ \"\\r\\n\" +\n"); + } + if (headers.length() > 0) { + headers.append("\"\""); + } + for (String p : receiveParams) { if (!skipJSONP && p.equals(onR.jsonp())) { skipJSONP = true; jsonpVarName = p; @@ -1180,13 +1196,13 @@ body.append(") {\n"); boolean webSocket = onR.method().equals("WebSocket"); if (webSocket) { - if (generateWSReceiveBody(index++, body, inType, onR, e, clazz, className, expectsList != 0, modelClass, n, args, params, urlBefore, jsonpVarName, urlAfter, dataMirror)) { + if (generateWSReceiveBody(index++, body, inType, onR, e, clazz, className, expectsList != 0, modelClass, n, args, params, urlBefore, jsonpVarName, urlAfter, dataMirror, headers)) { return false; } body.append(" }\n"); body.append(" private Object ws_" + e.getSimpleName() + ";\n"); } else { - if (generateJSONReceiveBody(index++, body, inType, onR, e, clazz, className, expectsList != 0, modelClass, n, args, params, urlBefore, jsonpVarName, urlAfter, dataMirror)) { + if (generateJSONReceiveBody(index++, body, inType, onR, e, clazz, className, expectsList != 0, modelClass, n, args, params, urlBefore, jsonpVarName, urlAfter, dataMirror, headers)) { return false; } body.append(" }\n"); @@ -1198,7 +1214,7 @@ return true; } - private boolean generateJSONReceiveBody(int index, StringWriter method, StringBuilder body, OnReceive onR, ExecutableElement e, Element clazz, String className, boolean expectsList, String modelClass, String n, List args, List params, StringBuilder urlBefore, String jsonpVarName, StringBuilder urlAfter, String dataMirror) { + private boolean generateJSONReceiveBody(int index, StringWriter method, StringBuilder body, OnReceive onR, ExecutableElement e, Element clazz, String className, boolean expectsList, String modelClass, String n, List args, List params, StringBuilder urlBefore, String jsonpVarName, StringBuilder urlAfter, String dataMirror, StringBuilder headers) { body.append( " case " + index + ": {\n" + " if (type == 2) { /* on error */\n" + @@ -1247,7 +1263,8 @@ " }\n" + " }\n" ); - method.append(" proto.loadJSON(" + index + ",\n "); + method.append(" proto.loadJSONWithHeaders(" + index + ",\n "); + method.append(headers.length() == 0 ? "null" : headers).append(",\n "); method.append(urlBefore).append(", "); if (jsonpVarName != null) { method.append(urlAfter); @@ -1271,7 +1288,7 @@ return false; } - private boolean generateWSReceiveBody(int index, StringWriter method, StringBuilder body, OnReceive onR, ExecutableElement e, Element clazz, String className, boolean expectsList, String modelClass, String n, List args, List params, StringBuilder urlBefore, String jsonpVarName, StringBuilder urlAfter, String dataMirror) { + private boolean generateWSReceiveBody(int index, StringWriter method, StringBuilder body, OnReceive onR, ExecutableElement e, Element clazz, String className, boolean expectsList, String modelClass, String n, List args, List params, StringBuilder urlBefore, String jsonpVarName, StringBuilder urlAfter, String dataMirror, StringBuilder headers) { body.append( " case " + index + ": {\n" + " if (type == 0) { /* on open */\n" + @@ -1655,37 +1672,35 @@ return false; } - private Iterable findParamNames( - Element e, String url, String jsonParam, StringBuilder... both + private void findParamNames( + Set params, Element e, String url, String jsonParam, StringBuilder... both ) { - Set params = new LinkedHashSet(); int wasJSON = 0; for (int pos = 0; ;) { int next = url.indexOf('{', pos); if (next == -1) { both[wasJSON].append('"') - .append(url.substring(pos)) + .append(url.substring(pos).replace("\"", "\\\"")) .append('"'); - return params; + return; } int close = url.indexOf('}', next); if (close == -1) { error("Unbalanced '{' and '}' in " + url, e); - return params; + return; } final String paramName = url.substring(next + 1, close); - if (params.add(paramName)) { - if (paramName.equals(jsonParam) && !jsonParam.isEmpty()) { - both[wasJSON].append('"') - .append(url.substring(pos, next)) - .append('"'); - wasJSON = 1; - } else { - both[wasJSON].append('"') - .append(url.substring(pos, next)) - .append("\" + ").append(paramName).append(" + "); - } + params.add(paramName); + if (paramName.equals(jsonParam) && !jsonParam.isEmpty()) { + both[wasJSON].append('"') + .append(url.substring(pos, next).replace("\"", "\\\"")) + .append('"'); + wasJSON = 1; + } else { + both[wasJSON].append('"') + .append(url.substring(pos, next).replace("\"", "\\\"")) + .append("\" + ").append(paramName).append(" + "); } pos = close + 1; } diff -r a7bec1c91507 json/src/main/java/org/netbeans/html/json/impl/PropertyBindingAccessor.java --- a/json/src/main/java/org/netbeans/html/json/impl/PropertyBindingAccessor.java Wed May 27 22:40:01 2015 +0200 +++ b/json/src/main/java/org/netbeans/html/json/impl/PropertyBindingAccessor.java Wed May 27 23:28:35 2015 +0200 @@ -43,7 +43,6 @@ package org.netbeans.html.json.impl; import net.java.html.BrwsrCtx; -import org.netbeans.html.json.spi.FunctionBinding; import org.netbeans.html.json.spi.JSONCall; import org.netbeans.html.json.spi.PropertyBinding; import org.netbeans.html.json.spi.Proto; @@ -59,7 +58,7 @@ if (DEFAULT != null) throw new IllegalStateException(); DEFAULT = this; } - + static { JSON.initClass(PropertyBinding.class); } @@ -68,34 +67,36 @@ Proto.Type access, Bindings bindings, String name, int index, M model, boolean readOnly ); protected abstract JSONCall newCall( - BrwsrCtx ctx, RcvrJSON callback, String urlBefore, String urlAfter, + BrwsrCtx ctx, RcvrJSON callback, + String headers, String urlBefore, String urlAfter, String method, Object data ); - + protected abstract Bindings bindings(Proto proto, boolean initialize); protected abstract void notifyChange(Proto proto, int propIndex); protected abstract Proto findProto(Proto.Type type, Object object); protected abstract Model cloneTo(Proto.Type type, Model model, BrwsrCtx c); protected abstract Object read(Proto.Type from, BrwsrCtx c, Object data); - + static Bindings getBindings(Proto proto, boolean initialize) { return DEFAULT.bindings(proto, initialize); } - + static void notifyProtoChange(Proto proto, int propIndex) { DEFAULT.notifyChange(proto, propIndex); } - + static PropertyBinding create( Proto.Type access, Bindings bindings, String name, int index, M model , boolean readOnly ) { return DEFAULT.newBinding(access, bindings, name, index, model, readOnly); } - static JSONCall createCall( - BrwsrCtx ctx, RcvrJSON callback, String urlBefore, String urlAfter, + public static JSONCall createCall( + BrwsrCtx ctx, RcvrJSON callback, + String headers, String urlBefore, String urlAfter, String method, Object data ) { - return DEFAULT.newCall(ctx, callback, urlBefore, urlAfter, method, data); + return DEFAULT.newCall(ctx, callback, headers, urlBefore, urlAfter, method, data); } static Proto protoFor(Proto.Type type, Object object) { return DEFAULT.findProto(type, object); diff -r a7bec1c91507 json/src/main/java/org/netbeans/html/json/spi/JSONCall.java --- a/json/src/main/java/org/netbeans/html/json/spi/JSONCall.java Wed May 27 22:40:01 2015 +0200 +++ b/json/src/main/java/org/netbeans/html/json/spi/JSONCall.java Wed May 27 23:28:35 2015 +0200 @@ -54,30 +54,36 @@ */ public final class JSONCall { private final RcvrJSON whenDone; + private final String headers; private final String urlBefore; private final String urlAfter; private final String method; private final Object data; private final BrwsrCtx ctx; - JSONCall(BrwsrCtx ctx, RcvrJSON whenDone, String urlBefore, String urlAfter, String method, Object data) { + JSONCall( + BrwsrCtx ctx, RcvrJSON whenDone, + String headers, String urlBefore, String urlAfter, + String method, Object data + ) { this.ctx = ctx; this.whenDone = whenDone; + this.headers = headers; this.urlBefore = urlBefore; this.urlAfter = urlAfter; this.method = method; this.data = data; } - - /** Do we have some data to send? Can the {@link #writeData(java.io.OutputStream)} method be + + /** Do we have some data to send? Can the {@link #writeData(java.io.OutputStream)} method be * called? - * + * * @return true, if the call has some data to send */ public boolean isDoOutput() { return this.data != null; } - + public void writeData(OutputStream os) throws IOException { if (this.data == null) { throw new IOException("No data!"); @@ -85,15 +91,25 @@ os.write(this.data.toString().getBytes("UTF-8")); os.flush(); } - + + /** Additional headers to be included in the request. + * Usually multiline string to be appended into the header. + * + * @return null or string with prepared (HTTP) request headers + * @since 1.2 + */ + public String getHeaders() { + return headers; + } + public String getMethod() { return method; } - + public boolean isJSONP() { return urlAfter != null; } - + public String composeURL(String jsonpCallback) { if ((urlAfter == null) != (jsonpCallback == null)) { throw new IllegalStateException(); @@ -112,7 +128,7 @@ dispatch(RcvrJSON.MsgEvnt.createMessage(result)); } } - + public void notifyError(Throwable error) { if (error == null) { dispatch(RcvrJSON.MsgEvnt.createClose()); @@ -120,7 +136,7 @@ dispatch(RcvrJSON.MsgEvnt.createError(error)); } } - + private void dispatch(final RcvrJSON.MsgEvnt ev) { ctx.execute(new Runnable() { @Override diff -r a7bec1c91507 json/src/main/java/org/netbeans/html/json/spi/PropertyBinding.java --- a/json/src/main/java/org/netbeans/html/json/spi/PropertyBinding.java Wed May 27 22:40:01 2015 +0200 +++ b/json/src/main/java/org/netbeans/html/json/spi/PropertyBinding.java Wed May 27 23:28:35 2015 +0200 @@ -50,8 +50,8 @@ import org.netbeans.html.json.impl.PropertyBindingAccessor; import org.netbeans.html.json.impl.RcvrJSON; -/** Describes a property when one is asked to - * bind it +/** Describes a property when one is asked to + * bind it * * @author Jaroslav Tulach */ @@ -62,8 +62,8 @@ static { new PropertyBindingAccessor() { @Override - protected JSONCall newCall(BrwsrCtx ctx, RcvrJSON callback, String urlBefore, String urlAfter, String method, Object data) { - return new JSONCall(ctx, callback, urlBefore, urlAfter, method, data); + protected JSONCall newCall(BrwsrCtx ctx, RcvrJSON callback, String headers, String urlBefore, String urlAfter, String method, Object data) { + return new JSONCall(ctx, callback, headers, urlBefore, urlAfter, method, data); } @Override @@ -108,33 +108,33 @@ /** Changes value of the property. Can be called only on dedicated * thread. See {@link Technology#runSafe(java.lang.Runnable)}. - * + * * @param v new value of the property */ public abstract void setValue(Object v); - + /** Obtains current value of the property this binding represents. * Can be called only on dedicated * thread. See {@link Technology#runSafe(java.lang.Runnable)}. - * + * * @return the value or null */ public abstract Object getValue(); - + /** Is this property read only? Or can one call {@link #setValue(java.lang.Object)}? - * + * * @return true, if this property is read only */ public abstract boolean isReadOnly(); /** Returns identical version of the binding, but one that holds on the * original model object via weak reference. - * + * * @return binding that uses weak reference * @since 1.1 */ public abstract PropertyBinding weak(); - + private static abstract class AImpl extends PropertyBinding { public final String name; public final boolean readOnly; @@ -149,7 +149,7 @@ this.access = access; this.readOnly = readOnly; } - + protected abstract M model(); @Override @@ -182,7 +182,7 @@ return name; } } // end of PBData - + private static final class Impl extends AImpl { private final M model; @@ -201,7 +201,7 @@ return new Weak(model, bindings, name, index, access, readOnly); } } - + private static final class Weak extends AImpl { private final Reference ref; public Weak(M model, Bindings bindings, String name, int index, Proto.Type access, boolean readOnly) { diff -r a7bec1c91507 json/src/main/java/org/netbeans/html/json/spi/Proto.java --- a/json/src/main/java/org/netbeans/html/json/spi/Proto.java Wed May 27 22:40:01 2015 +0200 +++ b/json/src/main/java/org/netbeans/html/json/spi/Proto.java Wed May 27 23:28:35 2015 +0200 @@ -49,7 +49,9 @@ import net.java.html.json.Model; import org.netbeans.html.json.impl.Bindings; import org.netbeans.html.json.impl.JSON; +import org.netbeans.html.json.impl.JSON.WS; import org.netbeans.html.json.impl.JSONList; +import org.netbeans.html.json.impl.PropertyBindingAccessor; import org.netbeans.html.json.impl.RcvrJSON; import org.netbeans.html.json.impl.RcvrJSON.MsgEvnt; @@ -57,8 +59,8 @@ * {@link Model} annotation. Contains methods the generated class can * use to communicate with behind the scene associated {@link Technology}. * Each {@link Proto} object is associated with - * singletonizer-like interface {@link Type} which provides the - * associated {@link Technology} the necessary information about the + * singletonizer-like interface {@link Type} which provides the + * associated {@link Technology} the necessary information about the * generated {@link Model} class. * * @author Jaroslav Tulach @@ -79,8 +81,8 @@ /** Browser context this proto object and its associated model * are operating-in. - * - * @return the associated context + * + * @return the associated context */ public BrwsrCtx getContext() { return context; @@ -89,19 +91,19 @@ /** Acquires global lock to compute a {@link ComputedProperty derived property} * on this proto object. This proto object must not be locked yet. No * dependency tracking is performed. - * + * * @throws IllegalStateException if already locked */ public void acquireLock() throws IllegalStateException { acquireLock(null); } - + /** Acquires global lock to compute a {@link ComputedProperty derived property} * on this proto object. This proto object must not be locked yet. The * name of the property is used to track dependencies on own * properties of other proto objects - when they are changed, this * {@link #valueHasMutated(java.lang.String) property is changed too}. - * + * * @param propName name of property we are about to compute * @throws IllegalStateException thrown when there is a cyclic * call is detected @@ -110,14 +112,14 @@ public void acquireLock(String propName) throws IllegalStateException { Observers.beginComputing(this, propName); } - + /** A property on this proto object is about to be accessed. Verifies * whether this proto object is accessible - e.g. it has not been * {@link #acquireLock() locked yet}. If everything is OK, the * propName is recorded in the chain of dependencies * tracked by {@link #acquireLock(java.lang.String)} and watched by * {@link #valueHasMutated(java.lang.String)}. - * + * * @param propName name of the property that is requested * @throws IllegalStateException if the model is locked * @since 0.9 @@ -125,27 +127,27 @@ public void accessProperty(String propName) throws IllegalStateException { Observers.accessingValue(this, propName); } - + /** Verifies the model is not locked otherwise throws an exception. * @throws IllegalStateException if the model is locked */ public void verifyUnlocked() throws IllegalStateException { Observers.verifyUnlocked(this); } - - /** When modifications are over, the model is switched into + + /** When modifications are over, the model is switched into * unlocked state by calling this method. */ public void releaseLock() { Observers.finishComputing(this); } - + /** Whenever model changes a property. It should notify the - * associated technology by calling this method. + * associated technology by calling this method. * Since 0.8.3: This method may be called by any thread - it reschedules * its actual execution into appropriate one by using * {@link BrwsrCtx#execute(java.lang.Runnable)}. - * + * * @param propName name of the changed property */ public void valueHasMutated(final String propName) { @@ -168,7 +170,7 @@ * Since 0.8.3: This method may be called by any thread - it reschedules * its actual execution into appropriate one by using * {@link BrwsrCtx#execute(java.lang.Runnable)}. - * + * * @param propName name of the changed property * @param oldValue provides previous value of the property * @param newValue provides new value of the property @@ -187,21 +189,21 @@ } }); } - + /** Initializes the associated model in the current {@link #getContext() context}. - * In case of knockout.js technology, applies given bindings + * In case of knockout.js technology, applies given bindings * of the current model to the body element of the page. */ public void applyBindings() { initBindings().applyBindings(null); } - + /** Initializes the associated model to the specified element's subtree. * The technology is taken from the current {@link #getContext() context} and - * in case of knockout.js applies given bindings + * in case of knockout.js applies given bindings * of the current model to the element of the page with 'id' attribute * set to the specified id value. - * + * * @param id the id of element to apply the binding to * @since 1.1 * @see Technology.ApplyId @@ -209,13 +211,13 @@ public void applyBindings(String id) { initBindings().applyBindings(id); } - + /** Invokes the provided runnable in the {@link #getContext() context} * of the browser. If the caller is already on the right thread, the - * run.run() is invoked immediately and synchronously. + * run.run() is invoked immediately and synchronously. * Otherwise the method returns immediately and the run() * method is performed later - * + * * @param run the action to execute */ public void runInBrowser(Runnable run) { @@ -224,10 +226,10 @@ /** Invokes the specified function index in the {@link #getContext() context} * of the browser. If the caller is already on the right thread, the - * index-th function is invoked immediately and synchronously. + * index-th function is invoked immediately and synchronously. * Otherwise the method returns immediately and the function is invoked * later. - * + * * @param index the index of the function as will be passed to * {@link Type#call(java.lang.Object, int, java.lang.Object, java.lang.Object)} * method @@ -247,11 +249,11 @@ } }); } - + /** Initializes the provided collection with a content of the array. - * The initialization can only be done soon after the the collection + * The initialization can only be done soon after the the collection * is created, otherwise an exception is throw - * + * * @param to the collection to initialize (assumed to be empty) * @param array the array to add to the collection * @throws IllegalStateException if the system has already been initialized @@ -270,7 +272,7 @@ /** Takes an object representing JSON result and extract some of its * properties. It is assumed that the props and * values arrays have the same length. - * + * * @param json the JSON object (actual type depends on the associated * {@link Technology}) * @param props list of properties to extract @@ -281,7 +283,7 @@ } /** Converts raw JSON data into a Java {@link Model} class. - * + * * @param type of the model class * @param modelClass the type of the class to create * @param data the raw JSON data @@ -294,29 +296,29 @@ /** Initializes asynchronous JSON connection to specified URL. Delegates * to {@link #loadJSON(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object, java.lang.Object...) } * with no extra parameters. - * + * * @param index the callback index to be used when a reply is received * to call {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object)}. - * + * * @param urlBefore the part of the URL before JSON-P callback parameter * @param urlAfter the rest of the URL or null if no JSON-P is used * @param method method to use for connection to the server * @param data string, number or a {@link Model} generated class to send to * the server when doing a query */ - public void loadJSON(final int index, + public void loadJSON(final int index, String urlBefore, String urlAfter, String method, final Object data ) { loadJSON(index, urlBefore, urlAfter, method, data, new Object[0]); } - - /** Initializes asynchronous JSON connection to specified URL. The + + /** Initializes asynchronous JSON connection to specified URL. The * method returns immediately and later does callback later. - * + * * @param index the callback index to be used when a reply is received * to call {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object)}. - * + * * @param urlBefore the part of the URL before JSON-P callback parameter * @param urlAfter the rest of the URL or null if no JSON-P is used * @param method method to use for connection to the server @@ -326,7 +328,31 @@ * {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object, java.lang.Object[])} * @since 0.8.1 */ - public void loadJSON(final int index, + public void loadJSON(final int index, + String urlBefore, String urlAfter, String method, + final Object data, final Object... params + ) { + loadJSONWithHeaders(index, null, urlBefore, urlAfter, method, data, params); + } + + /** Initializes asynchronous JSON connection to specified URL. The + * method returns immediately and later does callback later. + * + * @param index the callback index to be used when a reply is received + * to call {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object)}. + * + * @param headers headers to use for the request or null to use default ones + * @param urlBefore the part of the URL before JSON-P callback parameter + * @param urlAfter the rest of the URL or null if no JSON-P is used + * @param method method to use for connection to the server + * @param data string, number or a {@link Model} generated class to send to + * the server when doing a query + * @param params extra params to pass back when calling + * {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object, java.lang.Object[])} + * @since 1.2 + */ + public void loadJSONWithHeaders(final int index, + String headers, String urlBefore, String urlAfter, String method, final Object data, final Object... params ) { @@ -341,12 +367,16 @@ type.onMessage(obj, index, 2, msg.getException(), params); } } - JSON.loadJSON(context, new Rcvr(), urlBefore, urlAfter, method, data); + JSONCall call = PropertyBindingAccessor.createCall( + context, new Rcvr(), headers, urlBefore, urlAfter, method, data + ); + Transfer t = JSON.findTransfer(context); + t.loadJSON(call); } - - /** Opens new WebSocket connection to the specified URL. - * - * @param index the index to use later during callbacks to + + /** Opens new WebSocket connection to the specified URL. + * + * @param index the index to use later during callbacks to * {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object)} * @param url the ws:// or wss:// URL to connect to * @param data data to send to server (usually null) @@ -359,12 +389,12 @@ protected void onError(MsgEvnt msg) { type.onMessage(obj, index, 2, msg.getException()); } - + @Override protected void onMessage(MsgEvnt msg) { type.onMessage(obj, index, 1, msg.getValues()); } - + @Override protected void onClose(MsgEvnt msg) { type.onMessage(obj, index, 3, null); @@ -375,24 +405,26 @@ type.onMessage(obj, index, 0, null); } } - return JSON.openWS(context, new WSrcvr(), url, data); + WS ws = WS.create(JSON.findWSTransfer(context), new WSrcvr()); + ws.send(context, null, url, data); + return ws; } - + /** Sends a message to existing socket. - * + * * @param webSocket the socket to send message to * @param url the ws:// or wss:// URL to connect to, - * preferably the same as the one used when the socket was + * preferably the same as the one used when the socket was * {@link #wsOpen(int, java.lang.String, java.lang.Object) opened} * @param data the data to send or null if the socket is * supposed to be closed */ public void wsSend(Object webSocket, String url, Object data) { - ((JSON.WS)webSocket).send(context, url, data); + ((JSON.WS)webSocket).send(context, null, url, data); } /** Converts raw data (one of its properties) to string representation. - * + * * @param data the object * @param propName the name of object property or null * if the whole object should be converted @@ -401,9 +433,9 @@ public String toString(Object data, String propName) { return JSON.toString(context, data, propName); } - + /** Converts raw data (one of its properties) to a number representation. - * + * * @param data the object * @param propName the name of object property or null * if the whole object should be converted @@ -414,7 +446,7 @@ } /** Converts raw JSON data into a {@link Model} class representation. - * + * * @param type of the model to create * @param type class of the model to create * @param data raw JSON data (depends on associated {@link Technology}) @@ -426,7 +458,7 @@ } /** Creates new JSON like observable list. - * + * * @param the type of the list elements * @param propName name of a property this list is associated with * @param onChange index of the property to use when the list is modified @@ -441,7 +473,7 @@ /** Copies content of one collection to another, re-assigning all its * elements from their current context to the new ctx. - * + * * @param type of the collections * @param to the target collection to be filled with cloned values * @param ctx context for the new collection @@ -460,15 +492,15 @@ } } } - + // // internal state // - + final String toStr() { return "Proto[" + obj + "]@" + Integer.toHexString(System.identityHashCode(this)); } - + final Bindings initBindings() { if (ko == null) { Bindings b = Bindings.apply(context, obj); @@ -507,7 +539,7 @@ /** Functionality used by the code generated by annotation * processor for the {@link net.java.html.json.Model} annotation. - * + * * @param the generated class * @since 0.7 */ @@ -519,7 +551,7 @@ /** Constructor for subclasses generated by the annotation processor * associated with {@link net.java.html.json.Model} annotation. - * + * * @param clazz the generated model class * @param modelFor the original class annotated by the {@link net.java.html.json.Model} annotation. * @param properties number of properties the class has @@ -543,7 +575,7 @@ /** Registers property for the type. It is expected each index * is initialized only once. - * + * * @param name name of the property * @param index index of the property * @param readOnly is the property read only? @@ -555,7 +587,7 @@ } /** Registers function of given name at given index. - * + * * @param name name of the function * @param index name of the type */ @@ -563,10 +595,10 @@ assert functions[index] == null; functions[index] = name; } - + /** Creates new proto-object for given {@link Model} class bound to * provided context. - * + * * @param obj instance of appropriate {@link Model} class * @param context the browser context * @return new proto-object that the generated class can use for @@ -575,32 +607,32 @@ public Proto createProto(Object obj, BrwsrCtx context) { return new Proto(obj, this, context); } - + // // Implemented by subclasses // - + /** Sets value of a {@link #registerProperty(java.lang.String, int, boolean) registered property} * to new value. - * + * * @param model the instance of {@link Model model class} * @param index index of the property used during registration * @param value the value to set the property to */ protected abstract void setValue(Model model, int index, Object value); - - /** Obtains and returns value of a + + /** Obtains and returns value of a * {@link #registerProperty(java.lang.String, int, boolean) registered property}. - * + * * @param model the instance of {@link Model model class} * @param index index of the property used during registration * @return current value of the property */ protected abstract Object getValue(Model model, int index); - + /** Invokes a {@link #registerFunction(java.lang.String, int)} registered function * on given object. - * + * * @param model the instance of {@link Model model class} * @param index index of the property used during registration * @param data the currently selected object the function is about to operate on @@ -609,43 +641,43 @@ */ protected abstract void call(Model model, int index, Object data, Object event) throws Exception; - + /** Re-binds the model object to new browser context. - * + * * @param model the instance of {@link Model model class} * @param ctx browser context to clone the object to * @return new instance of the model suitable for new context */ protected abstract Model cloneTo(Model model, BrwsrCtx ctx); - + /** Reads raw JSON data and converts them to our model class. - * + * * @param c the browser context to work in * @param json raw JSON data to get values from * @return new instance of model class filled by the data */ protected abstract Model read(BrwsrCtx c, Object json); - + /** Called when a {@link #registerProperty(java.lang.String, int, boolean) registered property} * changes its value. - * + * * @param model the object that has the property * @param index the index of the property during registration */ protected abstract void onChange(Model model, int index); - + /** Finds out if there is an associated proto-object for given * object. - * + * * @param object an object, presumably (but not necessarily) instance of Model class * @return associated proto-object or null */ protected abstract Proto protoFor(Object object); - /** Called to report results of asynchronous over-the-wire + /** Called to report results of asynchronous over-the-wire * communication. Result of calling {@link Proto#wsOpen(int, java.lang.String, java.lang.Object)} * or {@link Proto#loadJSON(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object, java.lang.Object...)}. - * + * * @param model the instance of the model class * @param index index used during initiating the communication (via loadJSON or wsOpen calls) * @param type type of the message: 0 - onOpen, 1 - onMessage, 2 - onError, 3 - onClose - @@ -656,11 +688,11 @@ protected void onMessage(Model model, int index, int type, Object data) { onMessage(model, index, type, data, new Object[0]); } - - /** Called to report results of asynchronous over-the-wire + + /** Called to report results of asynchronous over-the-wire * communication. Result of calling {@link Proto#wsOpen(int, java.lang.String, java.lang.Object)} * or {@link Proto#loadJSON(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object, java.lang.Object...)}. - * + * * @param model the instance of the model class * @param index index used during initiating the communication (via loadJSON or wsOpen calls) * @param type type of the message: 0 - onOpen, 1 - onMessage, 2 - onError, 3 - onClose - @@ -682,7 +714,7 @@ /** Converts and array of raw JSON objects into an array of typed * Java {@link Model} classes. - * + * * @param the type of the destination array * @param context browser context to use * @param src array of raw JSON objects @@ -694,7 +726,7 @@ dest[i] = org.netbeans.html.json.impl.JSON.read(context, destType, src[i]); } } - + /** Compares two objects that can be converted to integers. * @param a first value * @param b second value @@ -719,7 +751,7 @@ * @param a first value * @param b second value * @return true if they are equals - */ + */ public final boolean isSame(Object a, Object b) { if (a == b) { return true; @@ -739,18 +771,18 @@ public final int hashPlus(Object o, int h) { return o == null ? h : h ^ o.hashCode(); } - + /** Converts an object to its JSON value. - * + * * @param obj the object to convert * @return JSON representation of the object */ public final String toJSON(Object obj) { return JSON.toJSON(obj); } - + /** Converts the value to string. - * + * * @param val the value * @return the converted value */ @@ -759,7 +791,7 @@ } /** Converts the value to number. - * + * * @param val the value * @return the converted value */ @@ -768,7 +800,7 @@ } /** Converts the value to character. - * + * * @param val the value * @return the converted value */ @@ -777,16 +809,16 @@ } /** Converts the value to boolean. - * + * * @param val the value * @return the converted value */ public final Boolean boolValue(Object val) { return JSON.boolValue(val); } - + /** Extracts value of specific type from given object. - * + * * @param the type of object one is interested in * @param type the type * @param val the object to convert to type @@ -833,7 +865,7 @@ * takes the provided collection, empties it and fills it again * with values extracted from value (which is supposed * to be an array). - * + * * @param the type of list elements * @param arr collection to fill with elements in value * @param type the type of elements in the collection diff -r a7bec1c91507 json/src/test/java/net/java/html/json/ModelProcessorTest.java --- a/json/src/test/java/net/java/html/json/ModelProcessorTest.java Wed May 27 22:40:01 2015 +0200 +++ b/json/src/test/java/net/java/html/json/ModelProcessorTest.java Wed May 27 23:28:35 2015 +0200 @@ -65,7 +65,7 @@ + "})\n" + "class X {\n" + "}\n"; - + Compile c = Compile.create(html, code); assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors()); boolean ok = false; @@ -81,7 +81,7 @@ fail("Should contain warning about Runnable:" + msgs); } } - + @Test public void verifyWrongTypeInInnerClass() throws IOException { String html = "" + ""; @@ -95,7 +95,7 @@ + " static class Inner {\n" + " }\n" + "}\n"; - + Compile c = Compile.create(html, code); assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors()); boolean ok = false; @@ -111,7 +111,7 @@ fail("Should contain warning about Runnable:" + msgs); } } - + @Test public void warnOnNonStatic() throws IOException { String html = "" + ""; @@ -127,7 +127,7 @@ + " return prop;\n" + " }\n" + "}\n"; - + Compile c = Compile.create(html, code); assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors()); boolean ok = false; @@ -143,7 +143,7 @@ fail("Should contain warning about non-static method:" + msgs); } } - + @Test public void computedCantReturnVoid() throws IOException { String html = "" + ""; @@ -158,7 +158,7 @@ + " @ComputedProperty static void y(int prop) {\n" + " }\n" + "}\n"; - + Compile c = Compile.create(html, code); assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors()); boolean ok = false; @@ -174,7 +174,7 @@ fail("Should contain warning about non-static method:" + msgs); } } - + @Test public void computedCantReturnRunnable() throws IOException { String html = "" + ""; @@ -190,7 +190,7 @@ + " return null;\n" + " }\n" + "}\n"; - + Compile c = Compile.create(html, code); assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors()); boolean ok = false; @@ -206,7 +206,7 @@ fail("Should contain warning about non-static method:" + msgs); } } - + @Test public void canWeCompileWithJDK1_5SourceLevel() throws IOException { String html = "" + ""; @@ -220,11 +220,11 @@ + "class X {\n" + " @ComputedProperty static double derived(long prop) { return prop; }" + "}\n"; - + Compile c = Compile.create(html, code, "1.5"); assertTrue(c.getErrors().isEmpty(), "No errors: " + c.getErrors()); } - + @Test public void putNeedsDataArgument() throws Exception { needsAnArg("PUT"); } @@ -232,7 +232,7 @@ @Test public void postNeedsDataArgument() throws Exception { needsAnArg("POST"); } - + private void needsAnArg(String method) throws Exception { String html = "" + ""; @@ -250,7 +250,7 @@ + " @OnReceive(method=\"" + method + "\", url=\"whereever\")\n" + " static void obtained(XModel m, PQ p) { }\n" + "}\n"; - + Compile c = Compile.create(html, code); assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors()); for (Diagnostic diagnostic : c.getErrors()) { @@ -260,10 +260,10 @@ } } fail("Needs an error message about missing data():\n" + c.getErrors()); - + } - - + + @Test public void jsonNeedsToUseGet () throws Exception { String html = "" + ""; @@ -281,7 +281,7 @@ + " @OnReceive(method=\"POST\", jsonp=\"callback\", url=\"whereever\")\n" + " static void obtained(XModel m, PQ p) { }\n" + "}\n"; - + Compile c = Compile.create(html, code); assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors()); for (Diagnostic diagnostic : c.getErrors()) { @@ -291,9 +291,99 @@ } } fail("Needs an error message about wrong method:\n" + c.getErrors()); - + } - + + @Test public void noHeadersForWebSockets() throws Exception { + String html = "" + + ""; + String code = "package x.y.z;\n" + + "import net.java.html.json.Model;\n" + + "import net.java.html.json.Property;\n" + + "import net.java.html.json.OnReceive;\n" + + "@Model(className=\"XModel\", properties={\n" + + " @Property(name=\"prop\", type=long.class)\n" + + "})\n" + + "class X {\n" + + " @Model(className=\"PQ\", properties={})\n" + + " class PImpl {\n" + + " }\n" + + " @OnReceive(method=\"WebSocket\", data = PQ.class, headers=\"SomeHeader: {some}\", url=\"whereever\")\n" + + " static void obtained(XModel m, PQ p) { }\n" + + "}\n"; + + Compile c = Compile.create(html, code); + assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors()); + for (Diagnostic diagnostic : c.getErrors()) { + String msg = diagnostic.getMessage(Locale.ENGLISH); + if (msg.contains("WebSocket spec does not support headers")) { + return; + } + } + fail("Needs an error message about headers:\n" + c.getErrors()); + + } + + @Test public void noNewLinesInHeaderLines() throws Exception { + String html = "" + + ""; + String code = "package x.y.z;\n" + + "import net.java.html.json.Model;\n" + + "import net.java.html.json.Property;\n" + + "import net.java.html.json.OnReceive;\n" + + "@Model(className=\"XModel\", properties={\n" + + " @Property(name=\"prop\", type=long.class)\n" + + "})\n" + + "class X {\n" + + " @Model(className=\"PQ\", properties={})\n" + + " class PImpl {\n" + + " }\n" + + " @OnReceive(headers=\"SomeHeader\\n: {some}\", url=\"whereever\")\n" + + " static void obtained(XModel m, PQ p) { }\n" + + "}\n"; + + Compile c = Compile.create(html, code); + assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors()); + for (Diagnostic diagnostic : c.getErrors()) { + String msg = diagnostic.getMessage(Locale.ENGLISH); + if (msg.contains("Header line cannot contain line separator")) { + return; + } + } + fail("Needs an error message about headers:\n" + c.getErrors()); + + } + + @Test public void noReturnInHeaderLines() throws Exception { + String html = "" + + ""; + String code = "package x.y.z;\n" + + "import net.java.html.json.Model;\n" + + "import net.java.html.json.Property;\n" + + "import net.java.html.json.OnReceive;\n" + + "@Model(className=\"XModel\", properties={\n" + + " @Property(name=\"prop\", type=long.class)\n" + + "})\n" + + "class X {\n" + + " @Model(className=\"PQ\", properties={})\n" + + " class PImpl {\n" + + " }\n" + + " @OnReceive(headers=\"Some\\rHeader: {some}\", url=\"whereever\")\n" + + " static void obtained(XModel m, PQ p) { }\n" + + "}\n"; + + Compile c = Compile.create(html, code); + assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors()); + for (Diagnostic diagnostic : c.getErrors()) { + String msg = diagnostic.getMessage(Locale.ENGLISH); + if (msg.contains("Header line cannot contain line separator")) { + return; + } + } + fail("Needs an error message about headers:\n" + c.getErrors()); + + } + @Test public void onErrorHasToExist() throws IOException { Compile res = Compile.create("", "package x;\n" + "@net.java.html.json.Model(className=\"MyModel\", properties= {\n" @@ -308,7 +398,7 @@ res.assertErrors(); res.assertError("not find doesNotExist"); } - + @Test public void usingListIsOK() throws IOException { Compile res = Compile.create("", "package x;\n" + "@net.java.html.json.Model(className=\"MyModel\", properties= {\n" @@ -370,7 +460,7 @@ res.assertErrors(); res.assertError("Cannot have the name"); } - + @Test public void onWebSocketJustTwoArgs() throws IOException { Compile res = Compile.create("", "package x;\n" + "@net.java.html.json.Model(className=\"MyModel\", properties= {\n" @@ -385,7 +475,7 @@ res.assertErrors(); res.assertError("only have two arg"); } - + @Test public void onErrorWouldHaveToBeStatic() throws IOException { Compile res = Compile.create("", "package x;\n" + "@net.java.html.json.Model(className=\"MyModel\", properties= {\n" diff -r a7bec1c91507 json/src/test/java/net/java/html/json/ModelTest.java --- a/json/src/test/java/net/java/html/json/ModelTest.java Wed May 27 22:40:01 2015 +0200 +++ b/json/src/test/java/net/java/html/json/ModelTest.java Wed May 27 23:28:35 2015 +0200 @@ -227,6 +227,18 @@ } + @OnReceive(url="{url}", headers={ + "Easy: {easy}", + "H-a+r!d?e.r: {harder}", + "H-a+r!d?e's\"t: {harder}", + "Repeat-ed: {rep}", + "Repeat+ed: {rep}", + "Same-URL: {url}" + }) + static void fetchPeopleWithHeaders(Modelik model, People p) { + model.fetchPeopleWithHeaders("url", "easy", "harder", "rep"); + } + @OnReceive(url = "{protocol}://{host}?callback={back}&query={query}", jsonp = "back") static void loadPeopleViaJSONP(Modelik thiz, People p) { Modelik m = null; diff -r a7bec1c91507 ko-felix-test/src/test/java/org/netbeans/html/ko/felix/test/DynamicHTTP.java --- a/ko-felix-test/src/test/java/org/netbeans/html/ko/felix/test/DynamicHTTP.java Wed May 27 22:40:01 2015 +0200 +++ b/ko-felix-test/src/test/java/org/netbeans/html/ko/felix/test/DynamicHTTP.java Wed May 27 23:28:35 2015 +0200 @@ -76,28 +76,28 @@ private static List resources; private static ServerConfiguration conf; private static HttpServer server; - + private DynamicHTTP() { } - + static URI initServer() throws Exception { server = HttpServer.createSimpleServer(null, new PortRange(8080, 65535)); final WebSocketAddOn addon = new WebSocketAddOn(); for (NetworkListener listener : server.getListeners()) { listener.registerAddOn(addon); - } + } resources = new ArrayList(); conf = server.getServerConfiguration(); final DynamicHTTP dh = new DynamicHTTP(); conf.addHttpHandler(dh, "/"); - + server.start(); return pageURL("http", server, "/test.html"); } - + @Override public void service(Request request, Response response) throws Exception { if ("/test.html".equals(request.getRequestURI())) { @@ -159,6 +159,8 @@ sb.append((char) ch); } params[i] = sb.toString(); + } else if (r.parameters[i].startsWith("http.header.")) { + params[i] = request.getHeader(r.parameters[i].substring(12)); } } if (params[i] == null) { @@ -171,7 +173,7 @@ } } } - + private URI registerWebSocket(Resource r) { WebSocketEngine.getEngine().register("", r.httpPath, new WS(r)); return pageURL("ws", server, r.httpPath); @@ -184,7 +186,7 @@ } return pageURL("http", server, r.httpPath); } - + private static URI pageURL(String proto, HttpServer server, final String page) { NetworkListener listener = server.getListeners().iterator().next(); int port = listener.getPort(); @@ -194,7 +196,7 @@ throw new IllegalStateException(ex); } } - + static final class Resource { final InputStream httpContent; @@ -235,7 +237,7 @@ } } } - + private static class WS extends WebSocketApplication { private final Resource r; diff -r a7bec1c91507 ko-osgi-test/src/test/java/org/netbeans/html/ko/osgi/test/DynamicHTTP.java --- a/ko-osgi-test/src/test/java/org/netbeans/html/ko/osgi/test/DynamicHTTP.java Wed May 27 22:40:01 2015 +0200 +++ b/ko-osgi-test/src/test/java/org/netbeans/html/ko/osgi/test/DynamicHTTP.java Wed May 27 23:28:35 2015 +0200 @@ -76,28 +76,28 @@ private static List resources; private static ServerConfiguration conf; private static HttpServer server; - + private DynamicHTTP() { } - + static URI initServer() throws Exception { server = HttpServer.createSimpleServer(null, new PortRange(8080, 65535)); final WebSocketAddOn addon = new WebSocketAddOn(); for (NetworkListener listener : server.getListeners()) { listener.registerAddOn(addon); - } + } resources = new ArrayList(); conf = server.getServerConfiguration(); final DynamicHTTP dh = new DynamicHTTP(); conf.addHttpHandler(dh, "/"); - + server.start(); return pageURL("http", server, "/test.html"); } - + @Override public void service(Request request, Response response) throws Exception { if ("/test.html".equals(request.getRequestURI())) { @@ -159,6 +159,8 @@ sb.append((char) ch); } params[i] = sb.toString(); + } else if (r.parameters[i].startsWith("http.header.")) { + params[i] = request.getHeader(r.parameters[i].substring(12)); } } if (params[i] == null) { @@ -171,7 +173,7 @@ } } } - + private URI registerWebSocket(Resource r) { WebSocketEngine.getEngine().register("", r.httpPath, new WS(r)); return pageURL("ws", server, r.httpPath); @@ -184,7 +186,7 @@ } return pageURL("http", server, r.httpPath); } - + private static URI pageURL(String proto, HttpServer server, final String page) { NetworkListener listener = server.getListeners().iterator().next(); int port = listener.getPort(); @@ -194,7 +196,7 @@ throw new IllegalStateException(ex); } } - + static final class Resource { final InputStream httpContent; @@ -235,7 +237,7 @@ } } } - + private static class WS extends WebSocketApplication { private final Resource r; diff -r a7bec1c91507 ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/LoadJSON.java --- a/ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/LoadJSON.java Wed May 27 22:40:01 2015 +0200 +++ b/ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/LoadJSON.java Wed May 27 23:28:35 2015 +0200 @@ -59,11 +59,11 @@ import java.util.concurrent.ThreadFactory; import java.util.logging.Logger; import net.java.html.js.JavaScriptBody; -import org.netbeans.html.json.spi.JSONCall; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; +import org.netbeans.html.json.spi.JSONCall; /** This is an implementation package - just * include its JAR on classpath and use official {@link Context} API @@ -101,7 +101,7 @@ final String url; Throwable error = null; Object json = null; - + if (call.isJSONP()) { url = call.composeURL("dummy"); } else { @@ -113,6 +113,28 @@ if (call.isDoOutput()) { conn.setDoOutput(true); } + String h = call.getHeaders(); + if (h != null) { + int pos = 0; + while (pos < h.length()) { + int tagEnd = h.indexOf(':', pos); + if (tagEnd == -1) { + break; + } + int r = h.indexOf('\r', tagEnd); + int n = h.indexOf('\n', tagEnd); + if (r == -1) { + r = h.length(); + } + if (n == -1) { + n = h.length(); + } + String key = h.substring(pos, tagEnd).trim(); + String val = h.substring(tagEnd + 1, Math.min(r, n)).trim(); + conn.setRequestProperty(key, val);; + pos = Math.max(r, n); + } + } if (call.getMethod() != null && conn instanceof HttpURLConnection) { ((HttpURLConnection) conn).setRequestMethod(call.getMethod()); } @@ -235,7 +257,7 @@ return o; } } - + public static void extractJSON(Object jsonObject, String[] props, Object[] values) { if (jsonObject instanceof JSONObject) { JSONObject obj = (JSONObject)jsonObject; @@ -252,7 +274,7 @@ values[i] = getProperty(jsonObject, props[i]); } } - + @JavaScriptBody(args = {"object", "property"}, body = "if (property === null) return object;\n" @@ -262,7 +284,7 @@ private static Object getProperty(Object object, String property) { return null; } - + public static Object parse(InputStream is) throws IOException { try { PushbackInputStream push = new PushbackInputStream(is, 1); diff -r a7bec1c91507 ko-ws-tyrus/src/test/java/org/netbeans/html/wstyrus/TyrusDynamicHTTP.java --- a/ko-ws-tyrus/src/test/java/org/netbeans/html/wstyrus/TyrusDynamicHTTP.java Wed May 27 22:40:01 2015 +0200 +++ b/ko-ws-tyrus/src/test/java/org/netbeans/html/wstyrus/TyrusDynamicHTTP.java Wed May 27 23:28:35 2015 +0200 @@ -75,28 +75,28 @@ private static List resources; private static ServerConfiguration conf; private static HttpServer server; - + private TyrusDynamicHTTP() { } - + static URI initServer() throws Exception { server = HttpServer.createSimpleServer(null, new PortRange(8080, 65535)); final WebSocketAddOn addon = new WebSocketAddOn(); for (NetworkListener listener : server.getListeners()) { listener.registerAddOn(addon); - } + } resources = new ArrayList(); conf = server.getServerConfiguration(); final TyrusDynamicHTTP dh = new TyrusDynamicHTTP(); conf.addHttpHandler(dh, "/"); - + server.start(); return pageURL("http", server, "/test.html"); } - + @Override public void service(Request request, Response response) throws Exception { if ("/test.html".equals(request.getRequestURI())) { @@ -158,6 +158,8 @@ sb.append((char) ch); } params[i] = sb.toString(); + } else if (r.parameters[i].startsWith("http.header.")) { + params[i] = request.getHeader(r.parameters[i].substring(12)); } } if (params[i] == null) { @@ -170,7 +172,7 @@ } } } - + private URI registerWebSocket(Resource r) { WebSocketEngine.getEngine().register("", r.httpPath, new WS(r)); return pageURL("ws", server, r.httpPath); @@ -183,7 +185,7 @@ } return pageURL("http", server, r.httpPath); } - + private static URI pageURL(String proto, HttpServer server, final String page) { NetworkListener listener = server.getListeners().iterator().next(); int port = listener.getPort(); @@ -193,7 +195,7 @@ throw new IllegalStateException(ex); } } - + static final class Resource { final InputStream httpContent; @@ -234,7 +236,7 @@ } } } - + private static class WS extends WebSocketApplication { private final Resource r; @@ -255,6 +257,6 @@ } } private static final Logger LOG = Logger.getLogger(WS.class.getName()); - + } } diff -r a7bec1c91507 ko4j/src/main/java/org/netbeans/html/ko4j/KOTransfer.java --- a/ko4j/src/main/java/org/netbeans/html/ko4j/KOTransfer.java Wed May 27 22:40:01 2015 +0200 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/KOTransfer.java Wed May 27 23:28:35 2015 +0200 @@ -46,6 +46,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; import org.netbeans.html.context.spi.Contexts; import org.netbeans.html.json.spi.JSONCall; import org.netbeans.html.json.spi.Transfer; @@ -62,7 +64,7 @@ implements Transfer { KOTransfer() { } - + @Override public void extract(Object obj, String[] props, Object[] values) { if (obj instanceof JSObjToStr) { @@ -87,7 +89,29 @@ call.notifyError(ex); } } - LoadJSON.loadJSON(call.composeURL(null), call, call.getMethod(), data); + List headerPairs = new ArrayList(); + String h = call.getHeaders(); + if (h != null) { + int pos = 0; + while (pos < h.length()) { + int tagEnd = h.indexOf(':', pos); + if (tagEnd == -1) { + break; + } + int r = h.indexOf('\r', tagEnd); + int n = h.indexOf('\n', tagEnd); + if (r == -1) { + r = h.length(); + } + if (n == -1) { + n = h.length(); + } + headerPairs.add(h.substring(pos, tagEnd).trim()); + headerPairs.add(h.substring(tagEnd + 1, Math.min(r, n)).trim()); + pos = Math.max(r, n); + } + } + LoadJSON.loadJSON(call.composeURL(null), call, call.getMethod(), data, headerPairs.toArray()); } } @@ -104,7 +128,7 @@ } return LoadJSON.parse(sb.toString()); } - + static void notifySuccess(Object done, Object str, Object data) { Object notifyObj; if (data instanceof Object[]) { @@ -118,11 +142,11 @@ } ((JSONCall)done).notifySuccess(notifyObj); } - + static void notifyError(Object done, Object msg) { ((JSONCall)done).notifyError(new Exception(msg.toString())); } - + private static final class JSObjToStr { final String str; final Object obj; diff -r a7bec1c91507 ko4j/src/main/java/org/netbeans/html/ko4j/LoadJSON.java --- a/ko4j/src/main/java/org/netbeans/html/ko4j/LoadJSON.java Wed May 27 22:40:01 2015 +0200 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/LoadJSON.java Wed May 27 23:28:35 2015 +0200 @@ -42,80 +42,16 @@ */ package org.netbeans.html.ko4j; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import net.java.html.js.JavaScriptBody; import org.netbeans.html.json.spi.JSONCall; -import org.netbeans.html.json.spi.Transfer; -import org.netbeans.html.json.spi.WSTransfer; /** * * @author Jaroslav Tulach */ -final class LoadJSON implements Transfer, WSTransfer { +final class LoadJSON { private LoadJSON() {} - - @Override - public void extract(Object obj, String[] props, Object[] values) { - extractJSON(obj, props, values); - } - @Override - public void loadJSON(final JSONCall call) { - if (call.isJSONP()) { - String me = createJSONP(call); - loadJSONP(call.composeURL(me), me); - } else { - String data = null; - if (call.isDoOutput()) { - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - call.writeData(bos); - data = new String(bos.toByteArray(), "UTF-8"); - } catch (IOException ex) { - call.notifyError(ex); - } - } - loadJSON(call.composeURL(null), call, call.getMethod(), data); - } - } - - @Override - public Object toJSON(InputStream is) throws IOException { - StringBuilder sb = new StringBuilder(); - InputStreamReader r = new InputStreamReader(is); - for (;;) { - int ch = r.read(); - if (ch == -1) { - break; - } - sb.append((char)ch); - } - return parse(sb.toString()); - } - - @Override - public LoadWS open(String url, JSONCall callback) { - return new LoadWS(callback, url); - } - - @Override - public void send(LoadWS socket, JSONCall data) { - socket.send(data); - } - - @Override - public void close(LoadWS socket) { - socket.close(); - } - - // - // implementations - // - @JavaScriptBody(args = {"object", "property"}, body = "if (property === null) return object;\n" @@ -157,11 +93,16 @@ return s; } - @JavaScriptBody(args = {"url", "done", "method", "data"}, javacall = true, body = "" + @JavaScriptBody(args = {"url", "done", "method", "data", "hp"}, javacall = true, body = "" + "var request = new XMLHttpRequest();\n" + "if (!method) method = 'GET';\n" + "request.open(method, url, true);\n" + "request.setRequestHeader('Content-Type', 'application/json; charset=utf-8');\n" + + "for (var i = 0; i < hp.length; i += 2) {\n" + + " var h = hp[i];\n" + + " var v = hp[i + 1];\n" + + " request.setRequestHeader(h, v);\n" + + "}\n" + "request.onreadystatechange = function() {\n" + " if (request.readyState !== 4) return;\n" + " var r = request.response || request.responseText;\n" @@ -182,10 +123,10 @@ + "else request.send();\n" ) static void loadJSON( - String url, JSONCall done, String method, String data + String url, JSONCall done, String method, String data, Object[] headerPairs ) { } - + @JavaScriptBody(args = {"url", "jsonp"}, body = "var scrpt = window.document.createElement('script');\n " + "scrpt.setAttribute('src', url);\n " @@ -203,5 +144,5 @@ values[i] = getProperty(jsonObject, props[i]); } } - + } diff -r a7bec1c91507 ko4j/src/test/java/org/netbeans/html/ko4j/DynamicHTTP.java --- a/ko4j/src/test/java/org/netbeans/html/ko4j/DynamicHTTP.java Wed May 27 22:40:01 2015 +0200 +++ b/ko4j/src/test/java/org/netbeans/html/ko4j/DynamicHTTP.java Wed May 27 23:28:35 2015 +0200 @@ -76,28 +76,28 @@ private static List resources; private static ServerConfiguration conf; private static HttpServer server; - + private DynamicHTTP() { } - + static URI initServer() throws Exception { server = HttpServer.createSimpleServer(null, new PortRange(8080, 65535)); final WebSocketAddOn addon = new WebSocketAddOn(); for (NetworkListener listener : server.getListeners()) { listener.registerAddOn(addon); - } + } resources = new ArrayList(); conf = server.getServerConfiguration(); final DynamicHTTP dh = new DynamicHTTP(); conf.addHttpHandler(dh, "/"); - + server.start(); return pageURL("http", server, "/test.html"); } - + @Override public void service(Request request, Response response) throws Exception { if ("/test.html".equals(request.getRequestURI())) { @@ -159,6 +159,8 @@ sb.append((char) ch); } params[i] = sb.toString(); + } else if (r.parameters[i].startsWith("http.header.")) { + params[i] = request.getHeader(r.parameters[i].substring(12)); } } if (params[i] == null) { @@ -171,7 +173,7 @@ } } } - + private URI registerWebSocket(Resource r) { WebSocketEngine.getEngine().register("", r.httpPath, new WS(r)); return pageURL("ws", server, r.httpPath); @@ -184,7 +186,7 @@ } return pageURL("http", server, r.httpPath); } - + private static URI pageURL(String proto, HttpServer server, final String page) { NetworkListener listener = server.getListeners().iterator().next(); int port = listener.getPort(); @@ -194,7 +196,7 @@ throw new IllegalStateException(ex); } } - + static final class Resource { final InputStream httpContent; @@ -235,7 +237,7 @@ } } } - + private static class WS extends WebSocketApplication { private final Resource r; diff -r a7bec1c91507 src/main/javadoc/overview.html --- a/src/main/javadoc/overview.html Wed May 27 22:40:01 2015 +0200 +++ b/src/main/javadoc/overview.html Wed May 27 23:28:35 2015 +0200 @@ -51,62 +51,65 @@

- Use Java to write application logic; Use HTML5 to render the UI; + Use Java to write application logic; Use HTML5 to render the UI; {@link net.java.html.json.Model Animate an HTML page from Java} (see Duke being rotated by CSS); Use {@link net.java.html.json.OnReceive REST} or WebSockets; interact with JavaScript; Get the best of both worlds! - - The goal of these APIs is to use full featured Java runtime - (like real HotSpot VM), - but still rely on a very lightweight rendering technology - (so it can potentially fit + + The goal of these APIs is to use full featured Java runtime + (like real HotSpot VM), + but still rely on a very lightweight rendering technology + (so it can potentially fit Bck2Brwsr and definitely - to various types of phones). What can be more lightweight - (from a browser perspective) than + to various types of phones). What can be more lightweight + (from a browser perspective) than HTML!? - By default we use {@link net.java.html.boot.fx JavaFX's WebView} - component to display the HTML. - We eliminate the need to manipulate the DOM directly, - there is a special {@link net.java.html.json Java to Knockout.js binding}. - As a result the HTML uses Knockout.js syntax, + By default we use {@link net.java.html.boot.fx JavaFX's WebView} + component to display the HTML. + We eliminate the need to manipulate the DOM directly, + there is a special {@link net.java.html.json Java to Knockout.js binding}. + As a result the HTML uses Knockout.js syntax, yet the application code can be written in Java.

- +

What's Been Improved in Version 1.2?

- + + One can control {@link net.java.html.json.OnReceive#headers() HTTP request headers} + when connecting to server using the {@link net.java.html.json.OnReceive} + annotation. Bugfix of issue 250503. - +

What's New in Version 1.1?

- +

The content of a {@link net.java.html.BrwsrCtx context} can be selected by registering implementations under specific - {@link org.netbeans.html.context.spi.Contexts.Id technology identifiers} - and requesting them during - {@link org.netbeans.html.context.spi.Contexts#newBuilder(java.lang.Object...) construction} + {@link org.netbeans.html.context.spi.Contexts.Id technology identifiers} + and requesting them during + {@link org.netbeans.html.context.spi.Contexts#newBuilder(java.lang.Object...) construction} of the context. org.netbeans.html:ko4j module's implementation offers ko4j, xhr and websocket identifiers - for its registered services + for its registered services (e.g. {@link org.netbeans.html.json.spi.Technology}, {@link org.netbeans.html.json.spi.Transfer} and {@link org.netbeans.html.json.spi.WSTransfer}). org.netbeans.html:ko-ws-tyrus - module registers its - {@link org.netbeans.html.json.spi.Transfer Java based JSON} and + module registers its + {@link org.netbeans.html.json.spi.Transfer Java based JSON} and {@link org.netbeans.html.json.spi.WSTransfer WebSocket} implementations under the name tyrus.

A particular DOM subtree that a knockout.js model gets - applied to can be selected by using + applied to can be selected by using {@link net.java.html.json.Models#applyBindings(java.lang.Object,java.lang.String) Models.applyBindings(m, id)} with an id of an HTML element. - There is new {@link net.java.html.json.Model#targetId()} attribute - which controls behavior of the generated applyBindings method. + There is new {@link net.java.html.json.Model#targetId()} attribute + which controls behavior of the generated applyBindings method. If specified and non-empty, then the generated method will call {@link net.java.html.json.Models#applyBindings(java.lang.Object,java.lang.String)} with this and the provided {@link net.java.html.json.Model#targetId() target id}. @@ -124,15 +127,15 @@ {@link org.netbeans.html.json.spi.PropertyBinding#weak()} and {@link org.netbeans.html.json.spi.FunctionBinding#weak()}) and now the Java {@link net.java.html.json.Model models} can garbage collect, - when no longer used. Library writers that use - {@link net.java.html.js.JavaScriptBody} annotation can also + when no longer used. Library writers that use + {@link net.java.html.js.JavaScriptBody} annotation can also control garbage collection behavior of method arguments by setting {@link net.java.html.js.JavaScriptBody#keepAlive() keepAlive=false} attribute.

- +

What's New in Version 1.0?

- +

{@link net.java.html.json.Property#array() Array properties} are now mutable from the knockout.js @@ -151,14 +154,14 @@ {@link net.java.html.BrwsrCtx#execute browser context} to prevent endless debugging when one forgets to do so.

- +

- What's new in older versions? Click the - link - to view even more + What's new in older versions? Click the + link + to view even more historic changes below:

- +
- +

What's New in Version 0.9?

- +

System can run in {@link net.java.html.boot.BrowserBuilder#classloader(java.lang.ClassLoader) Felix OSGi container} (originally only Equinox). {@link net.java.html.json.ComputedProperty Derived properties} @@ -184,32 +187,32 @@ Knockout.js library has been updated to version 3.2.0.

- +

What's New in 0.8.x Versions?

- +

Setters or array properties on classes generated by {@link net.java.html.json.Model} annotation can be accessed from any thread. {@link org.netbeans.html.sound.spi.AudioEnvironment} can be registered into {@link net.java.html.BrwsrCtx}. There is a {@link net.java.html.json.Models#parse(net.java.html.BrwsrCtx, java.lang.Class, java.io.InputStream, java.util.Collection) method} - to parse a JSON array and convert it into + to parse a JSON array and convert it into {@link net.java.html.json.Model model classes}. - Improved behavior of enum values in + Improved behavior of enum values in {@link net.java.html.json.Model knockout bindings}.

- +

- Few bugfixes for better portability. + Few bugfixes for better portability. New API for {@link net.java.html.boot.script.Scripts headless execution} on top of Nashorn - does not run knockout for Java - fully yet + fully yet (reported as JDK-8046013), - however even in current state it is quite + however even in current state it is quite {@link net.java.html.boot.script.Scripts useful for testing} - of + of {@link net.java.html.js Java/JavaScript interactions}.

- +

{@link net.java.html.boot.fx.FXBrowsers} has been extended with new helper methods to make it easier to use HTML+Java @@ -226,22 +229,22 @@ extended to work on systems that don't support {@link java.lang.Class#getProtectionDomain}.

- +

The first argument of method annotated by {@link net.java.html.json.OnReceive} annotation has to be the associated {@link net.java.html.json.Model model class}.

- +

{@link net.java.html.json.OnReceive} annotation now accepts {@link java.util.List} of data values as second argument (previously required an array).

- - + +

What's New in 0.7.x Versions?

- +

{@link net.java.html.js.JavaScriptBody} annotation has new attribute {@link net.java.html.js.JavaScriptBody#wait4js()} which allows @@ -250,17 +253,17 @@ new attribute as much as possible, as it can speed up execution in certain environments.

- +

Use {@link net.java.html.BrwsrCtx#execute(java.lang.Runnable)} in multi-threaded environment to execute your code on the browser thread. - See example + See example {@link net.java.html.BrwsrCtx#execute(java.lang.Runnable) using Java timer}.

- +

Interesting Entry Points

- +

Learn how to {@link net.java.html.json.Model animate an HTML page from Java} without referencing single HTML element from the Java code.

@@ -276,51 +279,51 @@

Getting Started

- There are many ways - to start developing - Html for Java application. + There are many ways + to start developing + Html for Java application. However to be sure one chooses the most recent setup, it is recommended - to switch to good old command line and use a + to switch to good old command line and use a Maven archetype - associated with every version of this project. Make sure at least + associated with every version of this project. Make sure at least JDK7 is your installed Java and type: -
+        
 $ mvn archetype:generate \
  -DarchetypeGroupId=org.apidesign.html \
  -DarchetypeArtifactId=knockout4j-archetype \
  -DarchetypeVersion=0.8 # or newer version, if available
         
- Answer few questions (for example choose myfirstbrwsrpage as artifactId) + Answer few questions (for example choose myfirstbrwsrpage as artifactId) and then you can:
 $ cd myfirstbrwsrpage
 $ mvn process-classes exec:java
         
- In a few seconds (or minutes if + In a few seconds (or minutes if Maven - decides to download the whole Internet of dependencies) you should - see a sample Hello World application rendered in a + decides to download the whole Internet of dependencies) you should + see a sample Hello World application rendered in a JavaFX web view component (that of course requires your JDK to come - with JavaFX; - JDK7 - and JDK8 from Oracle contain everything that is needed). - The generated application is built around one + with JavaFX; + JDK7 + and JDK8 from Oracle contain everything that is needed). + The generated application is built around one Java source (uses the {@link net.java.html.json.Model} annotation to auto-generate another Data.java class during compilation) and one HTML file (uses the Knockout - syntax to data-bind the HTML elements to the + syntax to data-bind the HTML elements to the generated Data model):
 $ ls src/main/java/**/DataModel.java
 $ ls src/main/webapp/pages/index.html
         
- That is all you need to get started. Play with the sources, + That is all you need to get started. Play with the sources, modify them and enjoy Html for Java! - +

IDE Support

- +

This API is part of NetBeans.org project and as such @@ -330,77 +333,77 @@ it builds on standard Java6 APIs and as such it shall work fine in any IDE.

- +

- A lot of work is done by + A lot of work is done by annotation processors that generate various boiler plate code during compilation. This is a standard part of Java since JDK6, but for example Eclipse is known not to deal with processors well and developers using it need to be careful. IntelliJ users hasn't reported any issues - and of course, NetBeans IDE support for + and of course, NetBeans IDE support for processors is outstanding.

- +

When using {@link net.java.html.js.JavaScriptBody} annotation, it is useful to do a bit of post processing of classes. There is a - Maven + Maven plugin for that. - NetBeans IDE will invoke it when doing a build. Other IDEs may - need some hint to do so. + NetBeans IDE will invoke it when doing a build. Other IDEs may + need some hint to do so. Anyway: If one does not see all (generated) sources or is getting - {@link java.lang.LinkageError}s when executing the application, + {@link java.lang.LinkageError}s when executing the application, switch to command line and do clean build from there:

$ mvn clean install

If that succeeds, your IDE of choice will hopefully - pick the generated sources up and present the result of the build - properly. If not, - download NetBeans, - you will be pleasantly - surprised - for example with our excellent - Java/JavaScript + pick the generated sources up and present the result of the build + properly. If not, + download NetBeans, + you will be pleasantly + surprised - for example with our excellent + Java/JavaScript debugging support.

- +

Deploy Your Application

- +

It is not goal of this documentation to list all possible ways - to package and deploy applications which use this API. However it is + to package and deploy applications which use this API. However it is important for new comers to see the benefits of using the HTML for Java API and as such - let's list at least few bundling options, known to work at the time of writing + let's list at least few bundling options, known to work at the time of writing this documentation.

- +

First of all, this is a client technology. You write client applications with it which may, but need not connect to a server. You don't need - Tomcat or WebLogic to deploy + Tomcat or WebLogic to deploy HTML for Java applications.

- +

The sample project generated by org.apidesign.html knockout4j-archetype is configured to use JavaFX - as the rendering technology. This setup is primarily suitable for + as the rendering technology. This setup is primarily suitable for development - it needs no special packaging, starts quickly and - allows you to use classical HotSpot VM debuggers. A final + allows you to use classical HotSpot VM debuggers. A final artifact from the build is also a ZIP file which you can use and distribute to your users. Good for desktop applications.

- +

@@ -409,17 +412,17 @@ All the HTML for Java libraries are packaged as OSGi bundles and as such they can easily be run in NetBeans as well as - in Eclipse. As a result one can use + in Eclipse. As a result one can use OSGi - and have a common module system for both platforms. In addition to that + and have a common module system for both platforms. In addition to that one can render using HTML and have a common UI in both platforms. In such case your application would be packaged as a set of OSGi bundles. - Read + Read more...

- +

@@ -429,7 +432,7 @@ height="64" align="left"/> - + There is more and more attempts to execute Java bytecode in a browser, without any special Java plugin installed. The HTML for Java is @@ -437,16 +440,16 @@ applications even on such restricted environments. It uses no reflection calls and that allows to statically pre-compile the applications into JavaScript. One of such environments - is called Bck2Brwsr, + is called Bck2Brwsr, another TeaVM. Both support the - {@link net.java.html.js.JavaScriptBody} annotation. Read + {@link net.java.html.js.JavaScriptBody} annotation. Read more or play - a minesweeper game packaged for your browser + a minesweeper game packaged for your browser (of course written in Java):

- +