Chat is functional (based on GET)
authorJaroslav Tulach <jtulach@netbeans.org>
Sun, 05 May 2013 18:04:13 +0200
changeset 1140fce839ac01
parent 10 80715a1dd72e
child 12 84266695c06f
Chat is functional (based on GET)
serverside/src/main/java/org/apidesign/bck2brwsr/demo/serverside/ChatClient.java
serverside/src/main/java/org/apidesign/bck2brwsr/demo/serverside/ChatServerResource.java
serverside/src/main/resources/org/apidesign/bck2brwsr/demo/serverside/chat.html
serverside/src/test/java/org/apidesign/bck2brwsr/demo/serverside/ChatClientTest.java
     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  }