1.1 --- a/serverside/src/main/java/org/apidesign/bck2brwsr/demo/serverside/ChatClient.java Sun May 05 17:20:46 2013 +0200
1.2 +++ b/serverside/src/main/java/org/apidesign/bck2brwsr/demo/serverside/ChatClient.java Sun May 05 18:04:13 2013 +0200
1.3 @@ -36,36 +36,47 @@
1.4 */
1.5 @Model(className = "ChatModel", properties = {
1.6 @Property(name = "user", type = String.class),
1.7 - @Property(name = "text", type = String.class),
1.8 + @Property(name = "comment", type = String.class),
1.9 @Property(name = "msgs", type = Message.class, array = true)
1.10 })
1.11 class ChatClient {
1.12 @ComputedProperty
1.13 - static boolean sendEnabled(String user, String text) {
1.14 - boolean res = user != null && text != null && !user.isEmpty() && !text.isEmpty();
1.15 - try {
1.16 - if (true) throw new IllegalStateException("Query for msg '" + text + "' by user " + user + " res: " + res);
1.17 - } catch (Exception ex) {
1.18 -
1.19 - }
1.20 + static boolean sendEnabled(String user, String comment) {
1.21 + boolean res = user != null && comment != null && !user.isEmpty() && !comment.isEmpty();
1.22 return res;
1.23 }
1.24
1.25 @Function
1.26 static void submit(ChatModel m) {
1.27 - if (!sendEnabled(m.getUser(), m.getText())) {
1.28 + if (!sendEnabled(m.getUser(), m.getComment())) {
1.29 return;
1.30 }
1.31 - Message msg = new Message(CNTX);
1.32 - msg.setComment(m.getText());
1.33 - msg.setUser(m.getUser());
1.34 - m.getMsgs().add(msg);
1.35 + m.postComment(m.getUser(), m.getComment());
1.36 + }
1.37 +
1.38 + @OnReceive(url = "/chat/addComment?user={user}&comment={comment}")
1.39 + static void postComment(ChatModel m, Message addedMessage) {
1.40 + if (addedMessage.getComment().equals(m.getComment())) {
1.41 + m.setComment("");
1.42 + }
1.43 }
1.44
1.45 @OnReceive(url = "/chat?since=0")
1.46 static void initialRead(ChatModel m, Query q) {
1.47 m.getMsgs().clear();
1.48 m.getMsgs().addAll(q.getMessages());
1.49 + moreMessages(m);
1.50 + }
1.51 +
1.52 + @OnReceive(url = "/chat?since={since}")
1.53 + static void updateMsgs(ChatModel m, Query q) {
1.54 + m.getMsgs().addAll(q.getMessages());
1.55 + moreMessages(m);
1.56 + }
1.57 +
1.58 + private static void moreMessages(ChatModel m) {
1.59 + long now = System.currentTimeMillis();
1.60 + m.updateMsgs("" + now);
1.61 }
1.62
1.63 static final Context CNTX = Context.findDefault(ChatClient.class);
1.64 @@ -76,6 +87,11 @@
1.65 m.setUser("system");
1.66 chm.getMsgs().add(m);
1.67 chm.applyBindings();
1.68 - chm.initialRead();
1.69 + try {
1.70 + // XXX: this should not be in static initializer -
1.71 + // XXX: prevents unit testing!
1.72 + chm.initialRead();
1.73 + } catch (Throwable ex) {
1.74 + }
1.75 }
1.76 }
2.1 --- a/serverside/src/main/java/org/apidesign/bck2brwsr/demo/serverside/ChatServerResource.java Sun May 05 17:20:46 2013 +0200
2.2 +++ b/serverside/src/main/java/org/apidesign/bck2brwsr/demo/serverside/ChatServerResource.java Sun May 05 18:04:13 2013 +0200
2.3 @@ -26,13 +26,20 @@
2.4 import java.io.Closeable;
2.5 import java.lang.reflect.Field;
2.6 import java.util.ArrayList;
2.7 +import java.util.IdentityHashMap;
2.8 import java.util.List;
2.9 +import java.util.Map;
2.10 import java.util.concurrent.Callable;
2.11 +import java.util.logging.Level;
2.12 +import java.util.logging.Logger;
2.13 +import javax.inject.Singleton;
2.14 import javax.ws.rs.Consumes;
2.15 +import javax.ws.rs.DefaultValue;
2.16 import javax.ws.rs.GET;
2.17 import javax.ws.rs.PUT;
2.18 import javax.ws.rs.Path;
2.19 import javax.ws.rs.Produces;
2.20 +import javax.ws.rs.QueryParam;
2.21 import javax.ws.rs.container.AsyncResponse;
2.22 import javax.ws.rs.container.Suspended;
2.23 import javax.ws.rs.core.MediaType;
2.24 @@ -44,10 +51,11 @@
2.25 import org.glassfish.jersey.server.ContainerFactory;
2.26 import org.glassfish.jersey.server.ResourceConfig;
2.27
2.28 -/** Server side of the chat application.
2.29 - */
2.30 -@Path("/")
2.31 +/** Server side of the chat application.*/
2.32 +@Path("/") @Singleton
2.33 public final class ChatServerResource {
2.34 + private static final Logger LOG = Logger.getLogger(ChatServerResource.class.getName());
2.35 +
2.36 public static void main(String... args) throws Exception {
2.37 ResourceConfig rc = new ResourceConfig(ChatServerResource.class);
2.38 GrizzlyHttpContainer c = ContainerFactory.createContainer(GrizzlyHttpContainer.class, rc);
2.39 @@ -73,17 +81,53 @@
2.40 msgs.add(welcome);
2.41 }
2.42
2.43 + private final Map<AsyncResponse, Long> awaiting = new IdentityHashMap<>();
2.44 +
2.45 @Produces(MediaType.APPLICATION_JSON)
2.46 - @GET public void getResources(@Suspended AsyncResponse ar) {
2.47 - Query q = new Query(Context.findDefault(Query.class));
2.48 - q.getMessages().addAll(msgs);
2.49 - ar.resume(q);
2.50 + @GET public synchronized void getResources(
2.51 + @QueryParam("since") @DefaultValue("0") long since,
2.52 + @Suspended AsyncResponse ar
2.53 + ) {
2.54 + Query q = new Query(Context.findDefault(ChatServerResource.class));
2.55 + for (Message m : msgs) {
2.56 + if (m.getWhen() >= since) {
2.57 + q.getMessages().add(m);
2.58 + }
2.59 + }
2.60 + if (!q.getMessages().isEmpty()) {
2.61 + ar.resume(q);
2.62 + } else {
2.63 + awaiting.put(ar, since);
2.64 + }
2.65 }
2.66
2.67 - @Consumes(MediaType.APPLICATION_JSON)
2.68 - @PUT public void publish(Message msg) {
2.69 - if (msg != null) {
2.70 - msgs.add(msg);
2.71 + private void handleAwaiting(long newest) {
2.72 + assert Thread.holdsLock(this);
2.73 + AGAIN: for (;;) {
2.74 + for (Map.Entry<AsyncResponse, Long> entry : awaiting.entrySet()) {
2.75 + AsyncResponse ar = entry.getKey();
2.76 + Long since = entry.getValue();
2.77 + if (since <= newest) {
2.78 + awaiting.remove(ar);
2.79 + getResources(since, ar);
2.80 + continue AGAIN;
2.81 + }
2.82 + }
2.83 + return;
2.84 }
2.85 }
2.86 +
2.87 + @Path("addComment") @GET
2.88 + public synchronized Message publish(
2.89 + @QueryParam("user") String user,
2.90 + @QueryParam("comment") String comment
2.91 + ) {
2.92 + Message msg = new Message(Context.findDefault(ChatServerResource.class));
2.93 + msg.setUser(user);
2.94 + msg.setComment(comment);
2.95 + msg.setWhen(System.currentTimeMillis());
2.96 + msgs.add(msg);
2.97 + handleAwaiting(msg.getWhen());
2.98 + return msg;
2.99 + }
2.100 }
3.1 --- a/serverside/src/main/resources/org/apidesign/bck2brwsr/demo/serverside/chat.html Sun May 05 17:20:46 2013 +0200
3.2 +++ b/serverside/src/main/resources/org/apidesign/bck2brwsr/demo/serverside/chat.html Sun May 05 18:04:13 2013 +0200
3.3 @@ -33,7 +33,7 @@
3.4 <div>Username:</div>
3.5 <input data-bind="value: user"/>
3.6 <div>Message:</div>
3.7 - <input data-bind="value: text"/>
3.8 + <input data-bind="value: comment"/>
3.9 <button data-bind="enable: sendEnabled, click: submit">Send</button>
3.10
3.11 <ul data-bind="foreach: msgs">
4.1 --- a/serverside/src/test/java/org/apidesign/bck2brwsr/demo/serverside/ChatClientTest.java Sun May 05 17:20:46 2013 +0200
4.2 +++ b/serverside/src/test/java/org/apidesign/bck2brwsr/demo/serverside/ChatClientTest.java Sun May 05 18:04:13 2013 +0200
4.3 @@ -42,14 +42,14 @@
4.4 @Test public void hasSendEnabled() {
4.5 ChatModel m = new ChatModel(Context.EMPTY);
4.6 assertFalse(m.isSendEnabled(), "By default disabled");
4.7 - m.setText("some msg");
4.8 + m.setComment("some msg");
4.9 m.setUser("by me");
4.10 assertTrue(m.isSendEnabled(), "Now it is enabled");
4.11 m.setUser(null);
4.12 assertFalse(m.isSendEnabled(), "No user means disabled");
4.13 m.setUser("by him");
4.14 assertTrue(m.isSendEnabled(), "Again enabled");
4.15 - m.setText("");
4.16 + m.setComment("");
4.17 assertFalse(m.isSendEnabled(), "Empty text means disabled");
4.18 }
4.19 }