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.solder.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();
}
}