From 78934ccb57d0b3a297aa37c7eb1c7d16abfce7b4 Mon Sep 17 00:00:00 2001 From: fmoga Date: Fri, 11 Feb 2011 20:06:28 +0000 Subject: Added multiple response support for the comet binding. git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@1069936 13f79535-47bb-0310-9956-ffa450edef68 --- .../comet/runtime/CometComponentContext.java | 54 ++++ .../sca/binding/comet/runtime/CometInvoker.java | 57 ++-- .../binding/comet/runtime/CometMessageContext.java | 51 ++++ .../runtime/CometReferenceBindingProvider.java | 2 +- .../comet/runtime/CometServiceBindingProvider.java | 4 +- .../sca/binding/comet/runtime/ServletFactory.java | 230 +++++++++-------- .../comet/runtime/callback/CometCallback.java | 31 +++ .../comet/runtime/handler/CometBindingHandler.java | 286 ++++++++++++--------- 8 files changed, 447 insertions(+), 268 deletions(-) create mode 100644 sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/CometComponentContext.java create mode 100644 sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/CometMessageContext.java create mode 100644 sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/callback/CometCallback.java (limited to 'sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java') diff --git a/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/CometComponentContext.java b/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/CometComponentContext.java new file mode 100644 index 0000000000..ce19da7e7b --- /dev/null +++ b/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/CometComponentContext.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.comet.runtime; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.tuscany.sca.interfacedef.Operation; +import org.apache.tuscany.sca.runtime.RuntimeEndpoint; + +public class CometComponentContext { + + private Map endpoints; + private Map operations; + + public CometComponentContext() { + endpoints = new ConcurrentHashMap(); + operations = new ConcurrentHashMap(); + } + + public void addEndpoint(String key, RuntimeEndpoint endpoint) { + endpoints.put(key, endpoint); + } + + public void addOperation(String key, Operation operation) { + operations.put(key, operation); + } + + public RuntimeEndpoint getEndpoint(String key) { + return endpoints.get(key); + } + + public Operation getOperation(String key) { + return operations.get(key); + } + +} diff --git a/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/CometInvoker.java b/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/CometInvoker.java index 9353571cb1..5e6375480e 100644 --- a/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/CometInvoker.java +++ b/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/CometInvoker.java @@ -20,43 +20,36 @@ package org.apache.tuscany.sca.binding.comet.runtime; import org.apache.tuscany.sca.assembly.EndpointReference; +import org.apache.tuscany.sca.binding.comet.runtime.handler.CometBindingHandler; +import org.apache.tuscany.sca.core.invocation.impl.MessageImpl; import org.apache.tuscany.sca.interfacedef.Operation; import org.apache.tuscany.sca.invocation.Invoker; import org.apache.tuscany.sca.invocation.Message; -/** - * Invoker for a service binding. Invoking is made from client Javascript so no - * behavior is needed. - */ public class CometInvoker implements Invoker { - /** - * The invoked operation. - */ - protected Operation operation; - - /** - * The endpoint to which the operation belongs. - */ - protected EndpointReference endpoint; - - /** - * Default constructor. - * - * @param operation the operation - * @param endpoint the endpoint - */ - public CometInvoker(final Operation operation, final EndpointReference endpoint) { - this.operation = operation; - this.endpoint = endpoint; - } - - /** - * No behavior. - */ - @Override - public Message invoke(final Message msg) { - return null; - } + protected Operation operation; + protected EndpointReference endpoint; + + public CometInvoker(final Operation operation, final EndpointReference endpoint) { + this.operation = operation; + this.endpoint = endpoint; + } + + @Override + public Message invoke(final Message msg) { + String operation = msg.getOperation().getName(); + CometMessageContext context = msg.getBindingContext(); + CometBindingHandler handler = context.getCometHandler(); + Message message = new MessageImpl(); + if (operation.equals("sendResponse")) { + String callbackMethod = context.getCallbackMethod(); + Object[] body = msg.getBody(); + handler.respondToClient(callbackMethod, body[0]); + } else if (operation.equals("isClientConnected")) { + message.setBody(handler.isClientConnected()); + } + return message; + } } diff --git a/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/CometMessageContext.java b/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/CometMessageContext.java new file mode 100644 index 0000000000..21dcc9287f --- /dev/null +++ b/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/CometMessageContext.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.comet.runtime; + +import org.apache.tuscany.sca.binding.comet.runtime.handler.CometBindingHandler; + +public class CometMessageContext { + + private CometBindingHandler cometHandler; + + private String callbackMethod; + + public CometMessageContext(CometBindingHandler cometHandler, String callbackMethod) { + this.cometHandler = cometHandler; + this.callbackMethod = callbackMethod; + } + + public CometBindingHandler getCometHandler() { + return cometHandler; + } + + public void setCometHandler(CometBindingHandler cometHandler) { + this.cometHandler = cometHandler; + } + + public String getCallbackMethod() { + return callbackMethod; + } + + public void setCallbackMethod(String callbackMethod) { + this.callbackMethod = callbackMethod; + } + +} diff --git a/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/CometReferenceBindingProvider.java b/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/CometReferenceBindingProvider.java index 17470e3738..4002f29a0d 100644 --- a/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/CometReferenceBindingProvider.java +++ b/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/CometReferenceBindingProvider.java @@ -61,7 +61,7 @@ public class CometReferenceBindingProvider implements ReferenceBindingProvider { @Override public InterfaceContract getBindingInterfaceContract() { - return null; + return endpoint.getReference().getInterfaceContract(); } @Override diff --git a/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/CometServiceBindingProvider.java b/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/CometServiceBindingProvider.java index 31c0b5b4d8..764fb67f69 100644 --- a/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/CometServiceBindingProvider.java +++ b/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/CometServiceBindingProvider.java @@ -65,7 +65,7 @@ public class CometServiceBindingProvider implements ServiceBindingProvider { JavascriptGenerator.generateServiceProxy(service); for (final Operation operation : serviceInterface.getOperations()) { JavascriptGenerator.generateMethodProxy(service, operation); - ServletFactory.addOperation(this.endpoint, operation); + ServletFactory.registerOperation(this.endpoint, operation); } } @@ -79,7 +79,7 @@ public class CometServiceBindingProvider implements ServiceBindingProvider { @Override public InterfaceContract getBindingInterfaceContract() { - return null; + return endpoint.getService().getInterfaceContract(); } @Override diff --git a/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/ServletFactory.java b/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/ServletFactory.java index c1244b3183..ed40ccd4f9 100644 --- a/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/ServletFactory.java +++ b/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/ServletFactory.java @@ -19,9 +19,6 @@ package org.apache.tuscany.sca.binding.comet.runtime; -import java.util.HashMap; -import java.util.Map; - import org.apache.tuscany.sca.host.http.ServletHost; import org.apache.tuscany.sca.interfacedef.Operation; import org.apache.tuscany.sca.runtime.RuntimeEndpoint; @@ -40,116 +37,121 @@ import org.atmosphere.cpr.AtmosphereServlet; */ public final class ServletFactory { - /** - * Init-param key for the AtmosphereServlet defining where to look for - * Jersey classes. - */ - private static final String PACKAGE_KEY = "com.sun.jersey.config.property.packages"; - - /** - * Package of the class handling dispatching to endpoints. - */ - private static final String PACKAGE_VALUE = "org.apache.tuscany.sca.binding.comet.runtime.handler"; - - /** - * Package of the class handling Javascript toolkit retrieval. - */ - private static final String JS_PACKAGE_VALUE = "org.apache.tuscany.sca.binding.comet.runtime.javascript"; - - /** - * Property in the ServletContext where endpoints are added incrementally as - * the Tuscany runtime calls the CometServiceBindingProvider for each comet - * service. - */ - public static final String ENDPOINTS_KEY = "org.apache.tuscany.sca.binding.comet.endpoints"; - - /** - * Property in the ServletContext where operations are added incrementally - * as the CometServiceBindingProvider is calling the registerServlet method - * for each comet service method. - */ - public static final String OPERATIONS_KEY = "org.apache.tuscany.sca.binding.comet.operations"; - - /** - * Path where services will be exposed. - */ - public static final String PATH = "/tuscany-comet/*"; - - /** - * Path where Javascript toolkit will be exposed. - */ - public static final String JS_PATH = "/org.apache.tuscany.sca.cometComponentContext.js/*"; - - /** - * The servlet that is exposing the comet services. - */ - private static AtmosphereServlet cometServlet = null; - - /** - * The servlet that is exposing the Javascript toolkit. - */ - private static AtmosphereServlet javascriptServlet = null; - - /** - * Private constructor for the singleton class. - */ - private ServletFactory() { - } - - /** - * Method called by CometServiceBindingProvider for each endpoint in order - * to create the two singleton servlets. - * - * @param servletHost the underlying servlet host - */ - public static synchronized void registerServlet(final ServletHost servletHost) { - if (ServletFactory.cometServlet == null) { - ServletFactory.cometServlet = new AtmosphereServlet(); - ServletFactory.cometServlet.addInitParameter(ServletFactory.PACKAGE_KEY, ServletFactory.PACKAGE_VALUE); - servletHost.addServletMapping(ServletFactory.PATH, ServletFactory.cometServlet); - // store operations and corresponding endpoint in the ServletContext - // so that they can be retrieved from inside the web service methods - final Map endpoints = new HashMap(); - ServletFactory.cometServlet.getServletContext().setAttribute(ServletFactory.ENDPOINTS_KEY, endpoints); - final Map operations = new HashMap(); - ServletFactory.cometServlet.getServletContext().setAttribute(ServletFactory.OPERATIONS_KEY, operations); - } - if (ServletFactory.javascriptServlet == null) { - ServletFactory.javascriptServlet = new AtmosphereServlet(); - ServletFactory.javascriptServlet.addInitParameter(ServletFactory.PACKAGE_KEY, - ServletFactory.JS_PACKAGE_VALUE); - servletHost.addServletMapping(ServletFactory.JS_PATH, ServletFactory.javascriptServlet); - } - } - - /** - * Method called by CometServiceBindingProvider for each endpoint operation - * in order to store all the operations the servlet will serve. - * - * @param endpoint the endpoint - * @param operation the operation - */ - public static synchronized void addOperation(final RuntimeEndpoint endpoint, final Operation operation) { - final String url = "/" + endpoint.getService().getName() + "/" + operation.getName(); - final Map endpoints = - (Map)ServletFactory.cometServlet.getServletContext() - .getAttribute(ServletFactory.ENDPOINTS_KEY); - endpoints.put(url, endpoint); - final Map operations = - (Map)ServletFactory.cometServlet.getServletContext() - .getAttribute(ServletFactory.OPERATIONS_KEY); - operations.put(url, operation); - } - - /** - * Method called by CometServiceBindingProvider for each endpoint operation - * in order to remove the two servlets. - * - * @param servletHost the underlying servlet host - */ - public static synchronized void unregisterServlet(final ServletHost servletHost) { - servletHost.removeServletMapping(ServletFactory.PATH); - servletHost.removeServletMapping(ServletFactory.JS_PATH); - } + /** + * Init-param key for the AtmosphereServlet defining where to look for + * Jersey classes. + */ + private static final String PACKAGE_KEY = "com.sun.jersey.config.property.packages"; + + /** + * Package of the class handling dispatching to endpoints. + */ + private static final String PACKAGE_VALUE = "org.apache.tuscany.sca.binding.comet.runtime.handler"; + + /** + * Package of the class handling Javascript toolkit retrieval. + */ + private static final String JS_PACKAGE_VALUE = "org.apache.tuscany.sca.binding.comet.runtime.javascript"; + + /** + * Key in the ServletContext where the comet component context is stored. + */ + public static final String COMET_COMPONENT_CONTEXT_KEY = "org.apache.tuscany.sca.binding.comet.operations"; + + /** + * Path where services will be exposed. + */ + public static final String PATH = "/tuscany-comet/*"; + + /** + * Path where Javascript toolkit will be exposed. + */ + public static final String JS_PATH = "/org.apache.tuscany.sca.cometComponentContext.js/*"; + + /** + * The servlet that is exposing the comet services. + */ + private static AtmosphereServlet cometServlet = null; + + /** + * The servlet that is exposing the Javascript toolkit. + */ + private static AtmosphereServlet javascriptServlet = null; + + /** + * Private constructor for the singleton class. + */ + private ServletFactory() { + } + + /** + * Method called by CometServiceBindingProvider for each endpoint in order + * to create the two singleton servlets. + * + * @param servletHost + * the underlying servlet host + */ + public static synchronized void registerServlet( + final ServletHost servletHost) { + registerCometServlet(servletHost); + registerJavascriptServlet(servletHost); + } + + private static void registerCometServlet(ServletHost servletHost) { + if (ServletFactory.cometServlet == null) { + ServletFactory.cometServlet = new AtmosphereServlet(); + ServletFactory.cometServlet.addInitParameter( + ServletFactory.PACKAGE_KEY, ServletFactory.PACKAGE_VALUE); + servletHost.addServletMapping(ServletFactory.PATH, + ServletFactory.cometServlet); + final CometComponentContext context = new CometComponentContext(); + ServletFactory.cometServlet.getServletContext().setAttribute( + ServletFactory.COMET_COMPONENT_CONTEXT_KEY, context); + } + } + + private static void registerJavascriptServlet(ServletHost servletHost) { + if (ServletFactory.javascriptServlet == null) { + ServletFactory.javascriptServlet = new AtmosphereServlet(); + ServletFactory.javascriptServlet + .addInitParameter(ServletFactory.PACKAGE_KEY, + ServletFactory.JS_PACKAGE_VALUE); + servletHost.addServletMapping(ServletFactory.JS_PATH, + ServletFactory.javascriptServlet); + } + } + + /** + * Method called by CometServiceBindingProvider for each endpoint operation + * in order to store all the operations the servlet will serve. + * + * @param endpoint + * the endpoint + * @param operation + * the operation + */ + public static void registerOperation(final RuntimeEndpoint endpoint, + final Operation operation) { + final String url = "/" + endpoint.getService().getName() + "/" + + operation.getName(); + CometComponentContext context = (CometComponentContext) cometServlet + .getServletContext().getAttribute(COMET_COMPONENT_CONTEXT_KEY); + context.addEndpoint(url, endpoint); + context.addOperation(url, operation); + } + + /** + * Method called by CometServiceBindingProvider for each endpoint operation + * in order to remove the two servlets. + * + * @param servletHost + * the underlying servlet host + */ + public static void unregisterServlet(final ServletHost servletHost) { + synchronized (servletHost) { + servletHost.removeServletMapping(ServletFactory.PATH); + servletHost.removeServletMapping(ServletFactory.JS_PATH); + } + } } diff --git a/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/callback/CometCallback.java b/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/callback/CometCallback.java new file mode 100644 index 0000000000..fb9facfd35 --- /dev/null +++ b/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/callback/CometCallback.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.tuscany.sca.binding.comet.runtime.callback; + +import org.oasisopen.sca.annotation.Remotable; + +@Remotable +public interface CometCallback { + + void sendResponse(Object response); + + boolean isClientConnected(); + +} diff --git a/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/handler/CometBindingHandler.java b/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/handler/CometBindingHandler.java index 512b834840..a62d73d467 100644 --- a/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/handler/CometBindingHandler.java +++ b/sca-java-2.x/trunk/modules/binding-comet-runtime/src/main/java/org/apache/tuscany/sca/binding/comet/runtime/handler/CometBindingHandler.java @@ -22,9 +22,9 @@ package org.apache.tuscany.sca.binding.comet.runtime.handler; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; -import java.util.Map; import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -33,14 +33,20 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; +import org.apache.tuscany.sca.assembly.EndpointReference; +import org.apache.tuscany.sca.binding.comet.runtime.CometComponentContext; +import org.apache.tuscany.sca.binding.comet.runtime.CometMessageContext; import org.apache.tuscany.sca.binding.comet.runtime.ServletFactory; +import org.apache.tuscany.sca.core.assembly.impl.RuntimeEndpointImpl; +import org.apache.tuscany.sca.core.assembly.impl.RuntimeEndpointReferenceImpl; +import org.apache.tuscany.sca.core.invocation.impl.MessageImpl; import org.apache.tuscany.sca.interfacedef.DataType; import org.apache.tuscany.sca.interfacedef.Operation; +import org.apache.tuscany.sca.invocation.Message; import org.apache.tuscany.sca.runtime.RuntimeEndpoint; import org.atmosphere.annotation.Broadcast; import org.atmosphere.cpr.Broadcaster; import org.atmosphere.cpr.DefaultBroadcaster; -import org.atmosphere.jersey.Broadcastable; import org.atmosphere.jersey.SuspendResponse; import com.google.gson.Gson; @@ -54,121 +60,163 @@ import com.sun.jersey.spi.container.servlet.PerSession; @PerSession public class CometBindingHandler { - /** - * The object used to suspend the response and send async responses back to - * client. - */ - private Broadcaster broadcaster; - - /** - * The service endpoints corresponding to each operation. - */ - private Map endpoints; - - /** - * The comet operations. - */ - private Map operations; - - /** - * JSON converter. - */ - private Gson gson; - - /** - * The underlying servlet context. - */ - @Context - private ServletContext sc; - - /** - * Method called at comet connect time. This suspends the response and keeps - * the connection opened. - * - * @return the suspended response - */ - @GET - public SuspendResponse connect() { - this.broadcaster = new DefaultBroadcaster(); - this.endpoints = (Map)this.sc.getAttribute(ServletFactory.ENDPOINTS_KEY); - this.operations = (Map)this.sc.getAttribute(ServletFactory.OPERATIONS_KEY); - this.gson = new Gson(); - return new SuspendResponse.SuspendResponseBuilder().broadcaster(this.broadcaster).outputComments(true) - .build(); - } - - /** - * Method called on service calls. - * - * @param service service called - * @param method operation called - * @param callbackMethod the callback method from Javascript - * @param jsonData arguments for the method sent as JSON array - * @return object used by the Broadcaster to send response through the - * persisted connection - * @throws InvocationTargetException if problems occur at service invocation - */ - @POST - @Path("/{service}/{method}") - @Broadcast - public Broadcastable callAndRespond(@PathParam("service") final String service, - @PathParam("method") final String method, - @FormParam("callback") final String callbackMethod, - @FormParam("params") final String jsonData) throws InvocationTargetException { - final String url = "/" + service + "/" + method; - final RuntimeEndpoint wire = this.endpoints.get(url); - final Operation operation = this.operations.get(url); - final Object[] args = new Object[operation.getInputType().getLogical().size()]; - final String[] json = this.parseArray(jsonData); - int index = 0; - // convert each argument to the corresponding class - for (final DataType dataType : operation.getInputType().getLogical()) { - args[index] = this.gson.fromJson(json[index], dataType.getPhysical()); - index++; - } - // invoke the service operation - final Object response = wire.invoke(operation, args); - return new Broadcastable(callbackMethod + "($.secureEvalJSON('" + this.gson.toJson(response) + "'))", "", - this.broadcaster); - } - - /** - * Parse the JSON array containing the arguments for the method call in - * order to avoid converting JSON to Object[]. Converting each object - * separately to it's corresponding type avoids type mismatch problems at - * service invocation. - * - * @param jsonArray the JSON array - * @return an array of JSON formatted objects - */ - private String[] parseArray(final String jsonArray) { - final List objects = new ArrayList(); - int bracketNum = 0; - int parNum = 0; - int startPos = 1; - for (int i = 0; i < jsonArray.length(); i++) { - switch (jsonArray.charAt(i)) { - case '{': - bracketNum++; - break; - case '}': - bracketNum--; - break; - case '[': - parNum++; - break; - case ']': - parNum--; - break; - case ',': - if ((bracketNum == 0) && (parNum == 1)) { - objects.add(jsonArray.substring(startPos, i)); - startPos = i + 1; - } - } - } - // add last object - objects.add(jsonArray.substring(startPos, jsonArray.length() - 1)); - return objects.toArray(new String[] {}); - } + /** + * The object used to suspend the response and send async responses back to + * client. + */ + private Broadcaster broadcaster; + + /** + * JSON converter. + */ + private Gson gson = new Gson(); + + /** + * The underlying servlet context. + */ + @Context + private ServletContext sc; + + @Context + private HttpServletRequest request; + + private CometComponentContext context; + + /** + * Method called at comet connect time. This suspends the response and keeps + * the connection opened. + * + * @return the suspended response + */ + @GET + public SuspendResponse connect() { + System.out.println("-- connect -- Session Id: " + request.getSession().getId()); + if (broadcaster == null) { + broadcaster = new DefaultBroadcaster(); + context = (CometComponentContext) sc.getAttribute(ServletFactory.COMET_COMPONENT_CONTEXT_KEY); + } + return new SuspendResponse.SuspendResponseBuilder().broadcaster(this.broadcaster).outputComments(true) + .build(); + } + + /** + * Method called on service calls. + * + * @param service + * service called + * @param method + * operation called + * @param callbackMethod + * the callback method from Javascript + * @param jsonData + * arguments for the method sent as JSON array + * @return object used by the Broadcaster to send response through the + * persisted connection + * @throws InvocationTargetException + * if problems occur at service invocation + */ + @POST + @Path("/{service}/{method}") + @Broadcast + public void handleRequest(@PathParam("service") final String service, @PathParam("method") final String method, + @FormParam("callback") final String callbackMethod, @FormParam("params") final String jsonData) + throws InvocationTargetException { + System.out.println("-- handleRequest -- Session Id: " + request.getSession().getId()); + final String url = "/" + service + "/" + method; + final RuntimeEndpoint wire = context.getEndpoint(url); + final Operation operation = context.getOperation(url); + + final Object[] args = decodeJsonDataForOperation(jsonData, operation); + Message msg = createMessageWithMockedCometReference(args, callbackMethod); + wire.invoke(operation, msg); + } + + /** + * Convert request parameters from JSON to operation parameter types. + * + * @param jsonData + * @param operation + * @return + */ + private Object[] decodeJsonDataForOperation(String jsonData, Operation operation) { + Object[] args = new Object[operation.getInputType().getLogical().size()]; + final String[] json = this.parseArray(jsonData); + int index = 0; + // convert each argument to the corresponding class + for (final DataType dataType : operation.getInputType().getLogical()) { + args[index] = this.gson.fromJson(json[index], dataType.getPhysical()); + index++; + } + return args; + } + + /** + * Creates the message to be sent with a mocked EndpointReference in the + * 'from' field as the request comes from a browser (there is no actual + * comet reference running in a controlled environment). + * + * @param args + * @param callbackMethod + * @return + */ + private Message createMessageWithMockedCometReference(Object[] args, String callbackMethod) { + Message msg = new MessageImpl(); + msg.setBody(args); + CometMessageContext messageContext = new CometMessageContext(this, callbackMethod); + msg.setBindingContext(messageContext); + EndpointReference re = new RuntimeEndpointReferenceImpl(); + re.setCallbackEndpoint(new RuntimeEndpointImpl()); + msg.setFrom(re); + return msg; + } + + /** + * Parse the JSON array containing the arguments for the method call in + * order to avoid converting JSON to Object[]. Converting each object + * separately to it's corresponding type avoids type mismatch problems at + * service invocation. + * + * @param jsonArray + * the JSON array + * @return an array of JSON formatted objects + */ + private String[] parseArray(final String jsonArray) { + final List objects = new ArrayList(); + int bracketNum = 0; + int parNum = 0; + int startPos = 1; + for (int i = 0; i < jsonArray.length(); i++) { + switch (jsonArray.charAt(i)) { + case '{': + bracketNum++; + break; + case '}': + bracketNum--; + break; + case '[': + parNum++; + break; + case ']': + parNum--; + break; + case ',': + if ((bracketNum == 0) && (parNum == 1)) { + objects.add(jsonArray.substring(startPos, i)); + startPos = i + 1; + } + } + } + // add last object + objects.add(jsonArray.substring(startPos, jsonArray.length() - 1)); + return objects.toArray(new String[] {}); + } + + public void respondToClient(String callbackMethod, Object response) { + broadcaster.broadcast(callbackMethod + "($.secureEvalJSON('" + this.gson.toJson(response) + "'))"); + } + + public boolean isClientConnected() { + return !broadcaster.getAtmosphereResources().isEmpty(); + } + } -- cgit v1.2.3