[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