package org.jboss.seam.remoting.model;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringReader;
import java.lang.reflect.Array;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.enterprise.context.Conversation;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
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.logging.Logger;
import org.jboss.seam.remoting.AbstractRequestHandler;
import org.jboss.seam.remoting.Call;
import org.jboss.seam.remoting.MarshalUtils;
import org.jboss.seam.remoting.RequestContext;
import org.jboss.seam.remoting.RequestHandler;
import org.jboss.seam.remoting.wrapper.BagWrapper;
import org.jboss.seam.remoting.wrapper.BeanWrapper;
import org.jboss.seam.remoting.wrapper.MapWrapper;
import org.jboss.seam.remoting.wrapper.Wrapper;
/**
* Handles incoming model fetch/apply requests
*
* @author Shane Bryzak
*/
public class ModelHandler extends AbstractRequestHandler implements RequestHandler
{
private static final Logger log = Logger.getLogger(ModelHandler.class);
@Inject BeanManager beanManager;
@Inject ModelRegistry registry;
@Inject Conversation conversation;
public void handle(HttpServletRequest request, HttpServletResponse response)
throws Exception
{
response.setContentType("text/xml");
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[256];
int read = request.getInputStream().read(buffer);
while (read != -1)
{
out.write(buffer, 0, read);
read = request.getInputStream().read(buffer);
}
String requestData = new String(out.toByteArray());
log.debug("Processing model request: " + requestData);
SAXReader xmlReader = new SAXReader();
Document doc = xmlReader.read(new StringReader(requestData));
final Element env = doc.getRootElement();
final RequestContext ctx = new RequestContext(env.element("header"));
try
{
activateConversationContext(request, ctx.getConversationId());
Element modelElement = env.element("body").element("model");
String operation = modelElement.attributeValue("operation");
if ("expand".equals(operation))
{
processExpandRequest(modelElement, ctx, response.getOutputStream());
}
else
{
Model model = null;
if ("fetch".equals(operation))
{
model = processFetchRequest(modelElement);
}
else if ("apply".equals(operation))
{
model = processApplyRequest(modelElement);
}
if (model.getAction() != null && model.getAction().getException() != null)
{
response.getOutputStream().write(ENVELOPE_TAG_OPEN);
response.getOutputStream().write(BODY_TAG_OPEN);
MarshalUtils.marshalException(model.getAction().getException(),
model.getAction().getContext(), response.getOutputStream());
response.getOutputStream().write(BODY_TAG_CLOSE);
response.getOutputStream().write(ENVELOPE_TAG_CLOSE);
response.getOutputStream().flush();
return;
}
model.evaluate();
ctx.setConversationId(conversation.isTransient() ? null : conversation.getId());
marshalResponse(model, ctx, response.getOutputStream());
}
}
finally
{
deactivateConversationContext(request);
}
}
@SuppressWarnings({ "unchecked" })
private Model processFetchRequest(Element modelElement)
throws Exception
{
Model model = registry.createModel();
if (modelElement.elements("action").size() > 0)
{
unmarshalAction(modelElement.element("action"), model);
}
for (Element beanElement : (List<Element>) modelElement.elements("bean"))
{
Element beanNameElement = beanElement.element("name");
Element beanQualifierElement = beanElement.element("qualifier");
Element beanPropertyElement = beanElement.element("property");
model.addBean(beanElement.attributeValue("alias"),
beanNameElement.getTextTrim(),
beanQualifierElement != null ? beanQualifierElement.getTextTrim() : null,
beanPropertyElement != null ? beanPropertyElement.getTextTrim() : null);
}
// TODO Unmarshal expressions - don't support this until security implications investigated
//for (Element exprElement : (List<Element>) modelElement.elements("expression"))
//{
//}
if (model.getAction() != null)
{
model.getAction().execute();
}
return model;
}
@SuppressWarnings("unchecked")
private void unmarshalAction(Element actionElement, Model model)
{
Element targetElement = actionElement.element("target");
Element qualifiersElement = actionElement.element("qualifiers");
Element methodElement = actionElement.element("method");
Element paramsElement = actionElement.element("params");
Element refsElement = actionElement.element("refs");
model.setAction(new Call(beanManager, targetElement.getTextTrim(),
qualifiersElement != null ? qualifiersElement.getTextTrim() : null,
methodElement != null ? methodElement.getTextTrim() : null));
if (refsElement != null)
{
for (Element refElement : (List<Element>) refsElement.elements("ref"))
{
model.getAction().getContext().createWrapperFromElement(refElement);
}
for (Wrapper w : model.getAction().getContext().getInRefs().values())
{
w.unmarshal();
}
}
if (paramsElement != null)
{
for (Element paramElement : (List<Element>) paramsElement.elements("param"))
{
model.getAction().addParameter(model.getAction().getContext().createWrapperFromElement(
(Element) paramElement.elements().get(0)));
}
}
}
@SuppressWarnings({ "unchecked", "unused" })
private Model processApplyRequest(Element modelElement)
throws Exception
{
Model model = registry.getModel(modelElement.attributeValue("id"));
model.setAction(null);
// We clone the outRefs to the inRefs so that the context can locate
// already-loaded refs when unmarshalling
for (int i = 0; i < model.getCallContext().getOutRefs().size(); i++)
{
model.getCallContext().getInRefs().put("" + i, model.getCallContext().getOutRefs().get(i));
}
Element refsElement = modelElement.element("refs");
if (refsElement != null)
{
List<Wrapper> newRefs = new ArrayList<Wrapper>();
for (Element ref : (List<Element>) refsElement.elements("ref"))
{
newRefs.add(model.getCallContext().createWrapperFromElement(ref));
}
// Unmarshal any new ref values
for (Wrapper w : newRefs)
{
w.unmarshal();
}
}
Element delta = modelElement.element("delta");
if (delta != null)
{
List<Element> changesets = delta.elements("changeset");
for (Element changeset : changesets)
{
int refId = Integer.parseInt(changeset.attributeValue("refid"));
if (changeset.elements("member").size() > 0)
{
Wrapper target = model.getCallContext().getOutRefs().get(refId);
if (!(target instanceof BeanWrapper))
{
throw new IllegalStateException("Changeset for refId [" +
refId + "] does not reference a valid bean object");
}
for (Element member : (List<Element>) changeset.elements("member"))
{
String name = member.attributeValue("name");
Wrapper source = model.getCallContext().createWrapperFromElement(
(Element) member.elementIterator().next());
if (source instanceof BagWrapper)
{
Object targetBag = ((BeanWrapper) target).getBeanProperty(name);
if (targetBag == null)
{
((BeanWrapper) target).setBeanProperty(name, source);
}
else
{
Type t = ((BeanWrapper) target).getBeanPropertyType(name);
if (!cloneBagContents(source.convert(t), ((Wrapper) targetBag).getValue()))
{
((BeanWrapper) target).setBeanProperty(name, source);
}
}
}
else if (source instanceof MapWrapper)
{
Object targetMap = ((BeanWrapper) target).getBeanProperty(name);
if (!Map.class.isAssignableFrom(targetMap.getClass()))
{
throw new IllegalStateException("Cannot assign Map value " +
"to non Map property [" + target.getClass().getName() +
"." + name + "]");
}
if (targetMap == null)
{
((BeanWrapper) target).setBeanProperty(name, source);
}
else
{
Type t = ((BeanWrapper) target).getBeanPropertyType(name);
cloneMapContents((Map<Object,Object>) source.convert(t),
(Map<Object,Object>) targetMap);
}
}
else
{
((BeanWrapper) target).setBeanProperty(name, source);
}
}
}
if (changeset.elements("bag").size() > 0)
{
Wrapper target = model.getCallContext().getOutRefs().get(refId);
Wrapper source = model.getCallContext().createWrapperFromElement(
(Element) changeset.element("bag"));
cloneBagContents(source.convert(target.getValue().getClass()),
target.getValue());
}
else if (changeset.elements("map").size() > 0)
{
Wrapper target = model.getCallContext().getOutRefs().get(refId);
Wrapper source = model.getCallContext().createWrapperFromElement(
(Element) changeset.element("map"));
cloneMapContents((Map<Object,Object>) source.convert(target.getValue().getClass()),
(Map<Object,Object>) target.getValue());
}
}
}
if (modelElement.elements("action").size() > 0)
{
unmarshalAction(modelElement.element("action"), model);
}
if (model.getAction() != null)
{
model.getAction().execute();
}
return model;
}
private void processExpandRequest(Element modelElement, RequestContext ctx, OutputStream out)
throws Exception
{
Model model = registry.getModel(modelElement.attributeValue("id"));
model.setAction(null);
Element refElement = modelElement.element("ref");
if (refElement == null)
{
throw new IllegalStateException("Invalid request state - no object ref found");
}
int refId = Integer.parseInt(refElement.attributeValue("id"));
Wrapper target = model.getCallContext().getOutRefs().get(refId);
int newRefIdx = model.getCallContext().getOutRefs().size();
Element memberElement = refElement.element("member");
String memberName = memberElement.attributeValue("name");
Wrapper value = ((BeanWrapper) target).getBeanProperty(memberName);
if (value instanceof BagWrapper)
{
((BagWrapper) value).setLoadLazy(true);
}
out.write(ENVELOPE_TAG_OPEN);
out.write(HEADER_OPEN);
out.write(CONTEXT_TAG_OPEN);
if (ctx.getConversationId() != null)
{
out.write(CONVERSATION_ID_TAG_OPEN);
out.write(ctx.getConversationId().getBytes());
out.write(CONVERSATION_ID_TAG_CLOSE);
}
out.write(CALL_ID_TAG_OPEN);
out.write(ctx.getCallId().toString().getBytes());
out.write(CALL_ID_TAG_CLOSE);
out.write(CONTEXT_TAG_CLOSE);
out.write(HEADER_CLOSE);
out.write(BODY_TAG_OPEN);
MarshalUtils.marshalModelExpand(model, value, out, newRefIdx);
out.write(BODY_TAG_CLOSE);
out.write(ENVELOPE_TAG_CLOSE);
out.flush();
}
/**
* Clones the contents of the specified source bag into the specified target
* bag. If the contents can be cloned, this method returns true, otherwise it
* returns false.
*
* @param sourceBag
* @param targetBag
* @return
*/
@SuppressWarnings("unchecked")
private boolean cloneBagContents(Object sourceBag, Object targetBag)
{
Class<?> cls = sourceBag.getClass();
if (cls.isArray())
{
int sourceLen = Array.getLength(sourceBag);
int targetLen = Array.getLength(targetBag);
if (targetLen != sourceLen) return false;
for (int i = 0; i < sourceLen; i++)
{
Array.set(targetBag, i, Array.get(sourceBag, i));
}
return true;
}
else if (List.class.isAssignableFrom(cls))
{
List<Object> sourceList = (List<Object>) sourceBag;
List<Object> targetList = (List<Object>) targetBag;
targetList.clear();
for (int i = 0; i < sourceList.size(); i++)
{
if (targetList.size() < i + 1)
{
targetList.add(i, sourceList.get(i));
}
else if (targetList.get(i) != sourceList.get(i))
{
targetList.set(i, sourceList.get(i));
}
}
return true;
}
else if (Set.class.isAssignableFrom(cls))
{
Set<Object> sourceSet = (Set<Object>) sourceBag;
Set<Object> targetSet = (Set<Object>) targetBag;
for (Object e : sourceSet)
{
if (!targetSet.contains(e))
{
targetSet.add(e);
}
}
for (Object e : targetSet)
{
if (!sourceSet.contains(e))
{
targetSet.remove(e);
}
}
return true;
}
return false;
}
/**
* Clones the contents of one Map into another
*
* @param sourceMap
* @param targetMap
*/
private void cloneMapContents(Map<Object,Object> sourceMap, Map<Object,Object> targetMap)
{
for (Object key : sourceMap.keySet())
{
if (!targetMap.containsKey(key))
{
targetMap.put(key, sourceMap.get(key));
}
}
for (Object key : targetMap.keySet())
{
if (!sourceMap.containsKey(key))
{
targetMap.remove(key);
}
}
}
private void marshalResponse(Model model, RequestContext ctx,
OutputStream out) throws IOException
{
out.write(ENVELOPE_TAG_OPEN);
out.write(HEADER_OPEN);
out.write(CONTEXT_TAG_OPEN);
if (ctx.getConversationId() != null)
{
out.write(CONVERSATION_ID_TAG_OPEN);
out.write(ctx.getConversationId().getBytes());
out.write(CONVERSATION_ID_TAG_CLOSE);
}
out.write(CALL_ID_TAG_OPEN);
out.write(ctx.getCallId().toString().getBytes());
out.write(CALL_ID_TAG_CLOSE);
out.write(CONTEXT_TAG_CLOSE);
out.write(HEADER_CLOSE);
out.write(BODY_TAG_OPEN);
MarshalUtils.marshalModel(model, out);
out.write(BODY_TAG_CLOSE);
out.write(ENVELOPE_TAG_CLOSE);
out.flush();
}
}