/*******************************************************************************
* Copyright (c) 2006-2008, Cloudsmith Inc.
* The code, documentation and other materials contained herein have been
* licensed under the Eclipse Public License - v 1.0 by the copyright holder
* listed above, as the Initial Contributor under such license. The text of
* such license is available at www.eclipse.org.
******************************************************************************/
package org.eclipse.buckminster.p2.remote.server.json;
import java.io.BufferedReader;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.eclipse.buckminster.p2.remote.IRepositoryDataStream;
import org.eclipse.buckminster.p2.remote.IRepositoryFacade;
import org.eclipse.buckminster.p2.remote.IRepositoryServer;
import org.eclipse.buckminster.p2.remote.marshall.IUMarshaller;
import org.eclipse.buckminster.p2.remote.marshall.URISerializer;
import org.eclipse.buckminster.p2.remote.server.MetadataRepositoryFacade;
import org.eclipse.buckminster.p2.remote.server.RepositoryDataStream;
import org.eclipse.buckminster.p2.remote.server.RepositoryServer;
import org.eclipse.buckminster.runtime.Logger;
import org.jabsorb.JSONRPCBridge;
import org.jabsorb.JSONRPCResult;
import org.jabsorb.serializer.AbstractSerializer;
import org.jabsorb.serializer.MarshallException;
import org.jabsorb.serializer.ObjectMatch;
import org.jabsorb.serializer.SerializerState;
import org.jabsorb.serializer.UnmarshallException;
import org.json.JSONException;
import org.json.JSONObject;
/**
* @author Thomas Hallgren
*/
public class RepositoryServerServlet extends HttpServlet
{
/**
* jabsorb version 1.3.0 is not able to handle callable references for return types of interface
* type. It can only manage passing the actual server implementation over to the client. This
* class is a remedy for that until a better solution is provided in jabsorb.
*/
@SuppressWarnings("unchecked")
public static class CallableInterfaceSerializer extends AbstractSerializer
{
private static final long serialVersionUID = -5868861157010733475L;
private final JSONRPCBridge m_bridge;
private static final Class<?>[] s_jsonClasses = new Class<?>[] { JSONObject.class };
private static final Class<?>[] s_concreteClasses = new Class<?>[] { MetadataRepositoryFacade.class,
RepositoryDataStream.class };
private static final Class<?>[] s_interfaceClasses = new Class<?>[] { IRepositoryFacade.class,
IRepositoryDataStream.class };
public CallableInterfaceSerializer(JSONRPCBridge bridge)
{
m_bridge = bridge;
}
public Class[] getJSONClasses()
{
return s_jsonClasses;
}
public Class[] getSerializableClasses()
{
return s_concreteClasses;
}
public Class[] getSerializableInterfaces()
{
return s_interfaceClasses;
}
public Object marshall(SerializerState state, Object p, Object o) throws MarshallException
{
Class oc = o.getClass();
int idx = s_concreteClasses.length;
while(--idx >= 0)
{
Class<?> c = s_concreteClasses[idx];
if(c.equals(oc))
{
try
{
Integer identity = new Integer(System.identityHashCode(o));
JSONObject jso = new JSONObject();
jso.put("JSONRPCType", "CallableReference");
jso.put("javaClass", s_interfaceClasses[idx].getName());
jso.put("objectID", identity);
m_bridge.registerObject(identity, o);
return jso;
}
catch(JSONException e)
{
throw new MarshallException(e.getMessage());
}
}
}
throw new MarshallException("Unable to marshal an instance of " + oc);
}
public ObjectMatch tryUnmarshall(SerializerState state, Class clazz, Object json)
throws UnmarshallException
{
state.setSerialized(json, ObjectMatch.OKAY);
return ObjectMatch.OKAY;
}
public Object unmarshall(SerializerState state, Class clazz, Object json) throws UnmarshallException
{
JSONObject jso = (JSONObject)json;
Object ref = null;
String json_type;
int object_id;
try
{
json_type = jso.getString("JSONRPCType");
object_id = jso.getInt("objectID");
}
catch(JSONException e)
{
throw new UnmarshallException(e.getMessage(), e);
}
if(json_type != null && json_type.equals("CallableReference"))
ref = m_bridge.getReference(object_id);
state.setSerialized(jso, ref);
return ref;
}
}
private static final long serialVersionUID = 2598977451925554498L;
/**
* The size of the buffer used for reading requests
*/
private final static int BUFFER_SIZE = 0x8000;
private static URI getRequestURI(HttpServletRequest request) throws ServletException
{
try
{
return new URI(request.getRequestURL().toString());
}
catch(URISyntaxException e)
{
throw new ServletException(e);
}
}
/**
* Called when a JSON-RPC requests comes in. Looks in the session for a JSONRPCBridge and if not
* found there, uses the global bridge; then passes off the JSON-PRC call to be handled by the
* JSONRPCBridge found.
* @param request servlet request from browser.
* @param response servlet response to browser.
* @throws IOException if an IOException occurs during processing.
*/
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException,
IOException
{
response.setContentType("text/plain;charset=utf-8");
String charset = request.getCharacterEncoding();
if(charset == null)
charset = "UTF-8";
BufferedReader in = new BufferedReader(new InputStreamReader(request.getInputStream(), charset));
CharArrayWriter data = new CharArrayWriter();
char buf[] = new char[BUFFER_SIZE];
int ret;
while((ret = in.read(buf, 0, BUFFER_SIZE)) != -1)
{
data.write(buf, 0, ret);
}
JSONRPCResult jsonResponse;
try
{
String receiveString = data.toString();
if(receiveString.length() == 0)
{
// This is a request to establish the session
//
establishSession(request, response);
return;
}
JSONRPCBridge bridge = findBridge(request);
JSONObject jsonRequest = new JSONObject(receiveString);
jsonResponse = bridge.call(new Object[] { request, response }, jsonRequest);
}
catch(JSONException e)
{
jsonResponse = new JSONRPCResult(JSONRPCResult.CODE_ERR_PARSE, null, JSONRPCResult.MSG_ERR_PARSE);
}
String sendString = jsonResponse.toString();
byte[] bout = sendString.getBytes("UTF-8");
response.setIntHeader("Content-Length", bout.length);
OutputStream out = response.getOutputStream();
out.write(bout);
out.flush();
out.close();
}
private void establishSession(HttpServletRequest request, HttpServletResponse response)
throws ServletException
{
Cookie cookie = new Cookie("buckminster.p2.remote", UUID.randomUUID().toString());
response.addCookie(cookie);
HttpSession session = request.getSession(true);
final JSONRPCBridge bridge = new JSONRPCBridge();
try
{
bridge.registerReference(MetadataRepositoryFacade.class);
bridge.registerReference(IRepositoryFacade.class);
bridge.registerReference(IRepositoryDataStream.class);
bridge.registerSerializer(new CallableInterfaceSerializer(bridge));
bridge.registerSerializer(new IUMarshaller());
bridge.registerSerializer(new URISerializer());
bridge.registerObject(IRepositoryServer.SERVICE_NAME, RepositoryServer.getServer(
getRequestURI(request), JSONRPCBridge.getSerializer()), IRepositoryServer.class);
}
catch(RuntimeException e)
{
throw e;
}
catch(Exception e)
{
throw new ServletException(e);
}
session.setAttribute("JSONRPCBridge", bridge);
Logger.setConsoleLevelThreshold(Logger.DEBUG);
}
private JSONRPCBridge findBridge(HttpServletRequest request) throws ServletException, IOException
{
HttpSession session = request.getSession(false);
if(session == null)
throw new ServletException("No session has been established");
JSONRPCBridge bridge = (JSONRPCBridge)session.getAttribute("JSONRPCBridge");
if(bridge == null)
throw new ServletException("JSONRPCBridge was not found in current session");
return bridge;
}
}