1   package com.acme;
2   
3   import java.io.IOException;
4   import java.io.PrintWriter;
5   import java.util.HashMap;
6   import java.util.LinkedList;
7   import java.util.Map;
8   import java.util.Queue;
9   
10  import javax.servlet.ServletException;
11  import javax.servlet.ServletRequest;
12  import javax.servlet.http.HttpServlet;
13  import javax.servlet.http.HttpServletRequest;
14  import javax.servlet.http.HttpServletResponse;
15  import javax.servlet.http.HttpSession;
16  
17  // Simple asynchronous Chat room.
18  // This does not handle duplicate usernames or multiple frames/tabs from the same browser
19  // Some code is duplicated for clarity.
20  public class ChatServlet extends HttpServlet
21  {
22      // inner class to hold message queue for each chat room member
23      class Member
24      {
25          String _name;
26          ServletRequest _pollRequest;
27          Queue<String> _queue = new LinkedList<String>();
28      }
29  
30      Map<String,Map<String,Member>> _rooms = new HashMap<String,Map<String, Member>>();
31      
32      
33      // Handle Ajax calls from browser
34      protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
35      {   
36          // Ajax calls are form encoded
37          String action = request.getParameter("action");
38          String message = request.getParameter("message");
39          String username = request.getParameter("user");
40  
41          if (action.equals("join"))
42              join(request,response,username);
43          else if (action.equals("poll"))
44              poll(request,response,username);
45          else if (action.equals("chat"))
46              chat(request,response,username,message);
47      }
48  
49      private synchronized void join(HttpServletRequest request,HttpServletResponse response,String username)
50      throws IOException
51      {
52          Member member = new Member();
53          member._name=username;
54          Map<String,Member> room=_rooms.get(request.getPathInfo());
55          if (room==null)
56          {
57              room=new HashMap<String,Member>();
58              _rooms.put(request.getPathInfo(),room);
59          }
60          room.put(username,member); 
61          response.setContentType("text/json;charset=utf-8");
62          PrintWriter out=response.getWriter();
63          out.print("{action:'join'}");
64      }
65  
66      private synchronized void poll(HttpServletRequest request,HttpServletResponse response,String username)
67      throws IOException
68      {
69          Map<String,Member> room=_rooms.get(request.getPathInfo());
70          Member member = room.get(username);
71  
72          if (member._queue.size()>0)
73          {
74              // Send one chat message
75              response.setContentType("text/json;charset=utf-8");
76              PrintWriter out=response.getWriter();
77              out.print("{action:'poll',");
78              out.print("from:'"+member._queue.poll()+"',");
79              out.print("chat:'"+member._queue.poll()+"'}");
80          }
81          //else if (request.getAttribute("javax.servlet.timeout")==Boolean.TRUE) 
82          else if (request.isTimeout()) 
83          {
84              // Timeout so send empty response
85              response.setContentType("text/json;charset=utf-8");
86              PrintWriter out=response.getWriter();
87              out.print("{action:'poll'}");
88          }
89          else
90          {        
91              // No chat in queue, so suspend and wait for timeout or chat
92              request.suspend();
93              member._pollRequest=request;
94          }
95      }
96  
97      private synchronized void chat(HttpServletRequest request,HttpServletResponse response,String username,String message)
98      throws IOException
99      {
100         Map<String,Member> room=_rooms.get(request.getPathInfo());
101         // Post chat to all members
102         for (Member m:room.values())
103         {
104             m._queue.add(username); // from
105             m._queue.add(message);  // chat
106 
107             // wakeup member if polling
108             if (m._pollRequest!=null)
109             {
110                 m._pollRequest.resume();
111                 m._pollRequest=null;
112             }
113         }
114 
115         response.setContentType("text/json;charset=utf-8");
116         PrintWriter out=response.getWriter();
117         out.print("{action:'chat'}");  
118     }
119 
120     
121     // Serve the HTML with embedded CSS and Javascript.
122     // This should be static content and should use real JS libraries.
123     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
124     {
125         if (!request.getRequestURI().endsWith("/"))
126         {
127             response.sendRedirect(request.getRequestURI()+"/");
128             return;
129         }
130         
131         response.setContentType("text/html");
132         PrintWriter out=response.getWriter();
133         out.println("<html><head>");
134         out.println("    <title>async chat</title>");
135         out.println("    <script type='text/javascript'>");
136         out.println("      function $() { return document.getElementById(arguments[0]); }");
137         out.println("      function $F() { return document.getElementById(arguments[0]).value; }");
138         out.println("      function getKeyCode(ev) { if (window.event) return window.event.keyCode; return ev.keyCode; } ");
139         out.println("      function xhr(method,uri,body,handler) {");
140         out.println("        var req=(window.XMLHttpRequest)?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');");
141         out.println("        req.onreadystatechange=function() { if (req.readyState==4 && handler) { eval('var o='+req.responseText);handler(o);} }");
142         out.println("        req.open(method,uri,true);");
143         out.println("        req.setRequestHeader('Content-Type','application/x-www-form-urlencoded');");
144         out.println("        req.send(body);");
145         out.println("      };");
146         out.println("      function send(action,user,message,handler){");
147         out.println("        if (message) message=message.replace('%','%25').replace('&','%26').replace('=','%3D');");
148         out.println("        if (user) user=user.replace('%','%25').replace('&','%26').replace('=','%3D');");
149         out.println("        xhr('POST','chat','action='+action+'&user='+user+'&message='+message,handler);");
150         out.println("      };");
151         out.println("      ");
152         out.println("      var room = {");
153         out.println("        join: function(name) {");
154         out.println("          this._username=name;");
155         out.println("          $('join').className='hidden';");
156         out.println("          $('joined').className='';");
157         out.println("          $('phrase').focus();");
158         out.println("          send('join', room._username,null);");
159         out.println("          send('chat', room._username,'has joined!');");
160         out.println("          send('poll', room._username,null, room._poll);");
161         out.println("        },");
162         out.println("        chat: function(text) {");
163         out.println("          if (text != null && text.length>0 )");
164         out.println("              send('chat',room._username,text);");
165         out.println("        },");
166         out.println("        _poll: function(m) {");
167         out.println("          //console.debug(m);");
168         out.println("          if (m.chat){");
169         out.println("            var chat=document.getElementById('chat');");
170         out.println("            var spanFrom = document.createElement('span');");
171         out.println("            spanFrom.className='from';");
172         out.println("            spanFrom.innerHTML=m.from+':&nbsp;';");
173         out.println("            var spanText = document.createElement('span');");
174         out.println("            spanText.className='text';");
175         out.println("            spanText.innerHTML=m.chat;");
176         out.println("            var lineBreak = document.createElement('br');");
177         out.println("            chat.appendChild(spanFrom);");
178         out.println("            chat.appendChild(spanText);");
179         out.println("            chat.appendChild(lineBreak);");
180         out.println("            chat.scrollTop = chat.scrollHeight - chat.clientHeight;   ");
181         out.println("          }");
182         out.println("          if (m.action=='poll')");
183         out.println("            send('poll', room._username,null, room._poll);");
184         out.println("        },");
185         out.println("        _end:''");
186         out.println("      };");
187         out.println("    </script>");
188         out.println("    <style type='text/css'>");
189         out.println("    div { border: 0px solid black; }");
190         out.println("    div#chat { clear: both; width: 40em; height: 20ex; overflow: auto; background-color: #f0f0f0; padding: 4px; border: 1px solid black; }");
191         out.println("    div#input { clear: both; width: 40em; padding: 4px; background-color: #e0e0e0; border: 1px solid black; border-top: 0px }");
192         out.println("    input#phrase { width:30em; background-color: #e0f0f0; }");
193         out.println("    input#username { width:14em; background-color: #e0f0f0; }");
194         out.println("    div.hidden { display: none; }");
195         out.println("    span.from { font-weight: bold; }");
196         out.println("    span.alert { font-style: italic; }");
197         out.println("    </style>");
198         out.println("</head><body>");
199         out.println("<div id='chat'></div>");
200         out.println("<div id='input'>");
201         out.println("  <div id='join' >");
202         out.println("    Username:&nbsp;<input id='username' type='text'/><input id='joinB' class='button' type='submit' name='join' value='Join'/>");
203         out.println("  </div>");
204         out.println("  <div id='joined' class='hidden'>");
205         out.println("    Chat:&nbsp;<input id='phrase' type='text'></input>");
206         out.println("    <input id='sendB' class='button' type='submit' name='join' value='Send'/>");
207         out.println("  </div>");
208         out.println("</div>");
209         out.println("<script type='text/javascript'>");
210         out.println("$('username').setAttribute('autocomplete','OFF');");
211         out.println("$('username').onkeyup = function(ev) { var keyc=getKeyCode(ev); if (keyc==13 || keyc==10) { room.join($F('username')); return false; } return true; } ;        ");
212         out.println("$('joinB').onclick = function(event) { room.join($F('username')); return false; };");
213         out.println("$('phrase').setAttribute('autocomplete','OFF');");
214         out.println("$('phrase').onkeyup = function(ev) {   var keyc=getKeyCode(ev); if (keyc==13 || keyc==10) { room.chat($F('phrase')); $('phrase').value=''; return false; } return true; };");
215         out.println("$('sendB').onclick = function(event) { room.chat($F('phrase')); $('phrase').value=''; return false; };");
216         out.println("</script>");
217         out.println("</body></html>");
218     }
219 }