[seam-commits] Seam SVN: r11690 - in modules/trunk/remoting: .settings and 10 other directories.

seam-commits at lists.jboss.org seam-commits at lists.jboss.org
Fri Nov 27 04:27:02 EST 2009


Author: shane.bryzak at jboss.com
Date: 2009-11-27 04:27:00 -0500 (Fri, 27 Nov 2009)
New Revision: 11690

Added:
   modules/trunk/remoting/.classpath
   modules/trunk/remoting/.project
   modules/trunk/remoting/.settings/
   modules/trunk/remoting/.settings/org.eclipse.jdt.core.prefs
   modules/trunk/remoting/.settings/org.maven.ide.eclipse.prefs
   modules/trunk/remoting/examples/
   modules/trunk/remoting/examples/helloworld/
   modules/trunk/remoting/pom.xml
   modules/trunk/remoting/src/main/java/org/
   modules/trunk/remoting/src/main/java/org/jboss/
   modules/trunk/remoting/src/main/java/org/jboss/seam/
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/BaseRequestHandler.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/Call.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/CallContext.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/ExecutionHandler.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/InterfaceGenerator.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/MarshalUtils.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/PollHandler.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/Remoting.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/RequestContext.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/RequestHandler.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/RequestHandlerFactory.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/SubscriptionHandler.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/annotations/
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/annotations/WebRemote.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/client/
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/client/ParserUtils.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/PollError.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/PollRequest.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/RemoteSubscriber.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/SubscriptionRegistry.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/SubscriptionRequest.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/UserTokens.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/BagWrapper.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/BaseWrapper.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/BeanWrapper.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/BooleanWrapper.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/ConversionException.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/ConversionScore.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/DateWrapper.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/MapWrapper.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/NullWrapper.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/NumberWrapper.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/StringWrapper.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/Wrapper.java
   modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/WrapperFactory.java
Log:
remoting


Added: modules/trunk/remoting/.classpath
===================================================================
--- modules/trunk/remoting/.classpath	                        (rev 0)
+++ modules/trunk/remoting/.classpath	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
+	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
+	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>

Added: modules/trunk/remoting/.project
===================================================================
--- modules/trunk/remoting/.project	                        (rev 0)
+++ modules/trunk/remoting/.project	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>seam-remoting</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.maven.ide.eclipse.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.maven.ide.eclipse.maven2Nature</nature>
+	</natures>
+</projectDescription>

Added: modules/trunk/remoting/.settings/org.eclipse.jdt.core.prefs
===================================================================
--- modules/trunk/remoting/.settings/org.eclipse.jdt.core.prefs	                        (rev 0)
+++ modules/trunk/remoting/.settings/org.eclipse.jdt.core.prefs	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,5 @@
+#Tue Nov 24 19:06:28 CET 2009
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.source=1.5

Added: modules/trunk/remoting/.settings/org.maven.ide.eclipse.prefs
===================================================================
--- modules/trunk/remoting/.settings/org.maven.ide.eclipse.prefs	                        (rev 0)
+++ modules/trunk/remoting/.settings/org.maven.ide.eclipse.prefs	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,9 @@
+#Tue Nov 24 19:06:26 CET 2009
+activeProfiles=
+eclipse.preferences.version=1
+fullBuildGoals=process-test-resources
+includeModules=false
+resolveWorkspaceProjects=true
+resourceFilterGoals=process-resources resources\:testResources
+skipCompilerPlugin=true
+version=1

Added: modules/trunk/remoting/pom.xml
===================================================================
--- modules/trunk/remoting/pom.xml	                        (rev 0)
+++ modules/trunk/remoting/pom.xml	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,84 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+   <modelVersion>4.0.0</modelVersion>
+   
+   <parent>
+      <artifactId>seam-parent</artifactId>
+      <groupId>org.jboss.seam</groupId>
+      <version>3.0.0-SNAPSHOT</version>
+   </parent>
+
+   <artifactId>seam-remoting</artifactId>
+   <packaging>jar</packaging>
+   <version>3.0.0-SNAPSHOT</version>
+   <name>Seam Remoting</name>
+
+   <dependencies>
+
+      <dependency>
+         <groupId>javax.el</groupId>
+         <artifactId>el-api</artifactId>
+         <scope>provided</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>javax.validation</groupId>
+         <artifactId>validation-api</artifactId>
+         <scope>provided</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>org.hibernate</groupId>
+         <artifactId>hibernate</artifactId>
+         <scope>provided</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>${seam.groupId}</groupId>
+         <artifactId>seam-el</artifactId>
+      </dependency>
+
+      <!--dependency>
+         <groupId>${seam.groupId}</groupId>
+         <artifactId>seam-international</artifactId>
+      </dependency-->
+
+      <dependency>
+         <groupId>javax.enterprise</groupId>
+         <artifactId>cdi-api</artifactId>
+         <scope>provided</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>javax.ejb</groupId>
+         <artifactId>ejb-api</artifactId>
+         <scope>provided</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>javax.jms</groupId>
+         <artifactId>jms</artifactId>
+         <scope>provided</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>dom4j</groupId>
+         <artifactId>dom4j</artifactId>
+         <scope>provided</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>javax.servlet</groupId>
+         <artifactId>servlet-api</artifactId>
+         <scope>provided</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>org.slf4j</groupId>
+         <artifactId>slf4j-api</artifactId>
+         <scope>provided</scope>
+      </dependency>
+   </dependencies>
+
+</project>

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/BaseRequestHandler.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/BaseRequestHandler.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/BaseRequestHandler.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,20 @@
+package org.jboss.seam.remoting;
+
+import javax.enterprise.inject.spi.BeanManager;
+import javax.servlet.ServletContext;
+
+/**
+ *
+ * @author Shane Bryzak
+ */
+public abstract class BaseRequestHandler implements RequestHandler
+{
+   protected BeanManager beanManager;
+   
+   public BaseRequestHandler(BeanManager beanManager)
+   {
+      this.beanManager = beanManager;
+   }
+   
+   public void setServletContext(ServletContext context) {}
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/Call.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/Call.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/Call.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,289 @@
+package org.jboss.seam.remoting;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.ejb.Local;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+
+import org.jboss.seam.remoting.annotations.WebRemote;
+import org.jboss.seam.remoting.wrapper.ConversionException;
+import org.jboss.seam.remoting.wrapper.ConversionScore;
+import org.jboss.seam.remoting.wrapper.Wrapper;
+
+/**
+ * 
+ * @author Shane Bryzak
+ */
+public class Call
+{
+   private BeanManager beanManager;
+
+   private String id;
+   private String componentName;
+   private String methodName;
+   // private String expression;
+   private Throwable exception;
+
+   private List<Wrapper> params = new ArrayList<Wrapper>();
+
+   private Object result;
+
+   private CallContext context;
+
+   private List<String> constraints = null;
+
+   /**
+    * Constructor.
+    */
+   public Call(BeanManager beanManager, String id, String componentName,
+         String methodName)
+   {
+      this.beanManager = beanManager;
+      this.id = id;
+      this.componentName = componentName;
+      this.methodName = methodName;
+      this.context = new CallContext(beanManager);
+   }
+
+   /**
+    * Return the call context.
+    * 
+    * @return CallContext
+    */
+   public CallContext getContext()
+   {
+      return context;
+   }
+
+   /**
+    * Returns the exception thrown by the invoked method. If no exception was
+    * thrown, will return null.
+    */
+   public Throwable getException()
+   {
+      return exception;
+   }
+
+   /**
+    * Add a parameter to this call.
+    * 
+    * @param param
+    */
+   public void addParameter(Wrapper param)
+   {
+      params.add(param);
+   }
+
+   /**
+    * Returns the result of this call.
+    * 
+    * @return Wrapper
+    */
+   public Object getResult()
+   {
+      return result;
+   }
+
+   /**
+    * Required for unit tests
+    * 
+    * @param result
+    */
+   public void setResult(Object result)
+   {
+      this.result = result;
+   }
+
+   /**
+    * Returns the id of this call.
+    * 
+    * @return String
+    */
+   public String getId()
+   {
+      return id;
+   }
+
+   /**
+    * Returns the object graph constraints annotated on the method that is
+    * called.
+    * 
+    * @return List The constraints
+    */
+   public List<String> getConstraints()
+   {
+      return constraints;
+   }
+
+   /**
+    * Required for unit tests
+    * 
+    * @param constraints
+    */
+   public void setConstraints(List<String> constraints)
+   {
+      this.constraints = constraints;
+   }
+
+   /**
+    * Execute this call
+    * 
+    * @throws Exception
+    */
+   public void execute() throws Exception
+   {
+      if (componentName != null)
+      {
+         processInvocation();
+      }
+   }
+
+   private void processInvocation() throws Exception
+   {
+      // Find the component we're calling
+      Bean bean = beanManager.getBeans(componentName).iterator().next();
+
+      if (bean == null)
+      {
+         throw new RuntimeException("No such component: " + componentName);
+      }
+
+      // Create an instance of the component
+      Object instance = bean.create(beanManager.createCreationalContext(bean));
+
+      if (instance == null)
+      {
+         throw new RuntimeException(String.format(
+               "Could not create instance of component %s", componentName));
+      }
+
+      Class type = null;
+
+      if (component.getType().isSessionBean()
+            && component.getBusinessInterfaces().size() > 0)
+      {
+         for (Class c : component.getBusinessInterfaces())
+         {
+            if (c.isAnnotationPresent(Local.class))
+            {
+               type = c;
+               break;
+            }
+         }
+
+         if (type == null)
+         {
+            throw new RuntimeException(String.format(
+              "Type cannot be determined for bean [%s]. Please ensure that it has a local interface.",
+                              bean));
+         }
+      }
+
+      if (type == null)
+      {
+         type = bean.getBeanClass();
+      }
+
+      // Find the method according to the method name and the parameter classes
+      Method m = findMethod(methodName, type);
+      if (m == null)
+         throw new RuntimeException("No compatible method found.");
+
+      if (m.getAnnotation(WebRemote.class).exclude().length > 0)
+         constraints = Arrays
+               .asList(m.getAnnotation(WebRemote.class).exclude());
+
+      Object[] params = convertParams(m.getGenericParameterTypes());
+
+      // Invoke!
+      try
+      {
+         result = m.invoke(instance, params);
+      } catch (InvocationTargetException e)
+      {
+         this.exception = e.getCause();
+      }
+   }
+
+   /**
+    * Convert our parameter values to an Object array of the specified target
+    * types.
+    * 
+    * @param targetTypes
+    *           Class[] An array containing the target class types.
+    * @return Object[] The converted parameter values.
+    */
+   private Object[] convertParams(Type[] targetTypes)
+         throws ConversionException
+   {
+      Object[] paramValues = new Object[targetTypes.length];
+
+      for (int i = 0; i < targetTypes.length; i++)
+      {
+         paramValues[i] = params.get(i).convert(targetTypes[i]);
+      }
+
+      return paramValues;
+   }
+
+   /**
+    * Find the best matching method within the specified class according to the
+    * parameter types that were provided to the Call.
+    * 
+    * @param name
+    *           String The name of the method.
+    * @param cls
+    *           Class The Class to search in.
+    * @return Method The best matching method.
+    */
+   private Method findMethod(String name, Class cls)
+   {
+      Map<Method, Integer> candidates = new HashMap<Method, Integer>();
+
+      for (Method m : cls.getDeclaredMethods())
+      {
+         if (m.getAnnotation(WebRemote.class) == null)
+            continue;
+
+         if (name.equals(m.getName())
+               && m.getParameterTypes().length == params.size())
+         {
+            int score = 0;
+
+            for (int i = 0; i < m.getParameterTypes().length; i++)
+            {
+               ConversionScore convScore = params.get(i).conversionScore(
+                     m.getParameterTypes()[i]);
+               if (convScore == ConversionScore.nomatch)
+                  continue;
+               score += convScore.getScore();
+            }
+            candidates.put(m, score);
+         }
+      }
+
+      Method bestMethod = null;
+      int bestScore = 0;
+
+      for (Entry<Method, Integer> entry : candidates.entrySet())
+      {
+         int thisScore = entry.getValue();
+         if (bestMethod == null || thisScore > bestScore)
+         {
+            bestMethod = entry.getKey();
+            bestScore = thisScore;
+         }
+      }
+
+      return bestMethod;
+   }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/CallContext.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/CallContext.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/CallContext.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,120 @@
+package org.jboss.seam.remoting;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.enterprise.inject.spi.BeanManager;
+
+import org.dom4j.Element;
+import org.jboss.seam.remoting.wrapper.Wrapper;
+import org.jboss.seam.remoting.wrapper.WrapperFactory;
+
+/**
+ * Represents the context of an individual call.
+ *
+ * @author Shane Bryzak
+ */
+public class CallContext
+{
+   private BeanManager beanManager;
+   
+   public CallContext(BeanManager beanManager)
+   {
+      this.beanManager = beanManager;
+   }
+   
+  /**
+   * Contains references to inbound bean objects identified by their ref.
+   */
+  private Map<String, Wrapper> inRefs = new HashMap<String, Wrapper>();
+
+  /**
+   * Contains references to outbound bean objects identified by their object.
+   */
+  private List<Wrapper> outRefs = new ArrayList<Wrapper>();
+
+  /**
+   *
+   * @param element Element
+   * @return Wrapper
+   */
+  public Wrapper createWrapperFromElement(Element element)
+  {
+    if ("ref".equals(element.getQualifiedName()))
+    {
+      if (inRefs.containsKey(element.attributeValue("id")))
+        return inRefs.get(element.attributeValue("id"));
+      else
+      {
+        Element value = (Element) element.elements().get(0);
+
+        Wrapper w = WrapperFactory.getInstance().createWrapper(
+              value.getQualifiedName(), beanManager);
+        w.setElement(value);
+        w.setCallContext(this);
+        inRefs.put(element.attributeValue("id"), w);
+        return w;
+      }
+    }
+    else
+    {
+      Wrapper w = WrapperFactory.getInstance().createWrapper(
+            element.getQualifiedName(), beanManager);
+      w.setElement(element);
+      w.setCallContext(this);
+      return w;
+    }
+  }
+
+  /**
+   *
+   * @return Wrapper
+   */
+  public Wrapper createWrapperFromObject(Object value, String path)
+  {
+    // Not very efficient but has to be done - may implement something better later
+    for (Wrapper ref : outRefs)
+    {
+      if (ref.getValue().equals(value))
+        return ref;
+    }
+
+    Wrapper w = WrapperFactory.getInstance().getWrapperForObject(value, beanManager);
+    w.setCallContext(this);
+    w.setPath(path);
+    return w;
+  }
+
+  /**
+   * Returns the inbound object references
+   *
+   * @return Map
+   */
+  public Map<String, Wrapper> getInRefs()
+  {
+    return inRefs;
+  }
+
+  /**
+   * Returns the outbound object references
+   *
+   * @return List
+   */
+  public List<Wrapper> getOutRefs()
+  {
+    return outRefs;
+  }
+
+  /**
+   * Add an outbound object reference
+   *
+   * @param w Wrapper
+   */
+  public void addOutRef(Wrapper w)
+  {
+    if (!outRefs.contains(w))
+      outRefs.add(w);
+  }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/ExecutionHandler.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/ExecutionHandler.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/ExecutionHandler.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,232 @@
+package org.jboss.seam.remoting;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.enterprise.context.Conversation;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+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.remoting.wrapper.Wrapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Unmarshals the calls from an HttpServletRequest, executes them in order and
+ * marshals the responses.
+ * 
+ * @author Shane Bryzak
+ */
+public class ExecutionHandler extends BaseRequestHandler implements
+      RequestHandler
+{
+   public ExecutionHandler(BeanManager beanManager)
+   {
+      super(beanManager);
+   }
+
+   private static final Logger log = LoggerFactory
+         .getLogger(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");
+
+      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 remote request: " + requestData);
+
+      // Parse the incoming request as XML
+      SAXReader xmlReader = new SAXReader();
+      Document doc = xmlReader.read(new StringReader(requestData));
+      final Element env = doc.getRootElement();
+      final RequestContext ctx = unmarshalContext(env);
+
+      // Extract the calls from the request
+      List<Call> calls = unmarshalCalls(env);
+
+      // Execute each of the calls
+      for (Call call : calls)
+      {
+         call.execute();
+      }
+
+      Bean conversationBean = beanManager.getBeans(Conversation.class)
+            .iterator().next();
+      Conversation conversation = (Conversation) conversationBean
+            .create(beanManager.createCreationalContext(conversationBean));
+
+      // Store the conversation ID in the outgoing context
+      ctx.setConversationId(conversation.getId());
+
+      // Package up the response
+      marshalResponse(calls, ctx, response.getOutputStream());
+
+   }
+
+   /**
+    * 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(beanManager, 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);
+         }
+
+         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, out);
+      }
+
+      out.write(BODY_TAG_CLOSE);
+      out.write(ENVELOPE_TAG_CLOSE);
+      out.flush();
+   }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/InterfaceGenerator.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/InterfaceGenerator.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/InterfaceGenerator.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,742 @@
+package org.jboss.seam.remoting;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ejb.Local;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.hibernate.type.ComponentType;
+import org.jboss.seam.remoting.annotations.WebRemote;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Generates JavaScript interface code.
+ * 
+ * @author Shane Bryzak
+ */
+public class InterfaceGenerator extends BaseRequestHandler implements
+      RequestHandler
+{
+   public InterfaceGenerator(BeanManager beanManager)
+   {
+      super(beanManager);
+   }
+  
+   private static Logger log = LoggerFactory.getLogger(InterfaceGenerator.class);
+
+   /**
+    * Maintain a cache of the accessible fields
+    */
+   private static Map<Class, Set<String>> accessibleProperties = new HashMap<Class, Set<String>>();
+
+   /**
+    * A cache of component interfaces, keyed by component name.
+    */
+   private Map<String, byte[]> interfaceCache = new HashMap<String, byte[]>();
+
+   /**
+    * 
+    * @param request
+    *           HttpServletRequest
+    * @param response
+    *           HttpServletResponse
+    * @throws Exception
+    */
+   public void handle(final HttpServletRequest request,
+         final HttpServletResponse response) throws Exception
+   {
+      if (request.getQueryString() == null)
+      {
+         throw new ServletException("Invalid request - no component specified");
+      }
+
+      Set<Bean> beans = new HashSet<Bean>();
+      Set<Type> types = new HashSet<Type>();
+
+      response.setContentType("text/javascript");
+
+      Enumeration e = request.getParameterNames();
+      while (e.hasMoreElements())
+      {
+         String componentName = ((String) e.nextElement()).trim();
+
+         Bean bean = beanManager.getBeans(componentName).iterator().next();
+         if (bean == null)
+         {
+            try
+            {
+               Class c = Class.forName(componentName);
+               appendClassSource(response.getOutputStream(), c, types);
+            } catch (ClassNotFoundException ex)
+            {
+               log.error(String.format("Component not found: [%s]",
+                     componentName));
+               throw new ServletException(
+                     "Invalid request - component not found.");
+            }
+         } else
+         {
+            beans.add(bean);
+         }
+      }
+
+      generateBeanInterface(beans, response.getOutputStream(), types);
+   }
+
+   /**
+    * Generates the JavaScript code required to invoke the methods of a
+    * component/s.
+    * 
+    * @param components
+    *           Component[] The components to generate javascript for
+    * @param out
+    *           OutputStream The OutputStream to write the generated javascript
+    *           to
+    * @throws IOException
+    *            Thrown if there is an error writing to the OutputStream
+    */
+   public void generateBeanInterface(Set<Bean> beans,
+         OutputStream out, Set<Type> types) throws IOException
+   {
+      for (Bean bean : beans)
+      {
+         if (bean != null)
+         {
+            if (!interfaceCache.containsKey(bean.getName()))
+            {
+               synchronized (interfaceCache)
+               {
+                  if (!interfaceCache.containsKey(bean.getName()))
+                  {
+                     ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+                     appendBeanSource(bOut, bean, types);
+                     interfaceCache.put(bean.getName(), bOut.toByteArray());
+                  }
+               }
+            }
+            out.write(interfaceCache.get(bean.getName()));
+         }
+      }
+   }
+
+   /**
+    * A helper method, used internally by InterfaceGenerator and also when
+    * serializing responses. Returns a list of the property names for the
+    * specified class which should be included in the generated interface for
+    * the type.
+    * 
+    * @param cls
+    *           Class
+    * @return List
+    */
+   public static Set<String> getAccessibleProperties(Class cls)
+   {
+      /**
+       * @todo This is a hack to get the "real" class - find out if there is an
+       *       API method in CGLIB that can be used instead
+       */
+      if (cls.getName().contains("EnhancerByCGLIB"))
+         cls = cls.getSuperclass();
+
+      if (!accessibleProperties.containsKey(cls))
+      {
+         synchronized (accessibleProperties)
+         {
+            if (!accessibleProperties.containsKey(cls))
+            {
+               Set<String> properties = new HashSet<String>();
+
+               Class c = cls;
+               while (c != null && !c.equals(Object.class))
+               {
+                  for (Field f : c.getDeclaredFields())
+                  {
+                     if (!Modifier.isTransient(f.getModifiers())
+                           && !Modifier.isStatic(f.getModifiers()))
+                     {
+                        String fieldName = f.getName().substring(0, 1)
+                              .toUpperCase()
+                              + f.getName().substring(1);
+                        String getterName = String.format("get%s", fieldName);
+                        String setterName = String.format("set%s", fieldName);
+                        Method getMethod = null;
+                        Method setMethod = null;
+
+                        try
+                        {
+                           getMethod = c.getMethod(getterName);
+                        } catch (SecurityException ex)
+                        {
+                        } catch (NoSuchMethodException ex)
+                        {
+                           // it might be an "is" method...
+                           getterName = String.format("is%s", fieldName);
+                           try
+                           {
+                              getMethod = c.getMethod(getterName);
+                           } catch (NoSuchMethodException ex2)
+                           { /* don't care */
+                           }
+                        }
+
+                        try
+                        {
+                           setMethod = c.getMethod(setterName, new Class[] { f
+                                 .getType() });
+                        } catch (SecurityException ex)
+                        {
+                        } catch (NoSuchMethodException ex)
+                        { /* don't care */
+                        }
+
+                        if (Modifier.isPublic(f.getModifiers())
+                              || (getMethod != null
+                                    && Modifier.isPublic(getMethod
+                                          .getModifiers()) || (setMethod != null && Modifier
+                                    .isPublic(setMethod.getModifiers()))))
+                        {
+                           properties.add(f.getName());
+                        }
+                     }
+                  }
+
+                  //
+                  for (Method m : c.getDeclaredMethods())
+                  {
+                     if (m.getName().startsWith("get")
+                           || m.getName().startsWith("is"))
+                     {
+                        int startIdx = m.getName().startsWith("get") ? 3 : 2;
+
+                        try
+                        {
+                           c.getMethod(String.format("set%s", m.getName()
+                                 .substring(startIdx)), m.getReturnType());
+                        } catch (NoSuchMethodException ex)
+                        {
+                           continue;
+                        }
+
+                        String propertyName = String.format("%s%s", Character
+                              .toLowerCase(m.getName().charAt(startIdx)), m
+                              .getName().substring(startIdx + 1));
+
+                        if (!properties.contains(propertyName))
+                           properties.add(propertyName);
+                     }
+                  }
+
+                  c = c.getSuperclass();
+               }
+
+               accessibleProperties.put(cls, properties);
+            }
+         }
+      }
+
+      return accessibleProperties.get(cls);
+   }
+
+   /**
+    * Appends component interface code to an outputstream for a specified
+    * component.
+    * 
+    * @param out
+    *           OutputStream The OutputStream to write to
+    * @param component
+    *           Component The component to generate an interface for
+    * @param types
+    *           Set A list of types that have already been generated for this
+    *           request. If this component has already been generated (i.e. it
+    *           is in the list) then it won't be generated again
+    * @throws IOException
+    *            If there is an error writing to the OutputStream.
+    */
+   private void appendBeanSource(OutputStream out, Bean bean,
+         Set<Type> types) throws IOException
+   {
+      StringBuilder componentSrc = new StringBuilder();
+
+      Set<Class> componentTypes = new HashSet<Class>();
+
+      if (bean.getType().isSessionBean()
+            && bean.getBusinessInterfaces().size() > 0)
+      {
+         for (Class c : bean.getBusinessInterfaces())
+         {
+            // Use the Local interface
+            // TODO remove dependency on javax.ejb - iterate through the annotations
+            // instead and do a string comparison
+            if (c.isAnnotationPresent(Local.class))
+            {
+               componentTypes.add(c);
+               break;
+            }
+         }
+
+         if (componentTypes.isEmpty())
+            throw new RuntimeException(
+                  String
+                        .format(
+                              "Type cannot be determined for component [%s]. Please ensure that it has a local interface.",
+                              bean));
+      } else if (bean.getType().equals(ComponentType.ENTITY_BEAN))
+      {
+         appendTypeSource(out, bean.getBeanClass(), types);
+         return;
+      } else if (component.getType().equals(ComponentType.JAVA_BEAN))
+      {
+         // Check if any of the methods are annotated with @WebRemote, and if so
+         // treat it as an "action" component instead of a type component
+         for (Method m : bean.getBeanClass().getDeclaredMethods())
+         {
+            if (m.getAnnotation(WebRemote.class) != null)
+            {
+               componentTypes.add(bean.getBeanClass());
+               break;
+            }
+         }
+
+         if (componentTypes.isEmpty())
+         {
+            appendTypeSource(out, bean.getBeanClass(), types);
+            return;
+         }
+      } else
+      {
+         componentTypes.add(bean.getBeanClass());
+      }
+
+      // If types already contains all the component types, then return
+      boolean foundNew = false;
+      for (Class type : componentTypes)
+      {
+         if (!types.contains(type))
+         {
+            foundNew = true;
+            break;
+         }
+      }
+      if (!foundNew)
+         return;
+
+      if (bean.getName().contains("."))
+      {
+         componentSrc.append("Seam.Remoting.createNamespace('");
+         componentSrc.append(bean.getName().substring(0,
+               bean.getName().lastIndexOf('.')));
+         componentSrc.append("');\n");
+
+      }
+
+      componentSrc.append("Seam.Remoting.type.");
+      componentSrc.append(bean.getName());
+      componentSrc.append(" = function() {\n");
+      componentSrc.append("  this.__callback = new Object();\n");
+
+      for (Class type : componentTypes)
+      {
+         if (types.contains(type))
+         {
+            break;
+         } else
+         {
+            types.add(type);
+
+            for (Method m : type.getDeclaredMethods())
+            {
+               if (m.getAnnotation(WebRemote.class) == null)
+                  continue;
+
+               // Append the return type to the source block
+               appendTypeSource(out, m.getGenericReturnType(), types);
+
+               componentSrc.append("  Seam.Remoting.type.");
+               componentSrc.append(bean.getName());
+               componentSrc.append(".prototype.");
+               componentSrc.append(m.getName());
+               componentSrc.append(" = function(");
+
+               // Insert parameters p0..pN
+               for (int i = 0; i < m.getGenericParameterTypes().length; i++)
+               {
+                  appendTypeSource(out, m.getGenericParameterTypes()[i], types);
+
+                  if (i > 0)
+                     componentSrc.append(", ");
+                  componentSrc.append("p");
+                  componentSrc.append(i);
+               }
+
+               if (m.getGenericParameterTypes().length > 0)
+                  componentSrc.append(", ");
+               componentSrc.append("callback, exceptionHandler) {\n");
+
+               componentSrc.append("    return Seam.Remoting.execute(this, \"");
+               componentSrc.append(m.getName());
+               componentSrc.append("\", [");
+
+               for (int i = 0; i < m.getParameterTypes().length; i++)
+               {
+                  if (i > 0)
+                     componentSrc.append(", ");
+                  componentSrc.append("p");
+                  componentSrc.append(i);
+               }
+
+               componentSrc.append("], callback, exceptionHandler);\n");
+
+               componentSrc.append("  }\n");
+            }
+         }
+         componentSrc.append("}\n");
+
+         // Set the component name
+         componentSrc.append("Seam.Remoting.type.");
+         componentSrc.append(bean.getName());
+         componentSrc.append(".__name = \"");
+         componentSrc.append(bean.getName());
+         componentSrc.append("\";\n\n");
+
+         // Register the component
+         componentSrc.append("Seam.Component.register(Seam.Remoting.type.");
+         componentSrc.append(bean.getName());
+         componentSrc.append(");\n\n");
+
+         out.write(componentSrc.toString().getBytes());
+      }
+   }
+
+   /**
+    * Append Javascript interface code for a specified class to a block of code.
+    * 
+    * @param source
+    *           StringBuilder The code block to append to
+    * @param type
+    *           Class The type to generate a Javascript interface for
+    * @param types
+    *           Set A list of the types already generated (only include each
+    *           type once).
+    */
+   private void appendTypeSource(OutputStream out, Type type, Set<Type> types)
+         throws IOException
+   {
+      if (type instanceof Class)
+      {
+         Class classType = (Class) type;
+
+         if (classType.isArray())
+         {
+            appendTypeSource(out, classType.getComponentType(), types);
+            return;
+         }
+
+         if (classType.getName().startsWith("java.") || types.contains(type)
+               || classType.isPrimitive())
+            return;
+
+         // Keep track of which types we've already added
+         types.add(type);
+
+         appendClassSource(out, classType, types);
+      } else if (type instanceof ParameterizedType)
+      {
+         for (Type t : ((ParameterizedType) type).getActualTypeArguments())
+            appendTypeSource(out, t, types);
+      }
+   }
+
+   /**
+    * Appends the interface code for a non-component class to an OutputStream.
+    * 
+    * @param out
+    *           OutputStream
+    * @param classType
+    *           Class
+    * @param types
+    *           Set
+    * @throws IOException
+    */
+   private void appendClassSource(OutputStream out, Class classType,
+         Set<Type> types) throws IOException
+   {
+      // Don't generate interfaces for enums
+      if (classType.isEnum())
+         return;
+
+      StringBuilder typeSource = new StringBuilder();
+
+      // Determine whether this class is a component; if so, use its name
+      // otherwise use its class name.
+      Bean bean = beanManager.getBeans(classType).iterator().next();
+      String componentName = bean.getName();  
+      if (componentName == null)
+         componentName = classType.getName();
+
+      String typeName = componentName.replace('.', '$');
+
+      typeSource.append("Seam.Remoting.type.");
+      typeSource.append(typeName);
+      typeSource.append(" = function() {\n");
+
+      StringBuilder fields = new StringBuilder();
+      StringBuilder accessors = new StringBuilder();
+      StringBuilder mutators = new StringBuilder();
+      Map<String, String> metadata = new HashMap<String, String>();
+
+      String getMethodName = null;
+      String setMethodName = null;
+
+      for (String propertyName : getAccessibleProperties(classType))
+      {
+         Type propertyType = null;
+
+         Field f = null;
+         try
+         {
+            f = classType.getDeclaredField(propertyName);
+            propertyType = f.getGenericType();
+         } catch (NoSuchFieldException ex)
+         {
+            setMethodName = String.format("set%s%s", Character
+                  .toUpperCase(propertyName.charAt(0)), propertyName
+                  .substring(1));
+
+            try
+            {
+               getMethodName = String.format("get%s%s", Character
+                     .toUpperCase(propertyName.charAt(0)), propertyName
+                     .substring(1));
+               propertyType = classType.getMethod(getMethodName)
+                     .getGenericReturnType();
+            } catch (NoSuchMethodException ex2)
+            {
+               try
+               {
+                  getMethodName = String.format("is%s%s", Character
+                        .toUpperCase(propertyName.charAt(0)), propertyName
+                        .substring(1));
+
+                  propertyType = classType.getMethod(getMethodName)
+                        .getGenericReturnType();
+               } catch (NoSuchMethodException ex3)
+               {
+                  // ???
+                  continue;
+               }
+            }
+         }
+
+         appendTypeSource(out, propertyType, types);
+
+         // Include types referenced by generic declarations
+         if (propertyType instanceof ParameterizedType)
+         {
+            for (Type t : ((ParameterizedType) propertyType)
+                  .getActualTypeArguments())
+            {
+               if (t instanceof Class)
+                  appendTypeSource(out, t, types);
+            }
+         }
+
+         if (f != null)
+         {
+            String fieldName = propertyName.substring(0, 1).toUpperCase()
+                  + propertyName.substring(1);
+            String getterName = String.format("get%s", fieldName);
+            String setterName = String.format("set%s", fieldName);
+
+            try
+            {
+               classType.getMethod(getterName);
+               getMethodName = getterName;
+            } catch (SecurityException ex)
+            {
+            } catch (NoSuchMethodException ex)
+            {
+               getterName = String.format("is%s", fieldName);
+               try
+               {
+                  if (Modifier.isPublic(classType.getMethod(getterName)
+                        .getModifiers()))
+                     getMethodName = getterName;
+               } catch (NoSuchMethodException ex2)
+               { /* don't care */
+               }
+            }
+
+            try
+            {
+               if (Modifier.isPublic(classType.getMethod(setterName,
+                     f.getType()).getModifiers()))
+                  setMethodName = setterName;
+            } catch (SecurityException ex)
+            {
+            } catch (NoSuchMethodException ex)
+            { /* don't care */
+            }
+         }
+
+         // Construct the list of fields.
+         if (getMethodName != null || setMethodName != null)
+         {
+            metadata.put(propertyName, getFieldType(propertyType));
+
+            fields.append("  this.");
+            fields.append(propertyName);
+            fields.append(" = undefined;\n");
+
+            if (getMethodName != null)
+            {
+               accessors.append("  Seam.Remoting.type.");
+               accessors.append(typeName);
+               accessors.append(".prototype.");
+               accessors.append(getMethodName);
+               accessors.append(" = function() { return this.");
+               accessors.append(propertyName);
+               accessors.append("; }\n");
+            }
+
+            if (setMethodName != null)
+            {
+               mutators.append("  Seam.Remoting.type.");
+               mutators.append(typeName);
+               mutators.append(".prototype.");
+               mutators.append(setMethodName);
+               mutators.append(" = function(");
+               mutators.append(propertyName);
+               mutators.append(") { this.");
+               mutators.append(propertyName);
+               mutators.append(" = ");
+               mutators.append(propertyName);
+               mutators.append("; }\n");
+            }
+         }
+      }
+
+      typeSource.append(fields);
+      typeSource.append(accessors);
+      typeSource.append(mutators);
+
+      typeSource.append("}\n\n");
+
+      // Append the type name
+      typeSource.append("Seam.Remoting.type.");
+      typeSource.append(typeName);
+      typeSource.append(".__name = \"");
+      typeSource.append(componentName);
+      typeSource.append("\";\n");
+
+      // Append the metadata
+      typeSource.append("Seam.Remoting.type.");
+      typeSource.append(typeName);
+      typeSource.append(".__metadata = [\n");
+
+      boolean first = true;
+
+      for (String key : metadata.keySet())
+      {
+         if (!first)
+            typeSource.append(",\n");
+
+         typeSource.append("  {field: \"");
+         typeSource.append(key);
+         typeSource.append("\", type: \"");
+         typeSource.append(metadata.get(key));
+         typeSource.append("\"}");
+
+         first = false;
+      }
+
+      typeSource.append("];\n\n");
+
+      // Register the type under Seam.Component if it is a component, otherwise
+      // register it under Seam.Remoting
+
+      // TODO fix this - a bean might not be named
+      if (classType.isAnnotationPresent(Named.class))
+         typeSource.append("Seam.Component.register(Seam.Remoting.type.");
+      else
+         typeSource.append("Seam.Remoting.registerType(Seam.Remoting.type.");
+
+      typeSource.append(typeName);
+      typeSource.append(");\n\n");
+
+      out.write(typeSource.toString().getBytes());
+   }
+
+   /**
+    * Returns the remoting "type" for a specified class.
+    * 
+    * @param type
+    *           Class
+    * @return String
+    */
+   protected String getFieldType(Type type)
+   {
+      if (type.equals(String.class)
+            || (type instanceof Class && ((Class) type).isEnum())
+            || type.equals(BigInteger.class) || type.equals(BigDecimal.class))
+         return "str";
+      else if (type.equals(Boolean.class) || type.equals(Boolean.TYPE))
+         return "bool";
+      else if (type.equals(Short.class) || type.equals(Short.TYPE)
+            || type.equals(Integer.class) || type.equals(Integer.TYPE)
+            || type.equals(Long.class) || type.equals(Long.TYPE)
+            || type.equals(Float.class) || type.equals(Float.TYPE)
+            || type.equals(Double.class) || type.equals(Double.TYPE)
+            || type.equals(Byte.class) || type.equals(Byte.TYPE))
+         return "number";
+      else if (type instanceof Class)
+      {
+         Class cls = (Class) type;
+         if (Date.class.isAssignableFrom(cls)
+               || Calendar.class.isAssignableFrom(cls))
+            return "date";
+         else if (cls.isArray())
+            return "bag";
+         else if (cls.isAssignableFrom(Map.class))
+            return "map";
+         else if (cls.isAssignableFrom(Collection.class))
+            return "bag";
+      } else if (type instanceof ParameterizedType)
+      {
+         ParameterizedType pt = (ParameterizedType) type;
+
+         if (pt.getRawType() instanceof Class
+               && Map.class.isAssignableFrom((Class) pt.getRawType()))
+            return "map";
+         else if (pt.getRawType() instanceof Class
+               && Collection.class.isAssignableFrom((Class) pt.getRawType()))
+            return "bag";
+      }
+
+      return "bean";
+   }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/MarshalUtils.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/MarshalUtils.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/MarshalUtils.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,82 @@
+package org.jboss.seam.remoting;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.jboss.seam.remoting.wrapper.BeanWrapper;
+import org.jboss.seam.remoting.wrapper.Wrapper;
+
+/**
+ *
+ *
+ * @author Shane Bryzak
+ */
+public class MarshalUtils
+{
+  private static final byte[] RESULT_TAG_OPEN_START = "<result id=\"".getBytes();
+  private static final byte[] RESULT_TAG_OPEN_END = "\">".getBytes();
+  private static final byte[] RESULT_TAG_OPEN = "<result>".getBytes();
+  private static final byte[] RESULT_TAG_CLOSE = "</result>".getBytes();
+
+  private static final byte[] VALUE_TAG_OPEN = "<value>".getBytes();
+  private static final byte[] VALUE_TAG_CLOSE = "</value>".getBytes();
+  
+  private static final byte[] EXCEPTION_TAG_OPEN = "<exception>".getBytes();
+  private static final byte[] EXCEPTION_TAG_CLOSE = "</exception>".getBytes();
+  
+  private static final byte[] MESSAGE_TAG_OPEN = "<message>".getBytes();
+  private static final byte[] MESSAGE_TAG_CLOSE = "</message>".getBytes();
+
+  public static void marshalResult(Call call, OutputStream out)
+      throws IOException
+  {
+    if (call.getId() != null)
+    {
+      out.write(RESULT_TAG_OPEN_START);
+      out.write(call.getId().getBytes());
+      out.write(RESULT_TAG_OPEN_END);
+    }
+    else
+      out.write(RESULT_TAG_OPEN);
+
+    if (call.getException() != null)
+    {
+       out.write(EXCEPTION_TAG_OPEN);
+       out.write(MESSAGE_TAG_OPEN);
+       call.getContext().createWrapperFromObject(call.getException().getMessage(), "").marshal(out);
+       out.write(MESSAGE_TAG_CLOSE);
+       out.write(EXCEPTION_TAG_CLOSE);
+    }
+    else
+    {
+       out.write(VALUE_TAG_OPEN);
+   
+       call.getContext().createWrapperFromObject(call.getResult(), "").marshal(out);
+   
+       out.write(VALUE_TAG_CLOSE);
+   
+       out.write(RequestHandler.REFS_TAG_OPEN);
+   
+       // Using a for-loop, because stuff can get added to outRefs as we recurse the object graph
+       for (int i = 0; i < call.getContext().getOutRefs().size(); i++)
+       {
+         Wrapper wrapper = call.getContext().getOutRefs().get(i);
+   
+         out.write(RequestHandler.REF_TAG_OPEN_START);
+         out.write(Integer.toString(i).getBytes());
+         out.write(RequestHandler.REF_TAG_OPEN_END);
+   
+         if (wrapper instanceof BeanWrapper && call.getConstraints() != null)
+           ((BeanWrapper) wrapper).serialize(out, call.getConstraints());
+         else
+           wrapper.serialize(out);
+   
+         out.write(RequestHandler.REF_TAG_CLOSE);
+       }
+   
+       out.write(RequestHandler.REFS_TAG_CLOSE);
+    }
+    
+    out.write(RESULT_TAG_CLOSE);
+  }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/PollHandler.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/PollHandler.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/PollHandler.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,203 @@
+package org.jboss.seam.remoting;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.enterprise.inject.spi.BeanManager;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.ObjectMessage;
+import javax.jms.TextMessage;
+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.remoting.messaging.PollError;
+import org.jboss.seam.remoting.messaging.PollRequest;
+import org.jboss.seam.remoting.wrapper.Wrapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Handles JMS Message poll requests.
+ * 
+ * @author Shane Bryzak
+ */
+public class PollHandler extends BaseRequestHandler implements RequestHandler
+{
+   private static Logger log = LoggerFactory.getLogger(PollHandler.class);
+
+   private static final byte[] ERRORS_TAG_OPEN_START = "<errors token=\""
+         .getBytes();
+   private static final byte[] ERRORS_TAG_OPEN_END = "\">".getBytes();
+
+   private static final byte[] ERROR_TAG_OPEN_START = "<error code=\""
+         .getBytes();
+   private static final byte[] ERROR_TAG_OPEN_END = "\">".getBytes();
+   private static final byte[] ERROR_TAG_CLOSE = "</error>".getBytes();
+
+   private static final byte[] MESSAGES_TAG_OPEN_START = "<messages token=\""
+         .getBytes();
+   private static final byte[] MESSAGES_TAG_OPEN_END = "\">".getBytes();
+   private static final byte[] MESSAGES_TAG_CLOSE = "</messages>".getBytes();
+
+   private static final byte[] MESSAGE_TAG_OPEN_START = "<message type=\""
+         .getBytes();
+   private static final byte[] MESSAGE_TAG_OPEN_END = "\">".getBytes();
+   private static final byte[] MESSAGE_TAG_CLOSE = "</message>".getBytes();
+
+   private static final byte[] VALUE_TAG_OPEN = "<value>".getBytes();
+   private static final byte[] VALUE_TAG_CLOSE = "</value>".getBytes();
+   
+   public PollHandler(BeanManager beanManager)
+   {
+      super(beanManager);
+   }   
+
+   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());
+      Element env = doc.getRootElement();
+
+      final List<PollRequest> polls = unmarshalRequests(env);
+
+      for (PollRequest req : polls)
+      {
+         req.poll();
+      }
+
+      // Package up the response
+      marshalResponse(polls, response.getOutputStream());
+   }
+
+   private List<PollRequest> unmarshalRequests(Element env) throws Exception
+   {
+      try
+      {
+         List<PollRequest> requests = new ArrayList<PollRequest>();
+
+         List<Element> requestElements = env.element("body").elements("poll");
+         for (Element e : requestElements)
+         {
+            requests.add(new PollRequest(e.attributeValue("token"), Integer
+                  .parseInt(e.attributeValue("timeout"))));
+         }
+
+         return requests;
+      } catch (Exception ex)
+      {
+         log.error("Error unmarshalling subscriptions from request", ex);
+         throw ex;
+      }
+   }
+
+   private void marshalResponse(List<PollRequest> reqs, OutputStream out)
+         throws IOException
+   {
+      out.write(ENVELOPE_TAG_OPEN);
+      out.write(BODY_TAG_OPEN);
+
+      for (PollRequest req : reqs)
+      {
+         if (req.getErrors() != null && req.getErrors().size() > 0)
+         {
+            out.write(ERRORS_TAG_OPEN_START);
+            out.write(req.getToken().getBytes());
+            out.write(ERRORS_TAG_OPEN_END);
+            for (PollError err : req.getErrors())
+            {
+               writeError(err, out);
+            }
+         } else if (req.getMessages() != null && req.getMessages().size() > 0)
+         {
+            out.write(MESSAGES_TAG_OPEN_START);
+            out.write(req.getToken().getBytes());
+            out.write(MESSAGES_TAG_OPEN_END);
+            for (Message m : req.getMessages())
+            {
+               try
+               {
+                  writeMessage(m, out);
+               } catch (JMSException ex)
+               {
+               } catch (IOException ex)
+               {
+               }
+            }
+            out.write(MESSAGES_TAG_CLOSE);
+         }
+      }
+
+      out.write(BODY_TAG_CLOSE);
+      out.write(ENVELOPE_TAG_CLOSE);
+      out.flush();
+   }
+
+   private void writeMessage(Message m, OutputStream out) throws IOException,
+         JMSException
+   {
+      out.write(MESSAGE_TAG_OPEN_START);
+
+      // We need one of these to maintain a list of outbound references
+      CallContext ctx = new CallContext(beanManager);
+      Object value = null;
+
+      if (m instanceof TextMessage)
+      {
+         out.write("text".getBytes());
+         value = ((TextMessage) m).getText();
+      } else if (m instanceof ObjectMessage)
+      {
+         out.write("object".getBytes());
+         value = ((ObjectMessage) m).getObject();
+      }
+
+      out.write(MESSAGE_TAG_OPEN_END);
+
+      out.write(VALUE_TAG_OPEN);
+      ctx.createWrapperFromObject(value, "").marshal(out);
+      out.write(VALUE_TAG_CLOSE);
+
+      out.write(REFS_TAG_OPEN);
+
+      // Using a for-loop, because stuff can get added to outRefs as we recurse
+      // the object graph
+      for (int i = 0; i < ctx.getOutRefs().size(); i++)
+      {
+         Wrapper wrapper = ctx.getOutRefs().get(i);
+
+         out.write(REF_TAG_OPEN_START);
+         out.write(Integer.toString(i).getBytes());
+         out.write(REF_TAG_OPEN_END);
+
+         wrapper.serialize(out);
+
+         out.write(REF_TAG_CLOSE);
+      }
+
+      out.write(REFS_TAG_CLOSE);
+
+      out.write(MESSAGE_TAG_CLOSE);
+   }
+
+   private void writeError(PollError error, OutputStream out)
+         throws IOException
+   {
+      out.write(ERROR_TAG_OPEN_START);
+      out.write(error.getCode().getBytes());
+      out.write(ERROR_TAG_OPEN_END);
+      out.write(error.getMessage().getBytes());
+      out.write(ERROR_TAG_CLOSE);
+   }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/Remoting.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/Remoting.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/Remoting.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,229 @@
+package org.jboss.seam.remoting;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Serves JavaScript implementation of Seam Remoting
+ * 
+ * @author Shane Bryzak
+ * 
+ */
+ at ApplicationScoped
+public class Remoting extends HttpServlet
+{
+   public static final int DEFAULT_POLL_TIMEOUT = 10; // 10 seconds
+   public static final int DEFAULT_POLL_INTERVAL = 1; // 1 second
+
+   private ServletConfig servletConfig;
+
+   private int pollTimeout = DEFAULT_POLL_TIMEOUT;
+
+   private int pollInterval = DEFAULT_POLL_INTERVAL;
+
+   private boolean debug = false;
+
+   /**
+    * We use a Map for this because a Servlet can serve requests for more than
+    * one context path.
+    */
+   private Map<String, byte[]> cachedConfig = new HashMap<String, byte[]>();
+
+   private static final Logger log = LoggerFactory.getLogger(Remoting.class);
+
+   private static final Pattern pathPattern = Pattern.compile("/(.*?)/([^/]+)");
+
+   private static final String REMOTING_RESOURCE_PATH = "resource";
+
+   private synchronized void initConfig(String contextPath,
+         HttpServletRequest request)
+   {
+      if (!cachedConfig.containsKey(contextPath))
+      {
+         StringBuilder sb = new StringBuilder();
+         sb.append("\nSeam.Remoting.resourcePath = \"");
+         sb.append(contextPath);
+         sb.append(request.getServletPath());
+         sb.append(servletConfig.getServletContext().getContextPath());
+         sb.append("\";");
+         sb.append("\nSeam.Remoting.debug = ");
+         sb.append(getDebug() ? "true" : "false");
+         sb.append(";");
+         sb.append("\nSeam.Remoting.pollInterval = ");
+         sb.append(getPollInterval());
+         sb.append(";");
+         sb.append("\nSeam.Remoting.pollTimeout = ");
+         sb.append(getPollTimeout());
+         sb.append(";");
+
+         cachedConfig.put(contextPath, sb.toString().getBytes());
+      }
+   }
+
+   /**
+    * Appends various configuration options to the remoting javascript client
+    * api.
+    * 
+    * @param out
+    *           OutputStream
+    */
+   private void appendConfig(OutputStream out, String contextPath,
+         HttpServletRequest request) throws IOException
+   {
+      if (!cachedConfig.containsKey(contextPath))
+      {
+         initConfig(contextPath, request);
+      }
+
+      out.write(cachedConfig.get(contextPath));
+   }
+
+   /**
+    * 
+    * @param resourceName
+    *           String
+    * @param out
+    *           OutputStream
+    */
+   private void writeResource(String resourceName, HttpServletResponse response)
+         throws IOException
+   {
+      // Only allow resource requests for .js files
+      if (resourceName.endsWith(".js"))
+      {
+         InputStream in = this.getClass().getClassLoader().getResourceAsStream(
+               "org/jboss/seam/remoting/" + resourceName);
+         try
+         {
+            if (in != null)
+            {
+               response.setContentType("text/javascript");
+
+               byte[] buffer = new byte[1024];
+               int read = in.read(buffer);
+               while (read != -1)
+               {
+                  response.getOutputStream().write(buffer, 0, read);
+                  read = in.read(buffer);
+               }
+            } else
+            {
+               log.error(String
+                     .format("Resource [%s] not found.", resourceName));
+            }
+         } finally
+         {
+            if (in != null)
+               in.close();
+         }
+      }
+   }
+
+   public int getPollTimeout()
+   {
+      return pollTimeout;
+   }
+
+   public void setPollTimeout(int pollTimeout)
+   {
+      this.pollTimeout = pollTimeout;
+   }
+
+   public int getPollInterval()
+   {
+      return pollInterval;
+   }
+
+   public void setPollInterval(int pollInterval)
+   {
+      this.pollInterval = pollInterval;
+   }
+
+   public boolean getDebug()
+   {
+      return debug;
+   }
+
+   public void setDebug(boolean debug)
+   {
+      this.debug = debug;
+   }
+
+   public void destroy()
+   {
+      // TODO Auto-generated method stub
+
+   }
+
+   public ServletConfig getServletConfig()
+   {
+      // TODO Auto-generated method stub
+      return null;
+   }
+
+   public String getServletInfo()
+   {
+      // TODO Auto-generated method stub
+      return null;
+   }
+
+   public void init(ServletConfig config) throws ServletException
+   {
+      this.servletConfig = config;
+   }
+
+   public void service(HttpServletRequest request, HttpServletResponse response)
+         throws ServletException, IOException
+   {
+      try
+      {
+         String pathInfo = request.getPathInfo().substring(
+               servletConfig.getServletContext().getContextPath().length());
+
+         RequestHandler handler = RequestHandlerFactory.getInstance()
+               .getRequestHandler(pathInfo);
+         if (handler != null)
+         {
+            handler.setServletContext(getServletContext());
+            handler.handle(request, response);
+         } else
+         {
+            Matcher m = pathPattern.matcher(pathInfo);
+            if (m.matches())
+            {
+               String path = m.group(1);
+               String resource = m.group(2);
+
+               if (REMOTING_RESOURCE_PATH.equals(path))
+               {
+                  writeResource(resource, response);
+                  if ("remote.js".equals(resource))
+                  {
+                     appendConfig(response.getOutputStream(), request
+                           .getContextPath(), request);
+                  }
+               }
+               response.getOutputStream().flush();
+            }
+         }
+      } catch (Exception ex)
+      {
+         log.error("Error", ex);
+      }
+   }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/RequestContext.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/RequestContext.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/RequestContext.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,20 @@
+package org.jboss.seam.remoting;
+
+/**
+ *
+ * @author Shane Bryzak
+ */
+public class RequestContext
+{
+  private String conversationId;
+
+  public String getConversationId()
+  {
+    return conversationId;
+  }
+
+  public void setConversationId(String conversationId)
+  {
+    this.conversationId = conversationId;
+  }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/RequestHandler.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/RequestHandler.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/RequestHandler.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,26 @@
+package org.jboss.seam.remoting;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ *
+ * @author Shane Bryzak
+ */
+public interface RequestHandler
+{
+  static final byte[] ENVELOPE_TAG_OPEN = "<envelope>".getBytes();
+  static final byte[] ENVELOPE_TAG_CLOSE = "</envelope>".getBytes();
+  static final byte[] BODY_TAG_OPEN = "<body>".getBytes();
+  static final byte[] BODY_TAG_CLOSE = "</body>".getBytes();
+  static final byte[] REFS_TAG_OPEN = "<refs>".getBytes();
+  static final byte[] REFS_TAG_CLOSE = "</refs>".getBytes();
+  static final byte[] REF_TAG_OPEN_START = "<ref id=\"".getBytes();
+  static final byte[] REF_TAG_OPEN_END = "\">".getBytes();
+  static final byte[] REF_TAG_CLOSE = "</ref>".getBytes();
+
+  void handle(HttpServletRequest request, HttpServletResponse response)
+      throws Exception;
+  void setServletContext(ServletContext context);
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/RequestHandlerFactory.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/RequestHandlerFactory.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/RequestHandlerFactory.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,54 @@
+package org.jboss.seam.remoting;
+
+import java.util.Map;
+import java.util.HashMap;
+
+import javax.enterprise.inject.spi.BeanManager;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jboss.seam.remoting.messaging.SubscriptionRegistry;
+
+/**
+ * Provides request handlers for different request paths.
+ *
+ * @author Shane Bryzak
+ */
+ at Singleton
+public class RequestHandlerFactory
+{
+  private static final String REQUEST_PATH_EXECUTE = "/execute";
+  private static final String REQUEST_PATH_SUBSCRIPTION = "/subscription";
+  private static final String REQUEST_PATH_POLL = "/poll";
+  private static final String REQUEST_PATH_INTERFACE = "/interface.js";
+
+  private Map<String,RequestHandler> handlers = new HashMap<String,RequestHandler>();
+
+  @Inject
+  private RequestHandlerFactory(BeanManager beanManager)
+  {
+    registerHandler(REQUEST_PATH_EXECUTE, new ExecutionHandler(beanManager));
+    registerHandler(REQUEST_PATH_SUBSCRIPTION, new SubscriptionHandler(beanManager));
+    registerHandler(REQUEST_PATH_INTERFACE, new InterfaceGenerator(beanManager));
+    
+    try
+    {
+       Class.forName("javax.jms.Message");
+       registerHandler(REQUEST_PATH_POLL, new PollHandler(beanManager));
+    }
+    catch (ClassNotFoundException ex) 
+    { 
+        // Don't register PollHandler, swallow the exception
+    }
+  }
+
+  public void registerHandler(String path, RequestHandler handler)
+  {
+    handlers.put(path, handler);
+  }
+
+  public RequestHandler getRequestHandler(String path)
+  {
+    return handlers.get(path);
+  }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/SubscriptionHandler.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/SubscriptionHandler.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/SubscriptionHandler.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,107 @@
+package org.jboss.seam.remoting;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.enterprise.inject.spi.BeanManager;
+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.remoting.messaging.RemoteSubscriber;
+import org.jboss.seam.remoting.messaging.SubscriptionRegistry;
+import org.jboss.seam.remoting.messaging.SubscriptionRequest;
+
+/**
+ * 
+ * @author Shane Bryzak
+ */
+public class SubscriptionHandler extends BaseRequestHandler implements
+      RequestHandler
+{
+   public SubscriptionHandler(BeanManager beanManager)
+   {
+      super(beanManager);
+   }
+
+   /**
+    * The entry point for handling a request.
+    * 
+    * @param request
+    *           HttpServletRequest
+    * @param response
+    *           HttpServletResponse
+    * @throws Exception
+    */
+   public void handle(HttpServletRequest request, 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());
+      Element env = doc.getRootElement();
+
+      Element body = env.element("body");
+
+      // First handle any new subscriptions
+      List<SubscriptionRequest> requests = new ArrayList<SubscriptionRequest>();
+
+      List<Element> elements = body.elements("subscribe");
+      for (Element e : elements)
+      {
+         requests.add(new SubscriptionRequest(e.attributeValue("topic")));
+      }
+
+      for (SubscriptionRequest req : requests)
+      {
+         req.subscribe();
+      }
+
+      // Then handle any unsubscriptions
+      List<String> unsubscribeTokens = new ArrayList<String>();
+
+      elements = body.elements("unsubscribe");
+      for (Element e : elements)
+      {
+         unsubscribeTokens.add(e.attributeValue("token"));
+      }
+
+      for (String token : unsubscribeTokens)
+      {
+         RemoteSubscriber subscriber = SubscriptionRegistry.instance()
+               .getSubscription(token);
+         if (subscriber != null)
+         {
+            subscriber.unsubscribe();
+         }
+      }
+
+      // Package up the response
+      marshalResponse(requests, response.getOutputStream());
+   }
+
+   private void marshalResponse(List<SubscriptionRequest> requests,
+         OutputStream out) throws IOException
+   {
+      out.write(ENVELOPE_TAG_OPEN);
+      out.write(BODY_TAG_OPEN);
+
+      for (SubscriptionRequest req : requests)
+      {
+         req.marshal(out);
+      }
+
+      out.write(BODY_TAG_CLOSE);
+      out.write(ENVELOPE_TAG_CLOSE);
+      out.flush();
+   }
+
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/annotations/WebRemote.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/annotations/WebRemote.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/annotations/WebRemote.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,24 @@
+package org.jboss.seam.remoting.annotations;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that the annotated method is accessible via the remoting framework.
+ *
+ * @author Shane Bryzak
+ */
+ at Target(METHOD)
+ at Documented
+ at Retention(RUNTIME)
+public @interface WebRemote 
+{
+  /**
+   * Specifies a list of paths to exclude from the result's object graph
+   */
+  String[] exclude() default {};
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/client/ParserUtils.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/client/ParserUtils.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/client/ParserUtils.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,40 @@
+package org.jboss.seam.remoting.client;
+
+import org.dom4j.Element;
+import org.jboss.seam.remoting.CallContext;
+import org.jboss.seam.remoting.wrapper.Wrapper;
+import java.util.Iterator;
+
+/**
+ *
+ *
+ * @author Shane Bryzak
+ */
+public class ParserUtils
+{
+
+
+  public static Object unmarshalResult(Element resultElement)
+  {
+    Element valueElement = resultElement.element("value");
+    Element refsElement = resultElement.element("refs");
+
+    CallContext ctx = new CallContext(); //
+
+    Iterator iter = refsElement.elementIterator("ref");
+    while (iter.hasNext())
+    {
+      ctx.createWrapperFromElement((Element) iter.next());
+    }
+
+    Wrapper resultWrapper = ctx.createWrapperFromElement((Element) valueElement.elementIterator().next());
+
+    // Now unmarshal the ref values
+    for (Wrapper w : ctx.getInRefs().values())
+      w.unmarshal();
+
+    return resultWrapper.getValue();
+  }
+
+
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/PollError.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/PollError.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/PollError.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,30 @@
+package org.jboss.seam.remoting.messaging;
+
+/**
+ *
+ * @author Shane Bryzak
+ */
+public class PollError
+{
+  public static final String ERROR_CODE_TOKEN_NOT_FOUND = "TOKEN_NOT_FOUND";
+  public static final String ERROR_CODE_JMS_EXCEPTION = "JMS_EXCEPTION";
+
+  private String code;
+  private String message;
+
+  public PollError(String code, String message)
+  {
+    this.code = code;
+    this.message = message;
+  }
+
+  public String getCode()
+  {
+    return code;
+  }
+
+  public String getMessage()
+  {
+    return message;
+  }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/PollRequest.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/PollRequest.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/PollRequest.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,63 @@
+package org.jboss.seam.remoting.messaging;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+
+/**
+ * Wrapper for a single request for a specified subscription poll.
+ *
+ * @author Shane Bryzak
+ */
+public class PollRequest
+{
+  private String token;
+  private int timeout;
+  private List<Message> messages;
+  private List<PollError> errors = new ArrayList<PollError>();
+  private SubscriptionRegistry registry;
+
+  public PollRequest(String token, int timeout, SubscriptionRegistry registry)
+  {
+    this.token = token;
+    this.timeout = timeout;
+    this.registry = registry;
+  }
+
+  public String getToken()
+  {
+    return token;
+  }
+
+  public List<Message> getMessages()
+  {
+    return messages;
+  }
+
+  public List<PollError> getErrors()
+  {
+    return errors;
+  }
+
+  public void poll()
+  {
+    RemoteSubscriber subscriber = registry.getSubscription(token);
+    if (subscriber != null)
+    {
+      try
+      {
+        messages = subscriber.poll(timeout);
+      }
+      catch (JMSException ex)
+      {
+        errors.add(new PollError(PollError.ERROR_CODE_JMS_EXCEPTION,
+                                 "Error polling for messages"));
+      }
+    }
+    else
+      errors.add(new PollError(PollError.ERROR_CODE_TOKEN_NOT_FOUND,
+                               "No subscription was found for the specified token."));
+  }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/RemoteSubscriber.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/RemoteSubscriber.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/RemoteSubscriber.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,105 @@
+package org.jboss.seam.remoting.messaging;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.Topic;
+import javax.jms.TopicConnection;
+import javax.jms.TopicSession;
+import javax.jms.TopicSubscriber;
+
+/**
+ *
+ * @author Shane Bryzak
+ */
+public class RemoteSubscriber
+{
+  private String token;
+  private String topicName;
+
+  private Topic topic;
+  private TopicSession topicSession;
+  private TopicSubscriber subscriber;
+  private SubscriptionRegistry registry;
+
+  public RemoteSubscriber(String token, String topicName, SubscriptionRegistry registry)
+  {
+    this.token = token;
+    this.topicName = topicName;
+    this.registry = registry;
+  }
+
+  public String getToken()
+  {
+    return token;
+  }
+
+  public String getTopicName()
+  {
+    return topicName;
+  }
+
+  public void subscribe(TopicConnection conn)
+      throws JMSException
+  {
+    topicSession = conn.createTopicSession(false, javax.jms.Session.AUTO_ACKNOWLEDGE);
+    topic = topicSession.createTopic(topicName);
+    subscriber = topicSession.createSubscriber(topic);
+  }
+
+  public void unsubscribe()
+  {
+    try {
+      subscriber.close();
+
+      // Remove the subscription's token from the user's session context
+      registry.getUserTokens().remove(token);
+    }
+    catch (JMSException ex) { }
+
+    try {
+      topicSession.close();
+    }
+    catch (JMSException ex) { }
+  }
+
+  public void setTopicSubscriber(TopicSubscriber subscriber)
+  {
+    this.subscriber = subscriber;
+  }
+
+  public TopicSubscriber getTopicSubscriber()
+  {
+    return subscriber;
+  }
+
+  public List<Message> poll(int timeout)
+      throws JMSException
+  {
+    List<Message> messages = null;
+
+    Message m = null;
+
+    synchronized(subscriber)
+    {
+      do {
+          // Only timeout for the first message.. subsequent messages should be nowait
+        if (messages == null && timeout > 0)
+          m = subscriber.receive(timeout * 1000);
+        else
+          m = subscriber.receiveNoWait();
+
+        if (m != null) {
+          if (messages == null)
+            messages = new ArrayList<Message> ();
+          messages.add(m);
+        }
+      }
+      while (m != null);
+    }
+
+    return messages;
+  }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/SubscriptionRegistry.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/SubscriptionRegistry.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/SubscriptionRegistry.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,169 @@
+package org.jboss.seam.remoting.messaging;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Instance;
+import javax.inject.Inject;
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import javax.jms.TopicConnection;
+
+/**
+ *
+ * @author Shane Bryzak
+ */
+ at ApplicationScoped
+public class SubscriptionRegistry
+{
+  private String connectionProvider;
+
+  private Object monitor = new Object();
+
+  private Map<String,RemoteSubscriber> subscriptions = new HashMap<String,RemoteSubscriber>();
+   
+  @Inject Instance<UserTokens> userTokenInstance;
+  @Inject Instance<TopicConnection> topicConnectionInstance;
+  
+  private volatile TopicConnection topicConnection;
+  
+  /**
+   * Contains a list of all the topics that clients are allowed to subscribe to.
+   */
+  private Set<String> allowedTopics = new HashSet<String>();
+
+  public Set<String> getAllowedTopics()
+  {
+    return allowedTopics;
+  }
+
+  public void setAllowedTopics(Set<String> allowedTopics)
+  {
+    this.allowedTopics = allowedTopics;
+  }
+
+  public String getConnectionProvider()
+  {
+    return connectionProvider;
+  }
+
+  public void setConnectionProvider(String connectionProvider)
+  {
+    this.connectionProvider = connectionProvider;
+  }
+
+  private TopicConnection getTopicConnection()
+    throws Exception
+  {
+    if (topicConnection == null)
+    {
+      synchronized(monitor)
+      {
+        if (topicConnection == null)
+        {
+            topicConnection = topicConnectionInstance.get();
+            
+            topicConnection.setExceptionListener(new ExceptionListener() {
+              public void onException(JMSException ex)
+              {
+                // swallow the exception for now - do we need to try and reconnect???
+              }
+            });
+            topicConnection.start();
+        }
+      }
+    }
+    return topicConnection;
+  }
+
+  public RemoteSubscriber subscribe(String topicName)
+  {
+    if (!allowedTopics.contains(topicName)) {
+      throw new IllegalArgumentException(String.format(
+        "Cannot subscribe to a topic that is not allowed. Topic [%s] is not an " +
+        "allowed topic.", topicName));
+    }
+
+    RemoteSubscriber sub = new RemoteSubscriber(UUID.randomUUID().toString(), 
+          topicName, this);
+
+    try {
+      subscribe(sub);
+      subscriptions.put(sub.getToken(), sub);
+
+      // Save the client's token in their session context
+      getUserTokens().add(sub.getToken());
+
+      return sub;
+    } catch (Exception ex) {
+      // TODO should log this
+      return null;
+    }
+  }
+
+  private void subscribe(RemoteSubscriber sub) 
+      throws JMSException, Exception
+  {
+     try {
+        sub.subscribe(getTopicConnection()); 
+     } catch (Exception e) {
+        // Clear the topic connection and try again.         
+        resetTopic(); 
+        sub.subscribe(getTopicConnection()); 
+     }
+  }
+
+  private void resetTopic()
+  {
+     TopicConnection savedTopic = null;
+     
+     synchronized(monitor) {
+        if (topicConnection != null) { 
+           savedTopic = topicConnection;
+           topicConnection = null;
+        }
+     }
+     
+     if (savedTopic != null) {
+        try { 
+           savedTopic.close(); 
+        } catch (Exception ignored) { }     
+     }     
+  }
+
+  public UserTokens getUserTokens()
+  {
+     return userTokenInstance.get();
+  }
+
+  public RemoteSubscriber getSubscription(String token)
+  {
+    if (!getUserTokens().contains(token)) {
+      throw new IllegalArgumentException("Invalid token argument - token not found in Session Context.");
+    }
+    
+    return subscriptions.get(token);
+  }
+  
+  public Set<String> getAllTokens() {
+      return subscriptions.keySet();
+  }
+
+  public void cleanupTokens(Set<String> tokens)
+  {
+       for (String token: tokens) {
+          RemoteSubscriber subscriber = subscriptions.remove(token);
+          if (subscriber!=null) {
+             try {
+                 subscriber.unsubscribe();
+             } catch (Exception e) {
+                //log.debug("problem cleaning up subcription", e);
+             }
+          }          
+       }
+  }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/SubscriptionRequest.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/SubscriptionRequest.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/SubscriptionRequest.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,37 @@
+package org.jboss.seam.remoting.messaging;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ *
+ * @author Shane Bryzak
+ */
+public class SubscriptionRequest
+{
+  private String topicName;
+  private RemoteSubscriber subscriber;
+  private SubscriptionRegistry registry;
+
+  public SubscriptionRequest(String topicName, SubscriptionRegistry registry)
+  {
+    this.topicName = topicName;
+    this.registry = registry;
+  }
+
+  public void subscribe()
+  {
+    subscriber = registry.subscribe(topicName);
+  }
+
+  public void marshal(OutputStream out)
+      throws IOException
+  {
+    out.write("<subscription topic=\"".getBytes());
+    out.write(topicName.getBytes());
+    out.write("\" token=\"".getBytes());
+    out.write(subscriber.getToken().getBytes());
+    out.write("\"/>".getBytes());
+    out.flush();
+  }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/UserTokens.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/UserTokens.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/messaging/UserTokens.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,34 @@
+package org.jboss.seam.remoting.messaging;
+
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.annotation.PreDestroy;
+import javax.enterprise.context.SessionScoped;
+import javax.inject.Inject;
+
+ at SessionScoped
+public class UserTokens implements Serializable
+{
+   Set<String> tokens = new HashSet<String>();
+   
+   @Inject SubscriptionRegistry registry;
+   
+   public void add(String token) {
+      tokens.add(token);
+   }
+   
+   public boolean contains(String token) {
+      return tokens.contains(token); 
+   }
+   
+   public void remove(String token) {
+      tokens.remove(token);
+   }
+   
+   @PreDestroy 
+   public void cleanUp() {
+       registry.cleanupTokens(tokens);
+   }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/BagWrapper.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/BagWrapper.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/BagWrapper.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,174 @@
+package org.jboss.seam.remoting.wrapper;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Array;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+
+import javax.enterprise.inject.spi.BeanManager;
+
+import org.dom4j.Element;
+import org.hibernate.collection.PersistentCollection;
+
+/**
+ * Wrapper for collections, arrays, etc.
+ * 
+ * @author Shane Bryzak
+ */
+public class BagWrapper extends BaseWrapper implements Wrapper
+{
+   public BagWrapper(BeanManager beanManager)
+   {
+      super(beanManager);
+   }
+
+   private static final byte[] BAG_TAG_OPEN = "<bag>".getBytes();
+   private static final byte[] BAG_TAG_CLOSE = "</bag>".getBytes();
+
+   private static final byte[] ELEMENT_TAG_OPEN = "<element>".getBytes();
+   private static final byte[] ELEMENT_TAG_CLOSE = "</element>".getBytes();
+
+   public void marshal(OutputStream out) throws IOException
+   {
+      out.write(BAG_TAG_OPEN);
+
+      // Fix to prevent uninitialized lazy loading in Hibernate
+      if (value instanceof PersistentCollection)
+      {
+         if (!((PersistentCollection) value).wasInitialized())
+         {
+            out.write(BAG_TAG_CLOSE);
+            return;
+         }
+      }
+
+      Collection vals = null;
+
+      // If the value is an array, convert it to a Collection
+      if (value.getClass().isArray())
+      {
+         vals = new ArrayList();
+         for (int i = 0; i < Array.getLength(value); i++)
+            vals.add(Array.get(value, i));
+      } else if (Collection.class.isAssignableFrom(value.getClass()))
+         vals = (Collection) value;
+      else
+         throw new RuntimeException(String.format(
+               "Can not marshal object as bag: [%s]", value));
+
+      for (Object val : vals)
+      {
+         out.write(ELEMENT_TAG_OPEN);
+         context.createWrapperFromObject(val, path).marshal(out);
+         out.write(ELEMENT_TAG_CLOSE);
+      }
+
+      out.write(BAG_TAG_CLOSE);
+   }
+
+   @SuppressWarnings("unchecked")
+   public Object convert(Type type) throws ConversionException
+   {
+      // First convert the elements in the bag to a List of Wrappers
+      List<Wrapper> vals = new ArrayList<Wrapper>();
+
+      for (Element e : (List<Element>) element.elements("element"))
+         vals.add(context.createWrapperFromElement((Element) e.elements()
+               .get(0)));
+
+      if (type instanceof Class && ((Class) type).isArray())
+      {
+         Class arrayType = ((Class) type).getComponentType();
+         value = Array.newInstance(arrayType, vals.size()); // Fix this
+         for (int i = 0; i < vals.size(); i++)
+            Array.set(value, i, vals.get(i).convert(arrayType));
+      } else if (type instanceof Class
+            && Collection.class.isAssignableFrom((Class) type))
+      {
+         try
+         {
+            value = getConcreteClass((Class) type).newInstance();
+         } catch (Exception ex)
+         {
+            throw new ConversionException(String.format(
+                  "Could not create instance of target type [%s].", type));
+         }
+         for (Wrapper w : vals)
+            ((Collection) value).add(w.convert(Object.class));
+      } else if (type instanceof ParameterizedType
+            && Collection.class
+                  .isAssignableFrom((Class) ((ParameterizedType) type)
+                        .getRawType()))
+      {
+         Class rawType = (Class) ((ParameterizedType) type).getRawType();
+         Type genType = Object.class;
+
+         for (Type t : ((ParameterizedType) type).getActualTypeArguments())
+         {
+            genType = t;
+            break;
+         }
+
+         try
+         {
+            value = getConcreteClass(rawType).newInstance();
+         } catch (Exception ex)
+         {
+            throw new ConversionException(String.format(
+                  "Could not create instance of target type [%s].", rawType));
+         }
+
+         for (Wrapper w : vals)
+            ((Collection) value).add(w.convert(genType));
+      }
+
+      return value;
+   }
+
+   private Class getConcreteClass(Class c)
+   {
+      if (c.isInterface())
+      {
+         // Support Set, Queue and (by default, and as a last resort) List
+         if (Set.class.isAssignableFrom(c))
+            return HashSet.class;
+         else if (Queue.class.isAssignableFrom(c))
+            return LinkedList.class;
+         else
+            return ArrayList.class;
+      } else
+         return c;
+   }
+
+   /**
+    * 
+    * @param cls
+    *           Class
+    * @return ConversionScore
+    */
+   public ConversionScore conversionScore(Class cls)
+   {
+      // There's no such thing as an exact match for a bag, so we'll just look
+      // for
+      // a compatible match
+
+      if (cls.isArray())
+         return ConversionScore.compatible;
+
+      if (cls.equals(Object.class))
+         return ConversionScore.compatible;
+
+      if (Collection.class.isAssignableFrom(cls))
+         return ConversionScore.compatible;
+
+      return ConversionScore.nomatch;
+   }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/BaseWrapper.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/BaseWrapper.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/BaseWrapper.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,113 @@
+package org.jboss.seam.remoting.wrapper;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.enterprise.inject.spi.BeanManager;
+
+import org.dom4j.Element;
+import org.jboss.seam.remoting.CallContext;
+
+/**
+ * Base class for all Wrapper implementations.
+ * 
+ * @author Shane Bryzak
+ */
+public abstract class BaseWrapper implements Wrapper
+{
+   protected BeanManager beanManager;
+
+   public BaseWrapper(BeanManager beanManager)
+   {
+      this.beanManager = beanManager;
+   }
+
+   /**
+    * The path of this object within the result object graph
+    */
+   protected String path;
+
+   /**
+    * The call context
+    */
+   protected CallContext context;
+
+   /**
+    * The DOM4J element containing the value
+    */
+   protected Element element;
+
+   /**
+    * The wrapped value
+    */
+   protected Object value;
+
+   /**
+    * Sets the path.
+    * 
+    * @param path
+    *           String
+    */
+   public void setPath(String path)
+   {
+      this.path = path;
+   }
+
+   /**
+    * Sets the wrapped value
+    * 
+    * @param value
+    *           Object
+    */
+   public void setValue(Object value)
+   {
+      this.value = value;
+   }
+
+   /**
+    * Returns the wrapped value
+    * 
+    * @return Object
+    */
+   public Object getValue()
+   {
+      return value;
+   }
+
+   /**
+    * Sets the call context
+    */
+   public void setCallContext(CallContext context)
+   {
+      this.context = context;
+   }
+
+   /**
+    * Extracts a value from a DOM4J Element
+    * 
+    * @param element
+    *           Element
+    */
+   public void setElement(Element element)
+   {
+      this.element = element;
+   }
+
+   /**
+    * Default implementation does nothing
+    */
+   public void unmarshal()
+   {
+   }
+
+   /**
+    * Default implementation does nothing
+    * 
+    * @param out
+    *           OutputStream
+    * @throws IOException
+    */
+   public void serialize(OutputStream out) throws IOException
+   {
+   }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/BeanWrapper.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/BeanWrapper.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/BeanWrapper.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,327 @@
+package org.jboss.seam.remoting.wrapper;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.List;
+
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+
+import org.dom4j.Element;
+import org.jboss.seam.remoting.InterfaceGenerator;
+
+/**
+ * @author Shane Bryzak
+ */
+public class BeanWrapper extends BaseWrapper implements Wrapper
+{
+   private static final byte[] REF_START_TAG_OPEN = "<ref id=\"".getBytes();
+   private static final byte[] REF_START_TAG_END = "\"/>".getBytes();
+
+   private static final byte[] BEAN_START_TAG_OPEN = "<bean type=\"".getBytes();
+   private static final byte[] BEAN_START_TAG_CLOSE = "\">".getBytes();
+   private static final byte[] BEAN_CLOSE_TAG = "</bean>".getBytes();
+
+   private static final byte[] MEMBER_START_TAG_OPEN = "<member name=\""
+         .getBytes();
+   private static final byte[] MEMBER_START_TAG_CLOSE = "\">".getBytes();
+   private static final byte[] MEMBER_CLOSE_TAG = "</member>".getBytes();
+
+   public BeanWrapper(BeanManager beanManager)
+   {
+      super(beanManager);
+   }
+
+   @Override
+   public void setElement(Element element)
+   {
+      super.setElement(element);
+
+      String beanType = element.attributeValue("type");
+
+      // TODO do it this way, it might not be a managed bean...
+      Bean bean = beanManager.getBeans(beanType).iterator().next();
+
+      if (bean != null)
+      {
+         value = bean.create(beanManager.createCreationalContext(bean));
+      } else
+      {
+         try
+         {
+            value = Class.forName(beanType).newInstance();
+         } catch (Exception ex)
+         {
+            throw new RuntimeException("Could not unmarshal bean element: "
+                  + element.getText(), ex);
+         }
+      }
+   }
+
+   @Override
+   public void unmarshal()
+   {
+      List members = element.elements("member");
+
+      for (Element member : (List<Element>) members)
+      {
+         String name = member.attributeValue("name");
+
+         Wrapper w = context.createWrapperFromElement((Element) member
+               .elementIterator().next());
+
+         Class cls = value.getClass();
+
+         // We're going to try a combination of ways to set the property value
+         // here
+         Method method = null;
+         Field field = null;
+
+         // First try to find the best matching method
+         String setter = "set" + Character.toUpperCase(name.charAt(0))
+               + name.substring(1);
+
+         ConversionScore score = ConversionScore.nomatch;
+         for (Method m : cls.getMethods())
+         {
+            if (setter.equals(m.getName()) && m.getParameterTypes().length == 1)
+            {
+               ConversionScore s = w.conversionScore(m.getParameterTypes()[0]);
+               if (s.getScore() > score.getScore())
+               {
+                  method = m;
+                  score = s;
+               }
+            }
+         }
+
+         // If we can't find a method, look for a matching field name
+         if (method == null)
+         {
+            while (field == null && !cls.equals(Object.class))
+            {
+               try
+               {
+                  // First check the declared fields
+                  field = cls.getDeclaredField(name);
+               } catch (NoSuchFieldException ex)
+               {
+                  // Couldn't find the field.. try the superclass
+                  cls = cls.getSuperclass();
+               }
+            }
+
+            if (field == null)
+            {
+               throw new RuntimeException(
+                     String
+                           .format(
+                                 "Error while unmarshalling - property [%s] not found in class [%s]",
+                                 name, value.getClass().getName()));
+            }
+         }
+
+         // Now convert the field value to the correct target type
+         Object fieldValue = null;
+         try
+         {
+            fieldValue = w.convert(method != null ? method
+                  .getGenericParameterTypes()[0] : field.getGenericType());
+         } catch (ConversionException ex)
+         {
+            throw new RuntimeException(
+                  "Could not convert value while unmarshaling", ex);
+         }
+
+         // If we have a setter method, invoke it
+         if (method != null)
+         {
+            try
+            {
+               method.invoke(value, fieldValue);
+            } catch (Exception e)
+            {
+               throw new RuntimeException(String.format(
+                     "Could not invoke setter method [%s]", method.getName()));
+            }
+         } else
+         {
+            // Otherwise try to set the field value directly
+            boolean accessible = field.isAccessible();
+            try
+            {
+               if (!accessible)
+                  field.setAccessible(true);
+               field.set(value, fieldValue);
+            } catch (Exception ex)
+            {
+               throw new RuntimeException("Could not set field value.", ex);
+            } finally
+            {
+               field.setAccessible(accessible);
+            }
+         }
+      }
+   }
+
+   public Object convert(Type type) throws ConversionException
+   {
+      if (type instanceof Class
+            && ((Class) type).isAssignableFrom(value.getClass()))
+         return value;
+      else
+         throw new ConversionException(String.format(
+               "Value [%s] cannot be converted to type [%s].", value, type));
+   }
+
+   public void marshal(OutputStream out) throws IOException
+   {
+      context.addOutRef(this);
+
+      out.write(REF_START_TAG_OPEN);
+      out
+            .write(Integer.toString(context.getOutRefs().indexOf(this))
+                  .getBytes());
+      out.write(REF_START_TAG_END);
+   }
+
+   @Override
+   public void serialize(OutputStream out) throws IOException
+   {
+      serialize(out, null);
+   }
+
+   public void serialize(OutputStream out, List<String> constraints)
+         throws IOException
+   {
+      out.write(BEAN_START_TAG_OPEN);
+
+      Class cls = value.getClass();
+
+      /**
+       * @todo This is a hack to get the "real" class - find out if there is an
+       *       API method in CGLIB that can be used instead
+       */
+      if (cls.getName().contains("EnhancerByCGLIB"))
+      {
+         cls = cls.getSuperclass();
+      }
+
+      if (cls.getName().contains("_$$_javassist_"))
+      {
+         cls = cls.getSuperclass();
+      }
+
+      // TODO fix this, bean might not have a name
+      Bean bean = beanManager.getBeans(cls).iterator().next();      
+      String componentName = bean.getName();
+
+      if (componentName != null)
+         out.write(componentName.getBytes());
+      else
+         out.write(cls.getName().getBytes());
+
+      out.write(BEAN_START_TAG_CLOSE);
+
+      for (String propertyName : InterfaceGenerator
+            .getAccessibleProperties(cls))
+      {
+         String fieldPath = path != null && path.length() > 0 ? String.format(
+               "%s.%s", path, propertyName) : propertyName;
+
+         // Also exclude fields listed using wildcard notation:
+         // [componentName].fieldName
+         String wildCard = String.format("[%s].%s",
+               componentName != null ? componentName : cls.getName(),
+               propertyName);
+
+         if (constraints == null
+               || (!constraints.contains(fieldPath) && !constraints
+                     .contains(wildCard)))
+         {
+            out.write(MEMBER_START_TAG_OPEN);
+            out.write(propertyName.getBytes());
+            out.write(MEMBER_START_TAG_CLOSE);
+
+            Field f = null;
+            try
+            {
+               f = cls.getField(propertyName);
+            } catch (NoSuchFieldException ex)
+            {
+            }
+
+            boolean accessible = false;
+            try
+            {
+               // Temporarily set the field's accessibility so we can read it
+               if (f != null)
+               {
+                  accessible = f.isAccessible();
+                  f.setAccessible(true);
+                  context.createWrapperFromObject(f.get(value), fieldPath)
+                        .marshal(out);
+               } else
+               {
+                  Method accessor = null;
+                  try
+                  {
+                     accessor = cls.getMethod(String.format("get%s%s",
+                           Character.toUpperCase(propertyName.charAt(0)),
+                           propertyName.substring(1)));
+                  } catch (NoSuchMethodException ex)
+                  {
+                     try
+                     {
+                        accessor = cls.getMethod(String.format("is%s%s",
+                              Character.toUpperCase(propertyName.charAt(0)),
+                              propertyName.substring(1)));
+                     } catch (NoSuchMethodException ex2)
+                     {
+                        // uh oh... continue with the next one
+                        continue;
+                     }
+                  }
+
+                  try
+                  {
+                     context.createWrapperFromObject(accessor.invoke(value),
+                           fieldPath).marshal(out);
+                  } catch (InvocationTargetException ex)
+                  {
+                     throw new RuntimeException(String.format(
+                           "Failed to read property [%s] for object [%s]",
+                           propertyName, value));
+                  }
+               }
+            } catch (IllegalAccessException ex)
+            {
+               throw new RuntimeException("Error reading value from field.");
+            } finally
+            {
+               if (f != null)
+                  f.setAccessible(accessible);
+            }
+
+            out.write(MEMBER_CLOSE_TAG);
+         }
+      }
+
+      out.write(BEAN_CLOSE_TAG);
+   }
+
+   public ConversionScore conversionScore(Class cls)
+   {
+      if (cls.equals(value.getClass()))
+         return ConversionScore.exact;
+      else if (cls.isAssignableFrom(value.getClass())
+            || cls.equals(Object.class))
+         return ConversionScore.compatible;
+      else
+         return ConversionScore.nomatch;
+   }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/BooleanWrapper.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/BooleanWrapper.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/BooleanWrapper.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,53 @@
+package org.jboss.seam.remoting.wrapper;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Type;
+
+import javax.enterprise.inject.spi.BeanManager;
+
+/**
+ * @author Shane Bryzak
+ */
+public class BooleanWrapper extends BaseWrapper implements Wrapper
+{
+   public BooleanWrapper(BeanManager beanManager)
+   {
+      super(beanManager);
+      // TODO Auto-generated constructor stub
+   }
+
+   private static final byte[] BOOL_TAG_OPEN = "<bool>".getBytes();
+   private static final byte[] BOOL_TAG_CLOSE = "</bool>".getBytes();
+
+   public void marshal(OutputStream out) throws IOException
+   {
+      out.write(BOOL_TAG_OPEN);
+      out.write(((Boolean) value).toString().getBytes());
+      out.write(BOOL_TAG_CLOSE);
+   }
+
+   public Object convert(Type type) throws ConversionException
+   {
+      if (type.equals(Boolean.class) || type.equals(Object.class))
+         value = Boolean.valueOf(element.getStringValue());
+      else if (type.equals(Boolean.TYPE))
+         value = Boolean.parseBoolean(element.getStringValue());
+      else
+         throw new ConversionException(String.format(
+               "Parameter [%s] cannot be converted to type [%s].", element
+                     .getStringValue(), type));
+
+      return value;
+   }
+
+   public ConversionScore conversionScore(Class cls)
+   {
+      if (cls.equals(Boolean.class) || cls.equals(Boolean.TYPE))
+         return ConversionScore.exact;
+      else if (cls.equals(Object.class))
+         return ConversionScore.compatible;
+      else
+         return ConversionScore.nomatch;
+   }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/ConversionException.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/ConversionException.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/ConversionException.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,21 @@
+package org.jboss.seam.remoting.wrapper;
+
+/**
+ * Thrown for an invalid conversion.
+ * 
+ * @author Shane Bryzak
+ */
+public class ConversionException extends Exception
+{
+   private static final long serialVersionUID = 5584559762846984501L;
+
+   public ConversionException(String message)
+   {
+      super(message);
+   }
+
+   public ConversionException(String message, Throwable cause)
+   {
+      super(message, cause);
+   }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/ConversionScore.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/ConversionScore.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/ConversionScore.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,24 @@
+package org.jboss.seam.remoting.wrapper;
+
+/**
+ *
+ * @author Shane Bryzak
+ */
+public enum ConversionScore
+{
+  nomatch(0),
+  compatible(1),
+  exact(2);
+
+  private int score;
+
+  ConversionScore(int score)
+  {
+    this.score = score;
+  }
+
+  public int getScore()
+  {
+    return score;
+  }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/DateWrapper.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/DateWrapper.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/DateWrapper.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,103 @@
+package org.jboss.seam.remoting.wrapper;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Type;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+import javax.enterprise.inject.spi.BeanManager;
+
+/**
+ * Handles date conversions
+ * 
+ * @author Shane Bryzak
+ */
+public class DateWrapper extends BaseWrapper implements Wrapper
+{
+   public DateWrapper(BeanManager beanManager)
+   {
+      super(beanManager);
+      // TODO Auto-generated constructor stub
+   }
+
+   private static final byte[] DATE_TAG_OPEN = "<date>".getBytes();
+   private static final byte[] DATE_TAG_CLOSE = "</date>".getBytes();
+
+   private static final String DATE_FORMAT = "yyyyMMddHHmmssSSS";
+
+   private DateFormat getDateFormat()
+   {
+      return new SimpleDateFormat(DATE_FORMAT);
+   }
+
+   public void marshal(OutputStream out) throws IOException
+   {
+      out.write(DATE_TAG_OPEN);
+      if (Date.class.isAssignableFrom(value.getClass()))
+      {
+         out.write(getDateFormat().format(value).getBytes());
+      } else if (Calendar.class.isAssignableFrom(value.getClass()))
+      {
+         out.write(getDateFormat().format(((Calendar) value).getTime())
+               .getBytes());
+      }
+      out.write(DATE_TAG_CLOSE);
+   }
+
+   public Object convert(Type type) throws ConversionException
+   {
+      if ((type instanceof Class && Date.class.isAssignableFrom((Class) type))
+            || type.equals(Object.class))
+      {
+         try
+         {
+            value = getDateFormat().parse(element.getStringValue());
+         } catch (ParseException ex)
+         {
+            throw new ConversionException(String.format(
+                  "Date value [%s] is not in a valid format.", element
+                        .getStringValue()));
+         }
+      } else if ((type instanceof Class && Calendar.class
+            .isAssignableFrom((Class) type)))
+      {
+         try
+         {
+            Calendar cal = Calendar.getInstance();
+            cal.setTime(getDateFormat().parse(element.getStringValue()));
+            value = cal;
+         } catch (ParseException ex)
+         {
+            throw new ConversionException(String.format(
+                  "Date value [%s] is not in a valid format.", element
+                        .getStringValue()));
+         }
+      } else
+      {
+         throw new ConversionException(String.format(
+               "Value [%s] cannot be converted to type [%s].", element
+                     .getStringValue(), type));
+      }
+
+      return value;
+   }
+
+   public ConversionScore conversionScore(Class cls)
+   {
+      if (Date.class.isAssignableFrom(cls)
+            || Calendar.class.isAssignableFrom(cls))
+      {
+         return ConversionScore.exact;
+      } else if (cls.equals(Object.class))
+      {
+         return ConversionScore.compatible;
+      } else
+      {
+         return ConversionScore.nomatch;
+      }
+   }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/MapWrapper.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/MapWrapper.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/MapWrapper.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,153 @@
+package org.jboss.seam.remoting.wrapper;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.enterprise.inject.spi.BeanManager;
+
+import org.dom4j.Element;
+
+/**
+ * @author Shane Bryzak
+ */
+public class MapWrapper extends BaseWrapper implements Wrapper
+{
+   public MapWrapper(BeanManager beanManager)
+   {
+      super(beanManager);
+   }
+
+   private static final byte[] MAP_TAG_OPEN = "<map>".getBytes();
+   private static final byte[] MAP_TAG_CLOSE = "</map>".getBytes();
+
+   private static final byte[] ELEMENT_TAG_OPEN = "<element>".getBytes();
+   private static final byte[] ELEMENT_TAG_CLOSE = "</element>".getBytes();
+
+   private static final byte[] KEY_TAG_OPEN = "<k>".getBytes();
+   private static final byte[] KEY_TAG_CLOSE = "</k>".getBytes();
+
+   private static final byte[] VALUE_TAG_OPEN = "<v>".getBytes();
+   private static final byte[] VALUE_TAG_CLOSE = "</v>".getBytes();
+
+   public void marshal(OutputStream out) throws IOException
+   {
+      out.write(MAP_TAG_OPEN);
+
+      Map m = (Map) this.value;
+
+      for (Object key : m.keySet())
+      {
+         out.write(ELEMENT_TAG_OPEN);
+
+         out.write(KEY_TAG_OPEN);
+         context.createWrapperFromObject(key, String.format("%s[key]", path))
+               .marshal(out);
+         out.write(KEY_TAG_CLOSE);
+
+         out.write(VALUE_TAG_OPEN);
+         context.createWrapperFromObject(m.get(key),
+               String.format("%s[value]", path)).marshal(out);
+         out.write(VALUE_TAG_CLOSE);
+
+         out.write(ELEMENT_TAG_CLOSE);
+      }
+
+      out.write(MAP_TAG_CLOSE);
+   }
+
+   public Object convert(Type type) throws ConversionException
+   {
+      if (context == null)
+      {
+         throw new IllegalStateException("No call context has been set");
+      }
+
+      Class typeClass = null;
+      Type keyType = null;
+      Type valueType = null;
+
+      // Either the type should be a generified Map
+      if (type instanceof ParameterizedType
+            && Map.class.isAssignableFrom((Class) ((ParameterizedType) type)
+                  .getRawType()))
+      {
+         typeClass = (Class) ((ParameterizedType) type).getRawType();
+
+         for (Type t : ((ParameterizedType) type).getActualTypeArguments())
+         {
+            if (keyType == null)
+               keyType = t;
+            else
+            {
+               valueType = t;
+               break;
+            }
+         }
+      }
+      // Or a non-generified Map
+      else if (type instanceof Class
+            && Map.class.isAssignableFrom((Class) type))
+      {
+         if (!((Class) type).isInterface())
+            typeClass = (Class) type;
+         keyType = Object.class;
+         valueType = Object.class;
+      }
+      // If it's neither, throw an exception
+      else
+         throw new ConversionException(String.format(
+               "Cannot convert value to type [%s]", type));
+
+      // If we don't have a concrete type, default to creating a HashMap
+      if (typeClass == null || typeClass.isInterface())
+         value = new HashMap();
+      else
+      {
+         try
+         {
+            // Otherwise create an instance of the concrete type
+            if (type instanceof Class)
+            {
+               value = ((Class) type).newInstance();
+            } else if (type instanceof ParameterizedType)
+            {
+               value = ((Class) ((ParameterizedType) type).getRawType())
+                     .newInstance();
+            }
+         } catch (Exception ex)
+         {
+            throw new ConversionException(String.format(
+                  "Could not create value of type [%s]", type));
+         }
+      }
+
+      for (Element e : (List<Element>) element.elements("element"))
+      {
+         Element keyElement = (Element) e.element("k").elementIterator().next();
+         Element valueElement = (Element) e.element("v").elementIterator()
+               .next();
+
+         ((Map) value).put(context.createWrapperFromElement(keyElement)
+               .convert(keyType), context
+               .createWrapperFromElement(valueElement).convert(valueType));
+      }
+
+      return value;
+   }
+
+   public ConversionScore conversionScore(Class cls)
+   {
+      if (Map.class.isAssignableFrom(cls))
+         return ConversionScore.exact;
+
+      if (cls.equals(Object.class))
+         return ConversionScore.compatible;
+
+      return ConversionScore.nomatch;
+   }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/NullWrapper.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/NullWrapper.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/NullWrapper.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,35 @@
+package org.jboss.seam.remoting.wrapper;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Type;
+
+import javax.enterprise.inject.spi.BeanManager;
+
+/**
+ * @author Shane Bryzak
+ */
+public class NullWrapper extends BaseWrapper implements Wrapper
+{
+   public NullWrapper(BeanManager beanManager)
+   {
+      super(beanManager);
+   }
+
+   private static final byte[] NULL_WRAPPER_TAG = "<null/>".getBytes();
+
+   public void marshal(OutputStream out) throws IOException
+   {
+      out.write(NULL_WRAPPER_TAG);
+   }
+
+   public Object convert(Type type) throws ConversionException
+   {
+      return null;
+   }
+
+   public ConversionScore conversionScore(Class cls)
+   {
+      return ConversionScore.compatible;
+   }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/NumberWrapper.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/NumberWrapper.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/NumberWrapper.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,116 @@
+package org.jboss.seam.remoting.wrapper;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Type;
+
+import javax.enterprise.inject.spi.BeanManager;
+
+/**
+ * Int wrapper class.
+ * 
+ * @author Shane Bryzak
+ */
+public class NumberWrapper extends BaseWrapper implements Wrapper
+{
+   public NumberWrapper(BeanManager beanManager)
+   {
+      super(beanManager);
+   }
+
+   private static final byte[] NUMBER_TAG_OPEN = "<number>".getBytes();
+   private static final byte[] NUMBER_TAG_CLOSE = "</number>".getBytes();
+
+   public Object convert(Type type) throws ConversionException
+   {
+      String val = element.getStringValue().trim();
+
+      if (type.equals(Short.class))
+      {
+         value = !"".equals(val) ? Short.valueOf(val) : null;
+      } else if (type.equals(Short.TYPE))
+      {
+         value = Short.parseShort(val);
+      } else if (type.equals(Integer.class))
+      {
+         value = !"".equals(val) ? Integer.valueOf(val) : null;
+      } else if (type.equals(Integer.TYPE))
+      {
+         value = Integer.parseInt(val);
+      } else if (type.equals(Long.class) || type.equals(Object.class))
+      {
+         value = !"".equals(val) ? Long.valueOf(val) : null;
+      } else if (type.equals(Long.TYPE))
+      {
+         value = Long.parseLong(val);
+      } else if (type.equals(Float.class))
+      {
+         value = !"".equals(val) ? Float.valueOf(val) : null;
+      } else if (type.equals(Float.TYPE))
+      {
+         value = Float.parseFloat(val);
+      } else if (type.equals(Double.class))
+      {
+         value = !"".equals(val) ? Double.valueOf(val) : null;
+      } else if (type.equals(Double.TYPE))
+      {
+         value = Double.parseDouble(val);
+      } else if (type.equals(Byte.class))
+      {
+         value = !"".equals(val) ? Byte.valueOf(val) : null;
+      } else if (type.equals(Byte.TYPE))
+      {
+         value = Byte.parseByte(val);
+      } else if (type.equals(String.class))
+      {
+         value = val;
+      } else
+      {
+         throw new ConversionException(String.format(
+               "Value [%s] cannot be converted to type [%s].", element
+                     .getStringValue(), type));
+      }
+
+      return value;
+   }
+
+   /**
+    * 
+    * @param out
+    *           OutputStream
+    * @throws IOException
+    */
+   public void marshal(OutputStream out) throws IOException
+   {
+      out.write(NUMBER_TAG_OPEN);
+      out.write(value.toString().getBytes());
+      out.write(NUMBER_TAG_CLOSE);
+   }
+
+   /**
+    * Allow conversions to either Integer or String.
+    * 
+    * @param cls
+    *           Class
+    * @return ConversionScore
+    */
+   public ConversionScore conversionScore(Class cls)
+   {
+      if (cls.equals(Integer.class) || cls.equals(Integer.TYPE)
+            || cls.equals(Long.class) || cls.equals(Long.TYPE)
+            || cls.equals(Short.class) || cls.equals(Short.TYPE)
+            || cls.equals(Double.class) || cls.equals(Double.TYPE)
+            || cls.equals(Float.class) || cls.equals(Float.TYPE)
+            || cls.equals(Byte.class) || cls.equals(Byte.TYPE))
+      {
+         return ConversionScore.exact;
+      }
+
+      if (cls.equals(String.class) || cls.equals(Object.class))
+      {
+         return ConversionScore.compatible;
+      }
+
+      return ConversionScore.nomatch;
+   }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/StringWrapper.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/StringWrapper.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/StringWrapper.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,256 @@
+package org.jboss.seam.remoting.wrapper;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.enterprise.inject.spi.BeanManager;
+
+/**
+ * String wrapper class.
+ * 
+ * @author Shane Bryzak
+ */
+public class StringWrapper extends BaseWrapper implements Wrapper
+{
+   public StringWrapper(BeanManager beanManager)
+   {
+      super(beanManager);
+   }
+
+   private interface StringConverter
+   {
+      Object convert(String value);
+   }
+
+   private static final Map<Class, StringConverter> converters = new HashMap<Class, StringConverter>();
+
+   static
+   {
+      converters.put(String.class, new StringConverter() {
+         public Object convert(String value)
+         {
+            return value;
+         }
+      });
+      converters.put(Object.class, new StringConverter() {
+         public Object convert(String value)
+         {
+            return value;
+         }
+      });
+      converters.put(StringBuilder.class, new StringConverter() {
+         public Object convert(String value)
+         {
+            return new StringBuilder(value);
+         }
+      });
+      converters.put(StringBuffer.class, new StringConverter() {
+         public Object convert(String value)
+         {
+            return new StringBuffer(value);
+         }
+      });
+      converters.put(Integer.class, new StringConverter() {
+         public Object convert(String value)
+         {
+            return Integer.valueOf(value);
+         }
+      });
+      converters.put(Integer.TYPE, new StringConverter() {
+         public Object convert(String value)
+         {
+            return Integer.parseInt(value);
+         }
+      });
+      converters.put(Long.class, new StringConverter() {
+         public Object convert(String value)
+         {
+            return Long.valueOf(value);
+         }
+      });
+      converters.put(Long.TYPE, new StringConverter() {
+         public Object convert(String value)
+         {
+            return Long.parseLong(value);
+         }
+      });
+      converters.put(Short.class, new StringConverter() {
+         public Object convert(String value)
+         {
+            return Short.valueOf(value);
+         }
+      });
+      converters.put(Short.TYPE, new StringConverter() {
+         public Object convert(String value)
+         {
+            return Short.parseShort(value);
+         }
+      });
+      converters.put(Boolean.class, new StringConverter() {
+         public Object convert(String value)
+         {
+            return Boolean.valueOf(value);
+         }
+      });
+      converters.put(Boolean.TYPE, new StringConverter() {
+         public Object convert(String value)
+         {
+            return Boolean.parseBoolean(value);
+         }
+      });
+      converters.put(Double.class, new StringConverter() {
+         public Object convert(String value)
+         {
+            return Double.valueOf(value);
+         }
+      });
+      converters.put(Double.TYPE, new StringConverter() {
+         public Object convert(String value)
+         {
+            return Double.parseDouble(value);
+         }
+      });
+      converters.put(Float.class, new StringConverter() {
+         public Object convert(String value)
+         {
+            return Float.valueOf(value);
+         }
+      });
+      converters.put(Float.TYPE, new StringConverter() {
+         public Object convert(String value)
+         {
+            return Float.parseFloat(value);
+         }
+      });
+      converters.put(Character.class, new StringConverter() {
+         public Object convert(String value)
+         {
+            return Character.valueOf(value.charAt(0));
+         }
+      });
+      converters.put(Character.TYPE, new StringConverter() {
+         public Object convert(String value)
+         {
+            return value.charAt(0);
+         }
+      });
+      converters.put(Byte.class, new StringConverter() {
+         public Object convert(String value)
+         {
+            return Byte.valueOf(value);
+         }
+      });
+      converters.put(Byte.TYPE, new StringConverter() {
+         public Object convert(String value)
+         {
+            return Byte.parseByte(value);
+         }
+      });
+      converters.put(BigInteger.class, new StringConverter() {
+         public Object convert(String value)
+         {
+            return new BigInteger(value);
+         }
+      });
+      converters.put(BigDecimal.class, new StringConverter() {
+         public Object convert(String value)
+         {
+            return new BigDecimal(value);
+         }
+      });
+   }
+
+   public static final String DEFAULT_ENCODING = "UTF-8";
+
+   private static final byte[] STRING_TAG_OPEN = "<str>".getBytes();
+   private static final byte[] STRING_TAG_CLOSE = "</str>".getBytes();
+
+   private static final Class[] COMPATIBLE_CLASSES = { Integer.class,
+         Integer.TYPE, Long.class, Long.TYPE, Short.class, Short.TYPE,
+         Boolean.class, Boolean.TYPE, Double.class, Double.TYPE, Float.class,
+         Float.TYPE, Character.class, Character.TYPE, Byte.class, Byte.TYPE,
+         BigInteger.class, BigDecimal.class, Object.class };
+
+   public Object convert(Type type) throws ConversionException
+   {
+      String elementValue = null;
+      try
+      {
+         elementValue = URLDecoder.decode(element.getStringValue(),
+               DEFAULT_ENCODING);
+      } catch (UnsupportedEncodingException ex)
+      {
+         throw new ConversionException(
+               "Error converting value - encoding not supported.");
+      }
+
+      try
+      {
+         if (converters.containsKey(type))
+            value = converters.get(type).convert(elementValue);
+         else if (type instanceof Class && ((Class) type).isEnum())
+            value = Enum.valueOf((Class) type, elementValue);
+         else
+            // Should never reach this line - calcConverstionScore should
+            // guarantee this.
+            throw new ConversionException(String.format(
+                  "Value [%s] cannot be converted to type [%s].", elementValue,
+                  type));
+
+         return value;
+      } catch (Exception ex)
+      {
+         if (ex instanceof ConversionException)
+            throw (ConversionException) ex;
+         else
+            throw new ConversionException(String.format(
+                  "Could not convert value [%s] to type [%s].", elementValue,
+                  type.toString()), ex);
+      }
+   }
+
+   public ConversionScore conversionScore(Class cls)
+   {
+      if (cls.equals(String.class) || StringBuffer.class.isAssignableFrom(cls))
+         return ConversionScore.exact;
+
+      for (Class c : COMPATIBLE_CLASSES)
+      {
+         if (cls.equals(c))
+            return ConversionScore.compatible;
+      }
+
+      if (cls.isEnum())
+      {
+         try
+         {
+            String elementValue = URLDecoder.decode(element.getStringValue(),
+                  DEFAULT_ENCODING);
+            Enum.valueOf(cls, elementValue);
+            return ConversionScore.compatible;
+         } catch (IllegalArgumentException ex)
+         {
+         } catch (UnsupportedEncodingException ex)
+         {
+         }
+      }
+
+      return ConversionScore.nomatch;
+   }
+
+   public void marshal(OutputStream out) throws IOException
+   {
+      out.write(STRING_TAG_OPEN);
+      out.write(URLEncoder.encode(value.toString(), DEFAULT_ENCODING).replace(
+            "+", "%20").getBytes());
+      out.write(STRING_TAG_CLOSE);
+   }
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/Wrapper.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/Wrapper.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/Wrapper.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,71 @@
+package org.jboss.seam.remoting.wrapper;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Type;
+
+import org.dom4j.Element;
+import org.jboss.seam.remoting.CallContext;
+
+/**
+ * Acts as a wrapper around parameter values passed within an AJAX call.
+ *
+ * @author Shane Bryzak
+ */
+public interface Wrapper
+{
+  /**
+   * Sets the path of the wrapped object within the resulting object graph
+   *
+   * @param path String
+   */
+  public void setPath(String path);
+
+  public void setCallContext(CallContext context);
+
+  /**
+   * Extracts a value from a DOM4J Element
+   *
+   * @param element Element
+   */
+  public void setElement(Element element);
+
+  /**
+   *
+   * @param value Object
+   */
+  public void setValue(Object value);
+
+  /**
+   *
+   * @return Object
+   */
+  public Object getValue();
+
+  /**
+   *
+   */
+  public void unmarshal();
+
+  /**
+   * Convert the wrapped parameter value to the specified target class.
+   *
+   */
+  public Object convert(Type type) throws ConversionException;
+
+  public void marshal(OutputStream out) throws IOException;
+
+  public void serialize(OutputStream out) throws IOException;
+
+  /**
+   * Returns a score indicating whether this parameter value can be converted
+   * to the specified type.  This helper method is used to determine which
+   * (possibly overloaded) method of a component can/should be called.
+   *
+   * 0 - Cannot be converted
+   * 1 - Can be converted to this type
+   * 2 - Param is this exact type
+   *
+   */
+  public ConversionScore conversionScore(Class cls);
+}

Added: modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/WrapperFactory.java
===================================================================
--- modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/WrapperFactory.java	                        (rev 0)
+++ modules/trunk/remoting/src/main/java/org/jboss/seam/remoting/wrapper/WrapperFactory.java	2009-11-27 09:27:00 UTC (rev 11690)
@@ -0,0 +1,139 @@
+package org.jboss.seam.remoting.wrapper;
+
+import java.lang.reflect.Constructor;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.enterprise.inject.spi.BeanManager;
+
+/**
+ * 
+ * @author Shane Bryzak
+ */
+public class WrapperFactory
+{
+   /**
+    * Singleton instance.
+    */
+   private static final WrapperFactory factory = new WrapperFactory();
+
+   /**
+    * A registry of wrapper types
+    */
+   private Map<String, Class> wrapperRegistry = new HashMap<String, Class>();
+
+   private Map<Class, Class> classRegistry = new HashMap<Class, Class>();
+
+   /**
+    * Private constructor
+    */
+   private WrapperFactory()
+   {
+      // Register the defaults
+      registerWrapper("str", StringWrapper.class);
+      registerWrapper("bool", BooleanWrapper.class);
+      registerWrapper("bean", BeanWrapper.class);
+      registerWrapper("number", NumberWrapper.class);
+      registerWrapper("null", NullWrapper.class);
+      registerWrapper("bag", BagWrapper.class);
+      registerWrapper("map", MapWrapper.class);
+      registerWrapper("date", DateWrapper.class);
+
+      // String types
+      registerWrapperClass(String.class, StringWrapper.class);
+      registerWrapperClass(StringBuilder.class, StringWrapper.class);
+      registerWrapperClass(StringBuffer.class, StringWrapper.class);
+      registerWrapperClass(Character.class, StringWrapper.class);
+
+      // Big numbers are handled by StringWrapper
+      registerWrapperClass(BigDecimal.class, StringWrapper.class);
+      registerWrapperClass(BigInteger.class, StringWrapper.class);
+
+      // Number types
+      registerWrapperClass(Integer.class, NumberWrapper.class);
+      registerWrapperClass(Long.class, NumberWrapper.class);
+      registerWrapperClass(Short.class, NumberWrapper.class);
+      registerWrapperClass(Double.class, NumberWrapper.class);
+      registerWrapperClass(Float.class, NumberWrapper.class);
+      registerWrapperClass(Byte.class, NumberWrapper.class);
+   }
+
+   public void registerWrapper(String type, Class wrapperClass)
+   {
+      wrapperRegistry.put(type, wrapperClass);
+   }
+
+   public void registerWrapperClass(Class cls, Class wrapperClass)
+   {
+      classRegistry.put(cls, wrapperClass);
+   }
+
+   public static WrapperFactory getInstance()
+   {
+      return factory;
+   }
+
+   public Wrapper createWrapper(String type, BeanManager beanManager)
+   {
+      Class wrapperClass = wrapperRegistry.get(type);
+
+      if (wrapperClass != null)
+      {
+         try
+         {
+            Constructor<Wrapper> c = wrapperClass
+                  .getConstructor(BeanManager.class);
+            Wrapper wrapper = c.newInstance(beanManager);
+            return wrapper;
+         } catch (Exception ex)
+         {
+         }
+      }
+
+      throw new RuntimeException(String.format(
+            "Failed to create wrapper for type: %s", type));
+   }
+
+   public Wrapper getWrapperForObject(Object obj, BeanManager beanManager)
+   {
+      if (obj == null)
+         return new NullWrapper(beanManager);
+
+      Wrapper w = null;
+
+      if (Map.class.isAssignableFrom(obj.getClass()))
+         w = new MapWrapper(beanManager);
+      else if (obj.getClass().isArray()
+            || Collection.class.isAssignableFrom(obj.getClass()))
+         w = new BagWrapper(beanManager);
+      else if (obj.getClass().equals(Boolean.class)
+            || obj.getClass().equals(Boolean.TYPE))
+         w = new BooleanWrapper(beanManager);
+      else if (obj.getClass().isEnum())
+         w = new StringWrapper(beanManager);
+      else if (Date.class.isAssignableFrom(obj.getClass())
+            || Calendar.class.isAssignableFrom(obj.getClass()))
+         w = new DateWrapper(beanManager);
+      else if (classRegistry.containsKey(obj.getClass()))
+      {
+         try
+         {
+            Constructor<Wrapper> c = classRegistry.get(obj.getClass())
+                  .getConstructor(BeanManager.class);
+            w = c.newInstance(beanManager);
+         } catch (Exception ex)
+         {
+            throw new RuntimeException("Failed to create wrapper instance.");
+         }
+      } else
+         w = new BeanWrapper(beanManager);
+
+      w.setValue(obj);
+      return w;
+   }
+}



More information about the seam-commits mailing list