PageRenderTime 33ms CodeModel.GetById 12ms app.highlight 16ms RepoModel.GetById 2ms app.codeStats 0ms

/tags/1.0/src/main/java/flowersinthesand/example/ChatServlet.java

http://jquery-stream.googlecode.com/
Java | 165 lines | 119 code | 30 blank | 16 comment | 6 complexity | d4ca9a4476b23ae690cfebfbdf59d916 MD5 | raw file
  1package flowersinthesand.example;
  2
  3import java.io.IOException;
  4import java.io.PrintWriter;
  5import java.util.LinkedHashMap;
  6import java.util.Map;
  7import java.util.UUID;
  8import java.util.concurrent.BlockingQueue;
  9import java.util.concurrent.ConcurrentHashMap;
 10import java.util.concurrent.LinkedBlockingQueue;
 11
 12import javax.servlet.AsyncContext;
 13import javax.servlet.AsyncEvent;
 14import javax.servlet.AsyncListener;
 15import javax.servlet.ServletConfig;
 16import javax.servlet.ServletException;
 17import javax.servlet.annotation.WebServlet;
 18import javax.servlet.http.HttpServlet;
 19import javax.servlet.http.HttpServletRequest;
 20import javax.servlet.http.HttpServletResponse;
 21
 22import com.google.gson.Gson;
 23
 24@WebServlet(urlPatterns = "/chat", asyncSupported = true)
 25public class ChatServlet extends HttpServlet {
 26
 27	private static final long serialVersionUID = -2919167206889576860L;
 28	private Map<String, AsyncContext> contexts = new ConcurrentHashMap<String, AsyncContext>();
 29	private BlockingQueue<String> messages = new LinkedBlockingQueue<String>();
 30	private Thread notifier = new Thread(new Runnable() {
 31		public void run() {
 32			boolean done = false;
 33			while (!done) {
 34				String message = null;
 35				try {
 36					message = messages.take();
 37					for (AsyncContext ac : contexts.values()) {
 38						try {
 39							sendMessage(ac.getResponse().getWriter(), message);
 40						} catch (IOException e) {
 41							contexts.remove(ac);
 42						}
 43					}
 44				} catch (InterruptedException e) {
 45					done = true;
 46				}
 47			}
 48		}
 49	});
 50	
 51	private void sendMessage(PrintWriter writer, String message) {
 52		// Sends a message according to the message format(message-size ; message-data ;) 
 53		writer.print(message.length());
 54		writer.print(";");
 55		writer.print(message);
 56		writer.print(";");
 57		writer.flush();
 58	}
 59
 60	@Override
 61	public void init(ServletConfig config) throws ServletException {
 62		super.init(config);
 63		notifier.start();
 64	}
 65
 66	// GET method is used to open stream
 67	@Override
 68	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
 69			IOException {
 70		response.setCharacterEncoding("utf-8");
 71		
 72		// Sets the Content-Type header to 'text/plain' that is the only one satisfying all streaming transport
 73		response.setContentType("text/plain");
 74		
 75		// For XDomainRequest transport, sets the Access-Control-Allow-Origin header
 76		response.setHeader("Access-Control-Allow-Origin", "*");
 77
 78		// Prints the stream connection id for further communication and one kilobyte padding at the top of the response
 79		// each part must end with a semicolon
 80		PrintWriter writer = response.getWriter();
 81		
 82		// UUID is very suitable
 83		final String id = UUID.randomUUID().toString();
 84		writer.print(id);
 85		writer.print(';');
 86		
 87		// The padding is needed by XMLHttpRequest of WebKit, XDomainRequest and Hidden iframe transport
 88		for (int i = 0; i < 1024; i++) {
 89			writer.print(' ');
 90		}
 91		writer.print(';');
 92		writer.flush();
 93		
 94		System.out.println(id + ": open");
 95		
 96		// Starts asynchronous mode
 97		final AsyncContext ac = request.startAsync();
 98		ac.setTimeout(5 * 60 * 1000);
 99		ac.addListener(new AsyncListener() {
100			public void onComplete(AsyncEvent event) throws IOException {
101				contexts.remove(id);
102			}
103
104			public void onTimeout(AsyncEvent event) throws IOException {
105				contexts.remove(id);
106			}
107
108			public void onError(AsyncEvent event) throws IOException {
109				contexts.remove(id);
110			}
111
112			public void onStartAsync(AsyncEvent event) throws IOException {
113
114			}
115		});
116		contexts.put(id, ac);
117	}
118
119	// POST method is used to handle data sent by user through the stream
120	@Override
121	protected void doPost(HttpServletRequest request, HttpServletResponse response)
122			throws ServletException, IOException {
123		request.setCharacterEncoding("utf-8");
124
125		// POST request always has metadata parameters
126		
127		// The stream connection id generated by the server 
128		String id = request.getParameter("metadata.id");
129		
130		// Request type such as send and close
131		String type = request.getParameter("metadata.type");
132		
133		System.out.println(id + ": " + type);
134		
135		// Handles a special case
136		if ("close".equals(type)) {
137			// Closes connection by its id
138			AsyncContext ac = contexts.get(id);
139			if (ac != null) {
140				ac.complete();
141			}
142			
143			return;
144		}
145
146		// Handles data sent from a client
147		Map<String, String> data = new LinkedHashMap<String, String>();
148		data.put("username", request.getParameter("username"));
149		data.put("message", request.getParameter("message"));
150
151		try {
152			messages.put(new Gson().toJson(data));
153		} catch (Exception e) {
154			throw new IOException(e);
155		}
156	}
157
158	@Override
159	public void destroy() {
160		messages.clear();
161		contexts.clear();
162		notifier.interrupt();
163	}
164
165}