1   //========================================================================
2   //$Id: ChatFilter.java,v 1.4 2005/11/14 11:00:33 gregwilkins Exp $
3   //Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
4   //------------------------------------------------------------------------
5   //Licensed under the Apache License, Version 2.0 (the "License");
6   //you may not use this file except in compliance with the License.
7   //You may obtain a copy of the License at 
8   //http://www.apache.org/licenses/LICENSE-2.0
9   //Unless required by applicable law or agreed to in writing, software
10  //distributed under the License is distributed on an "AS IS" BASIS,
11  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  //See the License for the specific language governing permissions and
13  //limitations under the License.
14  //========================================================================
15  
16  package com.acme;
17  
18  import java.io.IOException;
19  import java.util.ArrayList;
20  import java.util.HashMap;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.Map;
24  
25  import javax.servlet.FilterChain;
26  import javax.servlet.FilterConfig;
27  import javax.servlet.ServletException;
28  import javax.servlet.ServletRequest;
29  import javax.servlet.ServletResponse;
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpSession;
32  
33  import org.mortbay.util.ajax.AjaxFilter;
34  import org.mortbay.util.ajax.Continuation;
35  import org.mortbay.util.ajax.ContinuationSupport;
36  
37  public class ChatFilter extends AjaxFilter
38  {       
39      private Map chatrooms;
40      
41  
42      /* ------------------------------------------------------------ */
43      /* 
44       * @see org.mortbay.ajax.AjaxFilter#init(javax.servlet.FilterConfig)
45       */
46      public void init(FilterConfig filterConfig) throws ServletException
47      {
48          super.init(filterConfig);
49          chatrooms=new HashMap();
50      }
51      
52      /* ------------------------------------------------------------ */
53      /* 
54       * @see org.mortbay.ajax.AjaxFilter#destroy()
55       */
56      public void destroy()
57      {
58          super.destroy();
59          chatrooms.clear();
60          chatrooms=null;
61      }
62  
63  
64      /* ------------------------------------------------------------ */
65      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
66      {
67          HttpSession session = ((HttpServletRequest)request).getSession(true);
68          super.doFilter(request,response,chain);
69      }
70      
71      /* ------------------------------------------------------------ */
72      /* 
73       * @see org.mortbay.ajax.AjaxFilter#handle(java.lang.String, javax.servlet.http.HttpServletRequest, org.mortbay.ajax.AjaxFilter.AjaxResponse)
74       */
75      public void handle(String method, String message, HttpServletRequest request, AjaxResponse response)
76      {
77          request.getSession(true);
78          
79          String roomName=request.getParameter("room");
80          if (roomName==null)
81              roomName="0";
82          Map room = null;
83          synchronized(this)
84          {
85              room=(Map)chatrooms.get(roomName);
86              if (room==null)
87              {
88                  room=new HashMap();
89                  chatrooms.put(roomName,room);
90              }
91          }
92              
93          if ("join".equals(method))
94              doJoinChat(room,message,request, response);
95          else if ("chat".equals(method))
96              doChat(room,message,request,response);
97          else if ("poll".equals(method))
98              doPoll(room,request,response);
99          else if ("leave".equals(method))
100             doLeaveChat(room,message,request,response);
101         else
102             super.handle(method, message,request, response);   
103     }
104 
105     /* ------------------------------------------------------------ */
106     private void doJoinChat(Map room, String name, HttpServletRequest request, AjaxResponse response)
107     {
108         HttpSession session = request.getSession(true);
109         String id = session.getId();
110         if (name==null || name.length()==0)
111             name="Newbie";
112         Member member=null;
113         
114         synchronized (room)
115         {
116             member=(Member)room.get(id);
117             if (member==null)
118             {
119                 member = new Member(session,name);
120                 room.put(session.getId(),member);
121             }
122             else
123                 member.setName(name);
124             
125             // exists already, so just update name
126             sendMessage(room,member,"has joined the chat",true);
127             
128             //response.objectResponse("joined", "<joined from=\""+name+"\"/>");
129             sendMembers(room,response);
130         }
131     }
132     
133 
134     /* ------------------------------------------------------------ */
135     private void doLeaveChat(Map room, String name, HttpServletRequest request, AjaxResponse response)
136     {
137         HttpSession session = request.getSession(true);
138         String id = session.getId();
139 
140         Member member=null;
141         synchronized (room)
142         {
143             member = (Member)room.get(id);
144             if (member==null)
145                 return;
146             if ("Elvis".equals(member.getName()))
147                 sendMessage(room,member,"has left the building",true);
148             else
149                 sendMessage(room,member,"has left the chat",true);
150             room.remove(id);
151             member.setName(null);
152         }
153         //response.objectResponse("left", "<left from=\""+member.getName()+"\"/>");
154         sendMembers(room,response);
155     }
156 
157 
158     /* ------------------------------------------------------------ */
159     private void doChat(Map room, String text, HttpServletRequest request, AjaxResponse response)
160     {
161         HttpSession session = request.getSession(true);
162         String id = session.getId();
163         
164         Member member=null;
165         synchronized (room)
166         {
167             member = (Member)room.get(id);
168             
169             if (member==null)
170                 return;
171             sendMessage(room, member, text, false);
172         }
173     }
174 
175 
176     /* ------------------------------------------------------------ */
177     private void doPoll(Map room, HttpServletRequest request, AjaxResponse response)
178     {
179         HttpSession session = request.getSession(true);
180         String id = session.getId();
181         long timeoutMS = 60000L; 
182         if (request.getParameter("timeout")!=null)
183             timeoutMS=Long.parseLong(request.getParameter("timeout"));
184         if (session.isNew())
185             timeoutMS=1;
186         
187         Member member=null;
188         synchronized (room)
189         {
190             member = (Member)room.get(id);
191             if (member==null)
192             {
193                 member = new Member(session,null);
194                 room.put(session.getId(),member);
195             }
196             
197             if (!request.isInitial())
198                 member.setPoll(null);
199             else if (!member.hasMessages())
200             {   
201                 member.setPoll(request);
202                 request.suspend(timeoutMS);
203                 return;
204             }
205             
206 
207             if (member.sendMessages(response))
208                 sendMembers(room,response);
209         }
210         
211     }
212 
213     /* ------------------------------------------------------------ */
214     private void sendMessage(Map room, Member member, String text, boolean alert)
215     {
216         Message event=new Message(member.getName(),text,alert);
217         
218         ArrayList invalids=null;
219         synchronized (room)
220         {
221             Iterator iter = room.values().iterator();
222             while (iter.hasNext())
223             {
224                 Member m = (Member)iter.next();
225                 
226                 try 
227                 {
228                     m.getSession().getAttribute("anything");
229                     m.addMessage(event);
230                 }
231                 catch(IllegalStateException e)
232                 {
233                     if (invalids==null)
234                         invalids=new ArrayList();
235                     invalids.add(m);
236                     iter.remove();
237                 }
238             }
239         }
240             
241         for (int i=0;invalids!=null && i<invalids.size();i++)
242         {
243             Member m = (Member)invalids.get(i);
244             sendMessage(room,m,"has timed out of the chat",true);
245         }
246     }
247     
248     private void sendMembers(Map room, AjaxResponse response)
249     {
250         StringBuffer buf = new StringBuffer();
251         buf.append("<ul>\n");
252         synchronized (room)
253         {
254             Iterator iter = room.values().iterator();
255             while (iter.hasNext())
256             {
257                 Member m = (Member)iter.next();
258                 if (m.getName()==null)
259                     continue;
260                 buf.append("<li>");
261                 buf.append(encodeText(m.getName()));
262                 buf.append("</li>\n");
263             }
264         }
265         buf.append("</ul>\n");
266         response.elementResponse("members", buf.toString());
267     }
268 
269     
270     private static class Message
271     {
272         private String _from;
273         private String _text;
274         private boolean _alert;
275         
276         Message(String from, String text, boolean alert)
277         {
278             _from=from;
279             _text=text;
280             _alert=alert;
281         }
282         
283         boolean isAlert()
284         {
285             return _alert;
286         }
287         
288         public String toString()
289         {
290             return "<chat from=\""+_from+"\" alert=\""+_alert+"\">"+encodeText(_text)+"</chat>";
291         }
292     }
293 
294     private class Member
295     {
296         private HttpSession _session;
297         private String _name;
298         private List _messages = new ArrayList();
299         private ServletRequest _request;;
300         
301         Member(HttpSession session, String name)
302         {
303             _session=session;
304             _name=name;
305         }
306         
307         /* ------------------------------------------------------------ */
308         /**
309          * @return Returns the name.
310          */
311         public String getName()
312         {
313             return _name;
314         }
315 
316         /* ------------------------------------------------------------ */
317         /**
318          * @param name The name to set.
319          */
320         public void setName(String name)
321         {
322             _name = name;
323         }
324 
325         /* ------------------------------------------------------------ */
326         /**
327          * @return Returns the session.
328          */
329         public HttpSession getSession()
330         {
331             return _session;
332         }
333 
334         /* ------------------------------------------------------------ */
335         /**
336          * @param continuation The continuation to set.
337          */
338         public void setPoll(ServletRequest poll)
339         {
340             _request=poll;
341         }
342         
343         /* ------------------------------------------------------------ */
344         public void addMessage(Message event)
345         {
346             if (_name==null)
347                 return;
348             _messages.add(event);
349             if (_request!=null)
350                 _request.resume();
351         }
352 
353         /* ------------------------------------------------------------ */
354         public boolean hasMessages()
355         {
356             return _messages!=null && _messages.size()>0;
357         }
358         
359         /* ------------------------------------------------------------ */
360         public void rename(Map room,String name)
361         {
362             String oldName = getName();
363             setName(name);
364             if (oldName!=null)
365                 ChatFilter.this.sendMessage(room,this,oldName+" has been renamed to "+name,true);
366         }
367 
368         /* ------------------------------------------------------------ */
369         public boolean sendMessages(AjaxResponse response)
370         {
371             synchronized (this)
372             {
373                 boolean alerts=false;
374                 for (int i=0;i<_messages.size();i++)
375                 {
376                     Message event = (Message)_messages.get(i);
377                     response.objectResponse("chat", event.toString());
378                     alerts |= event.isAlert();
379                 }
380                 _messages.clear();
381                 return alerts;
382             }
383         }
384 
385     }
386 }