Search code examples
javajavascriptspringcometdreverse-ajax

Cometd publishes input data Map instead of waiting for Output from server


I am working on a Spring-MVC application which uses Cometd for chatting. The only problem I am facing is when I send a message from client-side, the client is appending that message directly to server-says, instead of waiting for me to call in the backend the publish mechanism and then appending that stuff. I tried it on 2 different computers connected, same result. Because of this, I am not able to access variables which I am setting in the backend.

Kindly let me know what I can do. Thanks a lot.

Debug Log :

DEBUG: org.cometd.server.BayeuxServerImpl.31913ea0 - <  {data={name=check123}, channel=/chat/1360}
wassup
DEBUG: org.cometd.server.BayeuxServerImpl.411c4c13 - Added channel /chat/1360
DEBUG: org.cometd.server.BayeuxServerImpl.411c4c13 - <  {data={accountid=1360, firstname=AKS, name=check123, channelname=/chat/1360, timestamp=2015-04-27 10:58:08.539}, channel=/chat/1360}
DEBUG: org.cometd.server.BayeuxServerImpl.31913ea0 - << {channel=/chat/1360, id=10, successful=true}
DEBUG: org.cometd.server.BayeuxServerImpl.31913ea0 - <  {channel=/chat/1360, id=10, successful=true}

In the above debug log, the first line comes from the client, and the statement 'wassup' is printed in the start of my @Listener method. Next, I add some data and publish it, but I cannot access any variables mentioned from the third line. Any ideas.

Here is Java code :

@Named
@Singleton
@Service("chat")
public class ChatServiceImpl{

    @Inject
    private BayeuxServer bayeux;

    @Inject
    private PersonService personService;

    @Inject
    private ChatMessagesService chatService;

    @Inject
    private ConversationService conversationService;

    @Inject
    private RepliesService repliesService;

    @Session
    private ServerSession serverSession;

    @PostConstruct
    public void init(){
        System.out.println("Echo Service Initialized");
    }

    @PreDestroy
    public void cleanUp() throws Exception {
        System.out.println("Spring Container is destroyed");
    }


    @Listener(value = "/person/*")
    public void privateChat(ServerSession remote, ServerMessage.Mutable message){

        System.out.println("wassup");
        Person sender = this.personService.getCurrentlyAuthenticatedUser();
        String senderName = sender.getFirstName();

        Map<String,Object> input = message.getDataAsMap();
        String data = (String) input.get("name");
        String temp = message.getChannel();
        String temp1 = temp;
        temp = temp.replace("/person/","");
        final int conversationId = Integer.valueOf(temp);
        Map<String,Object> output = new HashMap<>();
        output.put("text",data);
        output.put("sender",senderName);
        output.put("channelname",temp);
        output.put("timestamp",new Timestamp(System.currentTimeMillis()));
        bayeux.createChannelIfAbsent(message.getChannel(), channel -> channel.setPersistent(true));
        ServerChannel serverChannel = bayeux.getChannel(message.getChannel());
        serverChannel.publish(remote,output);
        Thread thread = new Thread(() ->{
            Replies replies = new Replies();
            replies.setReplyingPersonName(senderName);
            replies.setReplyText(data);
            this.repliesService.addReply(replies,conversationId, sender);
        });
        thread.start();
    }


    @Listener("/chat/*")
    public void processChat(ServerSession remote, ServerMessage.Mutable message){
        System.out.println("wassup");
        String firstName = this.personService.returnLoggedInUsersName();
        Timestamp timestamp = new Timestamp(System.currentTimeMillis());

        Map<String, Object> input = message.getDataAsMap();
        String data = (String)input.get("name");
        String temp = message.getChannel();
        String temp1 = temp;
        temp = temp.replace("/chat/","");
        final Long groupAccountIdentifier = Long.valueOf(temp);

        Map<String, Object> output = new HashMap<>();
        output.put("name",data);
        output.put("channelname",message.getChannel());
        output.put("firstname",firstName);
        output.put("timestamp",timestamp);
        output.put("accountid",groupAccountIdentifier);

        bayeux.createChannelIfAbsent(message.getChannel(), channel -> channel.setPersistent(true));
        ServerChannel serverChannel = bayeux.getChannel(message.getChannel());
        serverChannel.publish(serverSession,output);

        Thread thread = new Thread(() ->{
            ChatMessages chatMessages = new ChatMessages();
            chatMessages.setFirstName(firstName);
            chatMessages.setChatText(data);
            chatMessages.setChannelName(message.getChannel());
            chatMessages.setTimeStamp(new Timestamp((System.currentTimeMillis())));
            this.chatService.addChatMessage(chatMessages,groupAccountIdentifier);
        });
        thread.start();
    }
}

Javascript code :

(function($)
{
    var cometd = $.cometd;

    $(document).ready(function()
    {
        function _connectionEstablished()
        {
            $('#body').append('<div>CometD Connection Established</div>');
        }

        function _connectionBroken()
        {
            $('#body').append('<div>CometD Connection Broken</div>');
        }

        function _connectionClosed()
        {
            $('#body').append('<div>CometD Connection Closed</div>');
        }

        // Function that manages the connection status with the Bayeux server
        var _connected = false;
        function _metaConnect(message)
        {
            if (cometd.isDisconnected())
            {
                _connected = false;
                _connectionClosed();
                return;
            }

            var wasConnected = _connected;
            _connected = message.successful === true;
            if (!wasConnected && _connected)
            {
                _connectionEstablished();
            }
            else if (wasConnected && !_connected)
            {
                _connectionBroken();
            }
        }

        // Function invoked when first contacting the server and
        // when the server has lost the state of this client
        function _metaHandshake(handshake)
        {
            if (handshake.successful === true)
            {
                cometd.batch(function()
                {
                    cometd.subscribe('/chat/1360', function(message)
                    {
                        $('#hello1').append('<div>Server Says: ' + message.data.name + ' ' + ' ' +message.data.firstname+'</div>');
                    });

                });
                cometd.publish('/chat/1360');
            }
        }

        // Disconnect when the page unloads
        $(window).unload(function()
        {
            cometd.disconnect(true);
        });

        var cometURL = location.protocol + "//" + location.host + config.contextPath + "/cometd";

        cometd.configure({
            url: cometURL,
            logLevel: 'debug'
        });

        cometd.addListener('/meta/handshake', _metaHandshake);
        cometd.addListener('/meta/connect', _metaConnect);
        cometd.handshake();
    });
})(jQuery);

If anyone has any ideas, kindly let me know what I can do. If there is anymore information necessary, please feel free to ask. Thanks a lot. :-)

UPDATE

As per Sborder suggested second time, I made some changes, but I have met with partial success. Also I forgot to add my index.jsp, which is sending the actual text message.

Firstly, ChatServiceImpl :

     @Session
        private ServerSession serverSession;

  @Listener("/service/chat/{accountid}")
    public void processChat(ServerSession remote, ServerMessage.Mutable message,@Param("accountid")String accountid) {
        System.out.println("wassup and account id is "+accountid);
        String firstName = this.personService.returnLoggedInUsersName();
        Timestamp timestamp = new Timestamp(System.currentTimeMillis());

        Map<String, Object> input = message.getDataAsMap();
        String text = (String) input.get("name");
        String temp = message.getChannel();
 Map<String, Object> data = new HashMap<String,Object>();
        data.put("name", text);
        data.put("channelname", message.getChannel());
        data.put("firstname", firstName);
        data.put("timestamp", timestamp);
     ServerChannel serverChannel = bayeux.createChannelIfAbsent("/chat/1306").getReference();
        serverChannel.setPersistent(true);
        System.out.println("Channel name is "+serverChannel.getChannelId());

       serverChannel.publish(remote, data);
    }

Application.js :

   function _metaHandshake(handshake)
    {
        if (handshake.successful === true)
        {
            cometd.batch(function()
            {
                cometd.subscribe('/chat/1306', function(message){
                    $('.hello1').append('<div>Server Says: ' +message.data.name + ' ' + ' ' +message.data.firstname+'</div>');

                });

            });

        }
    }

index.jsp :

 <div id="body">
    <input id="enterText" type="text" />Enter text
    <input id="sendMessage" type="button"/>
</div>

<div class="hello1">

</div>
<div class="hello123">

</div>

<script type="text/javascript">
    var config = {
        contextPath: '${pageContext.request.contextPath}'
    };
    var cometd = $.cometd;
    $(document).on("click", "#sendMessage", function(){
        var text = $("#enterText").val();
        cometd.publish('/service/chat/1306', { name: text});
    });

So, if I use serverChannel.publish on a /service channel, then In the front-end, no text is appended to server. If I use remote.deliver instead of publish, the correct text is appended, but only to the client who is on the current browser, not to other client who is in another browser. How can I use serverChannel.publish to send data to all subscribers, the correct data I mean.


Solution

  • Your JavaScript client code publishes in the wrong way, just calling:

    cometd.publish('/chat/1360');
    

    It is missing the data that you want to send, and you should at least use an empty object, as in:

    cometd.publish('/chat/1360', {});
    

    Note that since your JavaScript client (the sender) also subscribes to channel /chat/1360, any message that the sender publishes on that channel will return back to the sender itself. This is the default behaviour of CometD.

    On top of that, on the server side you publish on that channel as well via ServerChannel.publish(...), so you are sending to the subscribers of that channel another message.

    There is no need for you to call:

    bayeux.createChannelIfAbsent(message.getChannel(), channel -> channel.setPersistent(true));
    

    because at that point the channel already exist; you can just call bayeux.getChannel(message.getChannel()).

    What you are doing is to send a message to the server (messageA), then you want to process the messageA fields on the server and produce a new, modified, message (messageB) to be broadcast to all subscribers, including the original sender.

    In this case it is much better that you send messageA on a service channel so it does not get broadcast to all subscribers. MessageA is not intended to be broadcast, it's just a mean to communicate to the server what you want to do, and service channels exist for exactly this purpose.

    Get your messages right first, and you will see that all the rest will work out nicely.

    You can use remote calls instead of service channels, they are simpler to use in this particular case where you want to perform some server-side processing of the messages you send.

    Lastly, look at channel parameters, instead of doing the parsing yourself, by using the listener notation:

    @Listener("/chat/{accountId}")
    public void processChat(ServerSession remote, ServerMessage.Mutable message, @Param("accountId") String account)
    {
        ...
    }