/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Emil Ong
*/
package com.caucho.bayeux;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.*;
import javax.servlet.http.*;
import com.caucho.util.*;
import com.caucho.vfs.*;
import com.caucho.servlet.comet.*;
/**
* Servlet to handle bayeux requests.
*/
public class BayeuxServlet extends GenericCometServlet
{
private static final Logger log =
Logger.getLogger(BayeuxServlet.class.getName());
private static final L10N L = new L10N(BayeuxServlet.class);
private static final ChannelTree _root = new ChannelTree();
private static final ConcurrentHashMap<String,BayeuxClient> _clients =
new ConcurrentHashMap<String,BayeuxClient>();
/**
* Services the initial request.
*
* @param request the servlet request object
* @param response the servlet response object
* @param controller the controller to be passed to application code
*
* @return true for keepalive, false for the end of the request
*/
@Override
public boolean service(ServletRequest req,
ServletResponse resp,
CometController controller)
throws IOException, ServletException
{
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
if (! "POST".equals(request.getMethod())) {
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
return false;
}
try {
String message = request.getParameter("message");
if (log.isLoggable(Level.FINEST)) {
log.finest("message = " + message);
}
JsonObject object = JsonDecoder.decode(message);
if (! (object instanceof JsonArray)) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
// XXX: Allow single JsonMap object
return false;
}
JsonArray array = (JsonArray) object;
for (int i = 0; i < array.size(); i++) {
if (! (array.get(i) instanceof JsonMap))
continue;
JsonMap map = (JsonMap) array.get(i);
JsonObject channelObject = map.get("channel");
if (! (channelObject instanceof JsonString))
continue;
String channel = channelObject.toString();
if ("/meta/handshake".equals(channel)) {
handleHandshake(map, request, response, controller);
// the handshake is handled as normal HTTP request, so disconnect
return false;
}
else if ("/meta/connect".equals(channel)) {
return handleConnect(map, request, response, controller);
}
else if ("/meta/subscribe".equals(channel)) {
return handleSubscribe(map, request, response, controller);
}
}
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Unknown channel");
return false;
}
catch (JsonDecodingException e) {
throw new ServletException(e);
}
}
/**
* Resumes service the initial request.
*
* @param request the servlet request object
* @param response the servlet response object
* @param controller the controller to be passed to application code
*
* @return true for keepalive, false for the end of the request
*/
@Override
public boolean resume(ServletRequest request,
ServletResponse response,
CometController controller)
throws IOException, ServletException
{
return false;
}
private void handleHandshake(JsonMap map,
HttpServletRequest request,
HttpServletResponse response,
CometController controller)
throws IOException, ServletException
{
PrintWriter out = response.getWriter();
JsonObject version = map.get("version");
JsonObject supportedConnectionTypes = map.get("supportedConnectionTypes");
JsonObject minimumVersion = map.get("minimumVersion");
JsonObject ext = map.get("ext");
JsonObject id = map.get("id");
if (version == null) {
sendHandshakeResponse(out, null, id, false, "missing version");
return;
}
if (supportedConnectionTypes == null) {
sendHandshakeResponse(out, null, id, false,
"missing supportedConnectionTypes");
return;
}
UUID uuid = UUID.randomUUID();
sendHandshakeResponse(out, uuid.toString(), id, true, null);
}
private void sendHandshakeResponse(PrintWriter out,
String clientId,
JsonObject id,
boolean success,
String error)
throws IOException, ServletException
{
out.println("[");
out.println("\t{");
out.println("\t\t\"channel\": \"/meta/handshake\",");
out.println("\t\t\"version\": \"1.0beta\",");
out.println("\t\t\"supportedConnectionTypes\": " +
"[\"long-polling\", \"callback-polling\", \"iframe\"],");
if (clientId != null)
out.println("\t\t\"clientId\": \"" + clientId + "\",");
out.println("\t\t\"successful\": \"" + success + "\",");
if (id != null && (id instanceof JsonString))
out.println("\t\t\"id\": \"" + id + "\",");
if (error != null)
out.println("\t\t\"error\": \"" + error + "\",");
out.println("\t}");
out.println("]");
}
private boolean handleConnect(JsonMap map,
HttpServletRequest request,
HttpServletResponse response,
CometController controller)
throws IOException, ServletException
{
PrintWriter out = response.getWriter();
JsonObject clientId = map.get("clientId");
JsonObject connectionType = map.get("connectionType");
JsonObject ext = map.get("ext");
JsonObject id = map.get("id");
BayeuxClient client = _clients.get(clientId);
/* XXX
if (client != null)
clientId = UUID.randomUUID().toString();*/
for (BayeuxConnectionType type : BayeuxConnectionType.values()) {
if (type.toString().equals(connectionType)) {
client = new BayeuxClient(controller, clientId.toString(), type);
_clients.put(clientId.toString(), client);
sendConnectResponse(out, clientId, id, true, null);
return true;
}
}
sendConnectResponse(out, clientId, id, false, "bad connection type");
return false;
}
private void sendConnectResponse(PrintWriter out,
JsonObject clientId,
JsonObject id,
boolean success,
String error)
throws IOException, ServletException
{
out.println("[");
out.println("\t{");
out.println("\t\t\"channel\": \"/meta/connect\",");
if (clientId != null)
out.println("\t\t\"clientId\": \"" + clientId + "\",");
out.println("\t\t\"successful\": \"" + success + "\",");
if (id != null)
out.println("\t\t\"id\": \"" + id + "\",");
if (error != null)
out.println("\t\t\"error\": \"" + error + "\",");
out.println("\t}");
out.println("]");
}
private boolean handleSubscribe(JsonMap map,
HttpServletRequest request,
HttpServletResponse response,
CometController controller)
throws IOException, ServletException
{
PrintWriter out = response.getWriter();
JsonObject clientId = map.get("clientId");
JsonObject subscription = map.get("subscription");
JsonObject id = map.get("id");
JsonObject ext = map.get("ext");
BayeuxClient client = _clients.get(clientId);
if (client == null) {
sendSubcribeResponse(out, clientId, subscription, id, false,
"Unknown client");
return false;
}
_root.subscribe(subscription.toString(), client);
sendSubcribeResponse(out, clientId, subscription, id, true, null);
return false;
}
private void sendSubcribeResponse(PrintWriter out,
JsonObject clientId,
JsonObject subscription,
JsonObject id,
boolean success,
String error)
throws IOException, ServletException
{
out.println("[");
out.println("\t{");
out.println("\t\t\"channel\": \"/meta/subscribe\",");
if (clientId != null)
out.println("\t\t\"clientId\": \"" + clientId + "\",");
if (subscription != null)
out.println("\t\t\"subscription\": \"" + subscription + "\",");
out.println("\t\t\"successful\": \"" + success + "\",");
if (id != null)
out.println("\t\t\"id\": \"" + id + "\",");
if (error != null)
out.println("\t\t\"error\": \"" + error + "\",");
out.println("\t}");
out.println("]");
}
}