package org.jboss.seam.remoting;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.contexts.FacesLifecycle;
import org.jboss.seam.contexts.PageContext;
import org.jboss.seam.contexts.RemotingLifecycle;
import org.jboss.seam.core.ConversationPropagation;
import org.jboss.seam.core.Manager;
import org.jboss.seam.log.LogProvider;
import org.jboss.seam.log.Logging;
import org.jboss.seam.remoting.wrapper.Wrapper;
import org.jboss.seam.servlet.ContextualHttpServletRequest;
/**
* Unmarshals the calls from an HttpServletRequest, executes them in order and
* marshals the responses.
*
* @author Shane Bryzak
*/
public class ExecutionHandler extends BaseRequestHandler implements RequestHandler
{
private static final LogProvider log = Logging.getLogProvider(ExecutionHandler.class);
private static final byte[] HEADER_OPEN = "<header>".getBytes();
private static final byte[] HEADER_CLOSE = "</header>".getBytes();
private static final byte[] CONVERSATION_ID_TAG_OPEN = "<conversationId>".getBytes();
private static final byte[] CONVERSATION_ID_TAG_CLOSE = "</conversationId>".getBytes();
private static final byte[] CONTEXT_TAG_OPEN = "<context>".getBytes();
private static final byte[] CONTEXT_TAG_CLOSE = "</context>".getBytes();
/**
* The entry point for handling a request.
*
* @param request HttpServletRequest
* @param response HttpServletResponse
* @throws Exception
*/
public void handle(HttpServletRequest request, final HttpServletResponse response)
throws Exception
{
// We're sending an XML response, so set the response content type to text/xml
response.setContentType("text/xml");
// Parse the incoming request as XML
SAXReader xmlReader = new SAXReader();
Document doc = xmlReader.read( request.getInputStream() );
final Element env = doc.getRootElement();
final RequestContext ctx = unmarshalContext(env);
// TODO - we really want to extract the page context from our request
RemotingLifecycle.restorePageContext();
new ContextualHttpServletRequest(request)
{
@Override
public void process() throws Exception
{
// Extract the calls from the request
List<Call> calls = unmarshalCalls(env);
// Execute each of the calls
for (Call call : calls)
{
call.execute();
}
// Store the conversation ID in the outgoing context
ctx.setConversationId( Manager.instance().getCurrentConversationId() );
// Package up the response
marshalResponse(calls, ctx, response.getOutputStream());
}
@Override
protected void restoreConversationId()
{
ConversationPropagation.instance().setConversationId( ctx.getConversationId() );
}
@Override
protected void handleConversationPropagation() {}
}.run();
}
/**
* Unmarshals the context from the request envelope header.
*
* @param env Element
* @return RequestContext
*/
private RequestContext unmarshalContext(Element env)
{
RequestContext ctx = new RequestContext();
Element header = env.element("header");
if (header != null)
{
Element context = header.element("context");
if (context != null)
{
Element convId = context.element("conversationId");
if (convId != null)
{
ctx.setConversationId(convId.getText());
}
}
}
return ctx;
}
/**
* Unmarshal the request into a list of Calls.
*
* @param env Element
* @throws Exception
*/
private List<Call> unmarshalCalls(Element env) throws Exception
{
try
{
List<Call> calls = new ArrayList<Call>();
List<Element> callElements = env.element("body").elements("call");
for (Element e : callElements)
{
Call call = new Call(e.attributeValue("id"),
e.attributeValue("component"),
e.attributeValue("method"));
// First reconstruct all the references
Element refsNode = e.element("refs");
Iterator iter = refsNode.elementIterator("ref");
while (iter.hasNext())
{
call.getContext().createWrapperFromElement((Element) iter.next());
}
// Now unmarshal the ref values
for (Wrapper w : call.getContext().getInRefs().values())
{
w.unmarshal();
}
Element paramsNode = e.element("params");
// Then process the param values
iter = paramsNode.elementIterator("param");
while (iter.hasNext())
{
Element param = (Element) iter.next();
call.addParameter(call.getContext().createWrapperFromElement(
(Element) param.elementIterator().next()));
}
calls.add(call);
}
List<Element> exprElements = env.element("body").elements("eval");
for (Element e : exprElements)
{
Call call = new Call(e.attributeValue("id"), e.attributeValue("expr"));
calls.add(call);
}
return calls;
}
catch (Exception ex)
{
log.error("Error unmarshalling calls from request", ex);
throw ex;
}
}
/**
* Write the results to the output stream.
*
* @param calls List The list of calls to write
* @param out OutputStream The stream to write to
* @throws IOException
*/
private void marshalResponse(List<Call> calls, RequestContext ctx, OutputStream out)
throws IOException
{
out.write(ENVELOPE_TAG_OPEN);
if (ctx.getConversationId() != null)
{
out.write(HEADER_OPEN);
out.write(CONTEXT_TAG_OPEN);
out.write(CONVERSATION_ID_TAG_OPEN);
out.write(ctx.getConversationId().getBytes());
out.write(CONVERSATION_ID_TAG_CLOSE);
out.write(CONTEXT_TAG_CLOSE);
out.write(HEADER_CLOSE);
}
out.write(BODY_TAG_OPEN);
for (Call call : calls)
{
MarshalUtils.marshalResult(call.getId(), call.getContext(), out,
call.getResult(), call.getConstraints());
}
out.write(BODY_TAG_CLOSE);
out.write(ENVELOPE_TAG_CLOSE);
out.flush();
}
}