[jboss-cvs] jboss-seam/src/remoting/org/jboss/seam/remoting/gwt ...

Shane Bryzak sbryzak at redhat.com
Sat Apr 14 23:40:02 EDT 2007


  User: sbryzak2
  Date: 07/04/14 23:40:02

  Added:       src/remoting/org/jboss/seam/remoting/gwt  
                        GWTRemoteService.java GWTToSeamAdapter.java
  Log:
  initial gwt support
  
  Revision  Changes    Path
  1.1      date: 2007/04/15 03:40:02;  author: sbryzak2;  state: Exp;jboss-seam/src/remoting/org/jboss/seam/remoting/gwt/GWTRemoteService.java
  
  Index: GWTRemoteService.java
  ===================================================================
  package org.jboss.seam.remoting.gwt;
  
  import static org.jboss.seam.InterceptionType.NEVER;
  import static org.jboss.seam.ScopeType.APPLICATION;
  import static org.jboss.seam.annotations.Install.BUILT_IN;
  
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.UnsupportedEncodingException;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Set;
  import java.util.zip.GZIPOutputStream;
  
  import javax.servlet.ServletContext;
  import javax.servlet.ServletException;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  
  import org.jboss.seam.annotations.Install;
  import org.jboss.seam.annotations.Intercept;
  import org.jboss.seam.annotations.Name;
  import org.jboss.seam.annotations.Scope;
  import org.jboss.seam.annotations.Startup;
  import org.jboss.seam.remoting.gwt.GWTToSeamAdapter.ReturnedObject;
  import org.jboss.seam.servlet.AbstractResource;
  
  import com.google.gwt.user.client.rpc.RemoteService;
  import com.google.gwt.user.client.rpc.SerializableException;
  import com.google.gwt.user.client.rpc.SerializationException;
  import com.google.gwt.user.server.rpc.impl.ServerSerializableTypeOracle;
  import com.google.gwt.user.server.rpc.impl.ServerSerializableTypeOracleImpl;
  import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader;
  import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter;
  
  /**
   * 
   * @author @hacker Michael Neale
   * This is a less then ideal approach, but GWT (up to and including 1.3) 
   * has no means to get into the internals of the RPC mechanism, and free it 
   * from the shackles of the servlet API.
   * So I, the liberator, have hacked this out of RemoteServiceServlet to do this, heretofore.
   * 
   * When GWT 1.4 comes along to save us all, this can be retired, and the RPC utility class
   * - as contributed by Rob Jellinghaus can be used instead.
   * 
   */
  @Startup
  @Scope(APPLICATION)
  @Name("org.jboss.seam.remoting.gwt.gwtRemoteService")
  @Install(precedence = BUILT_IN, classDependencies = {"com.google.gwt.user.client.rpc.RemoteService"})
  @Intercept(NEVER)
  public class GWTRemoteService extends AbstractResource {
  
    /*
     * These members are used to get and set the different HttpServletResponse and
     * HttpServletRequest headers.
     */
    private static final String ACCEPT_ENCODING = "Accept-Encoding";
    private static final String CHARSET_UTF8 = "UTF-8";
    private static final String CONTENT_ENCODING = "Content-Encoding";
    private static final String CONTENT_ENCODING_GZIP = "gzip";
    private static final String CONTENT_TYPE_TEXT_PLAIN_UTF8 = "text/plain; charset=utf-8";
    private static final String GENERIC_FAILURE_MSG = "The call failed on the server; see server log for details";
    private static final HashMap TYPE_NAMES;
  
    /**
     * Controls the compression threshold at and below which no compression will
     * take place.
     */
    private static final int UNCOMPRESSED_BYTE_SIZE_LIMIT = 256;
  
    static {
      TYPE_NAMES = new HashMap();
      TYPE_NAMES.put("Z", boolean.class);
      TYPE_NAMES.put("B", byte.class);
      TYPE_NAMES.put("C", char.class);
      TYPE_NAMES.put("D", double.class);
      TYPE_NAMES.put("F", float.class);
      TYPE_NAMES.put("I", int.class);
      TYPE_NAMES.put("J", long.class);
      TYPE_NAMES.put("S", short.class);
    }
    
    @Override
    protected String getResourcePath()
    {
       return "/gwt";
    }  
  
    /**
     * Return true if the response object accepts Gzip encoding. This is done by
     * checking that the accept-encoding header specifies gzip as a supported
     * encoding.
     */
    private static boolean acceptsGzipEncoding(HttpServletRequest request) {
      assert (request != null);
  
      String acceptEncoding = request.getHeader(ACCEPT_ENCODING);
      if (null == acceptEncoding) {
        return false;
      }
  
      return (acceptEncoding.indexOf(CONTENT_ENCODING_GZIP) != -1);
    }
  
    /**
     * This method attempts to estimate the number of bytes that a string will
     * consume when it is sent out as part of an HttpServletResponse. This really
     * a hack since we are assuming that every character will consume two bytes
     * upon transmission. This is definitely not true since some characters
     * actually consume more than two bytes and some consume less. This is even
     * less accurate if the string is converted to UTF8. However, it does save us
     * from converting every string that we plan on sending back to UTF8 just to
     * determine that we should not compress it.
     */
    private static int estimateByteSize(final String buffer) {
      return (buffer.length() * 2);
    }
  
    /**
     * Find the invoked method on either the specified interface or any super.
     */
    private static Method findInterfaceMethod(Class intf, String methodName,
        Class[] paramTypes, boolean includeInherited) {
      try {
        return intf.getDeclaredMethod(methodName, paramTypes);
      } catch (NoSuchMethodException e) {
        if (includeInherited) {
          Class[] superintfs = intf.getInterfaces();
          for (int i = 0; i < superintfs.length; i++) {
            Method method = findInterfaceMethod(superintfs[i], methodName,
                paramTypes, true);
            if (method != null) {
              return method;
            }
          }
        }
  
        return null;
      }
    }
  
    private final Set knownImplementedInterfaces = new HashSet();
  
    private final ThreadLocal perThreadRequest = new ThreadLocal();
  
    private final ThreadLocal perThreadResponse = new ThreadLocal();
  
    private final ServerSerializableTypeOracle serializableTypeOracle;
  
    /**
     * The default constructor.
     */
    public GWTRemoteService() {
      serializableTypeOracle = new ServerSerializableTypeOracleImpl(
          getPackagePaths());
    }
  
    /**
     * This is called internally.
     */
    @Override
    public final void getResource(HttpServletRequest request,
        HttpServletResponse response) {
      Throwable caught;
      try {
        // Store the request & response objects in thread-local storage.
        //
        perThreadRequest.set(request);
        perThreadResponse.set(response);
  
        // Read the request fully.
        //
        String requestPayload = readPayloadAsUtf8(request);
  
        // Invoke the core dispatching logic, which returns the serialized
        // result.
        //
        String responsePayload = processCall(requestPayload);
  
        // Write the response.
        //
        writeResponse(request, response, responsePayload);
        return;
      } catch (IOException e) {
        caught = e;
      } catch (ServletException e) {
        caught = e;
      } catch (SerializationException e) {
        caught = e;
      } catch (Throwable e) {
        caught = e;
      }
  
      respondWithFailure(response, caught);
    }
  
    /**
     * This is public so that it can be unit tested easily without HTTP.
     */
    public String processCall(String payload) throws SerializationException {
  
      // Let subclasses see the serialized request.
      //
      onBeforeRequestDeserialized(payload);
  
      // Create a stream to deserialize the request.
      //
      ServerSerializationStreamReader streamReader = new ServerSerializationStreamReader(
          serializableTypeOracle);
      streamReader.prepareToRead(payload);
  
      // Read the service interface
      //
      String serviceIntfName = streamReader.readString();
  
  //    // TODO(mmendez): need a way to check the type signature of the service intf
  //    // Verify that this very servlet implements the specified interface name.
  //    //
  //    if (!isImplementedRemoteServiceInterface(serviceIntfName)) {
  //      // Bad payload, possible hack attempt.
  //      //
  //      throw new SecurityException(
  //          "Blocked attempt to access interface '"
  //              + serviceIntfName
  //              + "', which is either not implemented by this servlet or which doesn't extend RemoteService; this is either misconfiguration or a hack attempt");
  //    }
  
      // Actually get the service interface, so that we can query its methods.
      //
  //    Class serviceIntf;
  //    try {
  //      serviceIntf = getClassFromName(serviceIntfName);
  //    } catch (ClassNotFoundException e) {
  //      throw new SerializationException("Unknown service interface class '"
  //          + serviceIntfName + "'", e);
  //    }
  
      // Read the method name.
      //
      String methodName = streamReader.readString();
  
       
      // Read the number and names of the parameter classes from the stream.
      // We have to do this so that we can find the correct overload of the
      // method.
      //
      int paramCount = streamReader.readInt();
      Class[] paramTypes = new Class[paramCount];
      for (int i = 0; i < paramTypes.length; i++) {
        String paramClassName = streamReader.readString();
        try {
          paramTypes[i] = getClassOrPrimitiveFromName(paramClassName);
        } catch (ClassNotFoundException e) {
          throw new SerializationException("Unknown parameter " + i + " type '"
              + paramClassName + "'", e);
        }
      }
  
  //    // For security, make sure the method is found in the service interface
  //    // and not just one that happens to be defined on this class.
  //    //
  //    Method serviceIntfMethod = findInterfaceMethod(serviceIntf, methodName,
  //        paramTypes, true);
  
  //    // If it wasn't found, don't continue.
  //    //
  //    if (serviceIntfMethod == null) {
  //      // Bad payload, possible hack attempt.
  //      //
  //      throw new SecurityException(
  //          "Method '"
  //              + methodName
  //              + "' (or a particular overload) on interface '"
  //              + serviceIntfName
  //              + "' was not found, this is either misconfiguration or a hack attempt");
  //    }
  
      // Deserialize the parameters.
      //
      Object[] args = new Object[paramCount];
      for (int i = 0; i < args.length; i++) {
        args[i] = streamReader.deserializeValue(paramTypes[i]);
      }
  
      GWTToSeamAdapter adapter = GWTToSeamAdapter.instance();    
      
      // Make the call via reflection.
      //
      String responsePayload = GENERIC_FAILURE_MSG;
      ServerSerializationStreamWriter streamWriter = new ServerSerializationStreamWriter(
          serializableTypeOracle);
      Throwable caught = null;
      try {
          ReturnedObject returnedObject = adapter.callWebRemoteMethod(serviceIntfName, methodName, paramTypes, args);
          Class returnType = returnedObject.returnType;
          Object returnVal = returnedObject.returnedObject;
  //      Class returnType = serviceIntfMethod.getReturnType();
  //      Object returnVal = serviceIntfMethod.invoke(this, args);
        responsePayload = createResponse(streamWriter, returnType, returnVal,
            false);
      } catch (IllegalArgumentException e) {
        caught = e;
      } catch (IllegalAccessException e) {
        caught = e;
      } catch (InvocationTargetException e) {
        // Try to serialize the caught exception if the client is expecting it,
        // otherwise log the exception server-side.
        caught = e;
        Throwable cause = e.getCause();
        if (cause != null) {
          // Update the caught exception to the underlying cause
          caught = cause;
          // Serialize the exception back to the client if it's a declared
          // exception
          if (cause instanceof SerializableException) {
            Class thrownClass = cause.getClass();
            responsePayload = createResponse(streamWriter, thrownClass, cause,
                true);
            // Don't log the exception on the server
            caught = null;
          }
        }
      }
  
      if (caught != null) {
        responsePayload = GENERIC_FAILURE_MSG;
        ServletContext servletContext = getServletContext();
        // servletContext may be null (for example, when unit testing)
        if (servletContext != null) {
          // Log the exception server side
          servletContext.log("Exception while dispatching incoming RPC call",
              caught);
        }
      }
  
      // Let subclasses see the serialized response.
      //
      onAfterResponseSerialized(responsePayload);
  
      return responsePayload;
    }
  
    /**
     * Gets the <code>HttpServletRequest</code> object for the current call. It
     * is stored thread-locally so that simultaneous invocations can have
     * different request objects.
     */
    protected final HttpServletRequest getThreadLocalRequest() {
      return (HttpServletRequest) perThreadRequest.get();
    }
  
    /**
     * Gets the <code>HttpServletResponse</code> object for the current call. It
     * is stored thread-locally so that simultaneous invocations can have
     * different response objects.
     */
    protected final HttpServletResponse getThreadLocalResponse() {
      return (HttpServletResponse) perThreadResponse.get();
    }
  
    /**
     * Override this method to examine the serialized response that will be
     * returned to the client. The default implementation does nothing and need
     * not be called by subclasses.
     */
    protected void onAfterResponseSerialized(String serializedResponse) {
    }
  
    /**
     * Override this method to examine the serialized version of the request
     * payload before it is deserialized into objects. The default implementation
     * does nothing and need not be called by subclasses.
     */
    protected void onBeforeRequestDeserialized(String serializedRequest) {
    }
  
    /**
     * Determines whether the response to a given servlet request should or should
     * not be GZIP compressed. This method is only called in cases where the
     * requestor accepts GZIP encoding.
     * <p>
     * This implementation currently returns <code>true</code> if the response
     * string's estimated byte length is longer than 256 bytes. Subclasses can
     * override this logic.
     * </p>
     * 
     * @param request the request being served
     * @param response the response that will be written into
     * @param responsePayload the payload that is about to be sent to the client
     * @return <code>true</code> if responsePayload should be GZIP compressed,
     *         otherwise <code>false</code>.
     */
    protected boolean shouldCompressResponse(HttpServletRequest request,
        HttpServletResponse response, String responsePayload) {
      return estimateByteSize(responsePayload) > UNCOMPRESSED_BYTE_SIZE_LIMIT;
    }
  
    /**
     * @param stream
     * @param responseType
     * @param responseObj
     * @param isException
     * @return response
     */
    private String createResponse(ServerSerializationStreamWriter stream,
        Class responseType, Object responseObj, boolean isException) {
      stream.prepareToWrite();
      if (responseType != void.class) {
        try {
          stream.serializeValue(responseObj, responseType);
        } catch (SerializationException e) {
          responseObj = e;
          isException = true;
        }
      }
  
      String bufferStr = (isException ? "{EX}" : "{OK}") + stream.toString();
      return bufferStr;
    }
  
    /**
     * Returns the {@link Class} instance for the named class.
     * 
     * @param name the name of a class or primitive type
     * @return Class instance for the given type name
     * @throws ClassNotFoundException if the named type was not found
     */
    private Class getClassFromName(String name) throws ClassNotFoundException {
      return Class.forName(name, false, this.getClass().getClassLoader());
    }
  
    /**
     * Returns the {@link Class} instance for the named class or primitive type.
     * 
     * @param name the name of a class or primitive type
     * @return Class instance for the given type name
     * @throws ClassNotFoundException if the named type was not found
     */
    private Class getClassOrPrimitiveFromName(String name)
        throws ClassNotFoundException {
      Object value = TYPE_NAMES.get(name);
      if (value != null) {
        return (Class) value;
      }
  
      return getClassFromName(name);
    }
  
    /**
     * Obtain the special package-prefixes we use to check for custom serializers
     * that would like to live in a package that they cannot. For example,
     * "java.util.ArrayList" is in a sealed package, so instead we use this prefix
     * to check for a custom serializer in
     * "com.google.gwt.user.client.rpc.core.java.util.ArrayList". Right now, it's
     * hard-coded because we don't have a pressing need for this mechanism to be
     * extensible, but it is imaginable, which is why it's implemented this way.
     */
    private String[] getPackagePaths() {
      return new String[] {"com.google.gwt.user.client.rpc.core"};
    }
  
    /**
     * Returns true if the {@link java.lang.reflect.Method Method} definition on
     * the service is specified to throw the exception contained in the
     * InvocationTargetException or false otherwise. NOTE we do not check that the
     * type is serializable here. We assume that it must be otherwise the
     * application would never have been allowed to run.
     * 
     * @param serviceIntfMethod
     * @param e
     * @return is expected exception
     */
    private boolean isExpectedException(Method serviceIntfMethod, Throwable cause) {
      assert (serviceIntfMethod != null);
      assert (cause != null);
  
      Class[] exceptionsThrown = serviceIntfMethod.getExceptionTypes();
      if (exceptionsThrown.length <= 0) {
        // The method is not specified to throw any exceptions
        //
        return false;
      }
  
      Class causeType = cause.getClass();
  
      for (int index = 0; index < exceptionsThrown.length; ++index) {
        Class exceptionThrown = exceptionsThrown[index];
        assert (exceptionThrown != null);
  
        if (exceptionThrown.isAssignableFrom(causeType)) {
          return true;
        }
      }
  
      return false;
    }
  
    /**
     * Used to determine whether the specified interface name is implemented by
     * this class without loading the class (for security).
     */
    private boolean isImplementedRemoteServiceInterface(String intfName) {
      synchronized (knownImplementedInterfaces) {
        // See if it's cached.
        //
        if (knownImplementedInterfaces.contains(intfName)) {
          return true;
        }
  
        Class cls = getClass();
  
        // Unknown, so walk up the class hierarchy to find the first class that
        // implements the requested interface
        //
        while ((cls != null) && !GWTRemoteService.class.equals(cls)) {
          Class[] intfs = cls.getInterfaces();
          for (int i = 0; i < intfs.length; i++) {
            Class intf = intfs[i];
            if (isImplementedRemoteServiceInterfaceRecursive(intfName, intf)) {
              knownImplementedInterfaces.add(intfName);
              return true;
            }
          }
  
          // did not find the interface in this class so we look in the
          // superclass
          cls = cls.getSuperclass();
        }
  
        return false;
      }
    }
  
    /**
     * Only called from isImplementedInterface().
     */
    private boolean isImplementedRemoteServiceInterfaceRecursive(String intfName,
        Class intfToCheck) {
      assert (intfToCheck.isInterface());
  
      if (intfToCheck.getName().equals(intfName)) {
        // The name is right, but we also verify that it is assignable to
        // RemoteService.
        // 
        if (RemoteService.class.isAssignableFrom(intfToCheck)) {
          return true;
        } else {
          return false;
        }
      }
  
      Class[] intfs = intfToCheck.getInterfaces();
      for (int i = 0; i < intfs.length; i++) {
        Class intf = intfs[i];
        if (isImplementedRemoteServiceInterfaceRecursive(intfName, intf)) {
          return true;
        }
      }
  
      return false;
    }
  
    private String readPayloadAsUtf8(HttpServletRequest request)
        throws IOException, ServletException {
      int contentLength = request.getContentLength();
      if (contentLength == -1) {
        // Content length must be known.
        throw new ServletException("Content-Length must be specified");
      }
  
      String contentType = request.getContentType();
      boolean contentTypeIsOkay = false;
      // Content-Type must be specified.
      if (contentType != null) {
        // The type must be plain text.
        if (contentType.startsWith("text/plain")) {
          // And it must be UTF-8 encoded (or unspecified, in which case we assume
          // that it's either UTF-8 or ASCII).
          if (contentType.indexOf("charset=") == -1) {
            contentTypeIsOkay = true;
          } else if (contentType.indexOf("charset=utf-8") != -1) {
            contentTypeIsOkay = true;
          }
        }
      }
      if (!contentTypeIsOkay) {
        throw new ServletException(
            "Content-Type must be 'text/plain' with 'charset=utf-8' (or unspecified charset)");
      }
      InputStream in = request.getInputStream();
      try {
        byte[] payload = new byte[contentLength];
        int offset = 0;
        int len = contentLength;
        int byteCount;
        while (offset < contentLength) {
          byteCount = in.read(payload, offset, len);
          if (byteCount == -1) {
            throw new ServletException("Client did not send " + contentLength
                + " bytes as expected");
          }
          offset += byteCount;
          len -= byteCount;
        }
        return new String(payload, "UTF-8");
      } finally {
        if (in != null) {
          in.close();
        }
      }
    }
  
    /**
     * Called when the machinery of this class itself has a problem, rather than
     * the invoked third-party method. It writes a simple 500 message back to the
     * client.
     */
    private void respondWithFailure(HttpServletResponse response, Throwable caught) {
      ServletContext servletContext = getServletContext();
      servletContext.log("Exception while dispatching incoming RPC call", caught);
      try {
        response.setContentType("text/plain");
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        response.getWriter().write(GENERIC_FAILURE_MSG);
      } catch (IOException e) {
        servletContext.log(
            "sendError() failed while sending the previous failure to the client",
            caught);
      }
    }
  
    private void writeResponse(HttpServletRequest request,
        HttpServletResponse response, String responsePayload) throws IOException {
  
      byte[] reply = responsePayload.getBytes(CHARSET_UTF8);
      String contentType = CONTENT_TYPE_TEXT_PLAIN_UTF8;
  
      if (acceptsGzipEncoding(request)
          && shouldCompressResponse(request, response, responsePayload)) {
        // Compress the reply and adjust headers.
        //
        ByteArrayOutputStream output = null;
        GZIPOutputStream gzipOutputStream = null;
        Throwable caught = null;
        try {
          output = new ByteArrayOutputStream(reply.length);
          gzipOutputStream = new GZIPOutputStream(output);
          gzipOutputStream.write(reply);
          gzipOutputStream.finish();
          gzipOutputStream.flush();
          response.setHeader(CONTENT_ENCODING, CONTENT_ENCODING_GZIP);
          reply = output.toByteArray();
        } catch (UnsupportedEncodingException e) {
          caught = e;
        } catch (IOException e) {
          caught = e;
        } finally {
          if (null != gzipOutputStream) {
            gzipOutputStream.close();
          }
          if (null != output) {
            output.close();
          }
        }
  
        if (caught != null) {
          getServletContext().log("Unable to compress response", caught);
          response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
          return;
        }
      }
  
      // Send the reply.
      //
      response.setContentLength(reply.length);
      response.setContentType(contentType);
      response.setStatus(HttpServletResponse.SC_OK);
      response.getOutputStream().write(reply);
    }
  }
  
  
  
  
  1.1      date: 2007/04/15 03:40:02;  author: sbryzak2;  state: Exp;jboss-seam/src/remoting/org/jboss/seam/remoting/gwt/GWTToSeamAdapter.java
  
  Index: GWTToSeamAdapter.java
  ===================================================================
  package org.jboss.seam.remoting.gwt;
  
  import static org.jboss.seam.InterceptionType.NEVER;
  import static org.jboss.seam.ScopeType.APPLICATION;
  import static org.jboss.seam.annotations.Install.BUILT_IN;
  
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.util.HashMap;
  import java.util.Map;
  
  import org.jboss.seam.Component;
  import org.jboss.seam.annotations.Install;
  import org.jboss.seam.annotations.Intercept;
  import org.jboss.seam.annotations.Name;
  import org.jboss.seam.annotations.Scope;
  import org.jboss.seam.annotations.WebRemote;
  import org.jboss.seam.util.EJB;
  
  /**
   * This class adapts GWT RPC mechanism to Seam actions.
   * 
   * @author Michael Neale
   */
  @Scope(APPLICATION)
  @Name("org.jboss.seam.remoting.gwt.gwtToSeamAdapter")
  @Intercept(NEVER)
  @Install(value = false, precedence=BUILT_IN)
  public class GWTToSeamAdapter {
  
     /** A very simple cache of previously looked up methods */
     private final Map METHOD_CACHE = new HashMap();
     
     public static GWTToSeamAdapter instance()
     {
        GWTToSeamAdapter adapter = (GWTToSeamAdapter) Component.getInstance(GWTToSeamAdapter.class, true);
  
       if (adapter == null)
       {
         throw new IllegalStateException("No GWTToSeamAdapter exists");
       }
  
       return adapter;
     }   
  
     /**
      * Call the service.
      * 
      * @param serviceIntfName
      *            The interface name - this will be the fully qualified name of
      *            the remote service interface as understood by GWT. This
      *            correlates to a component name in seam.
      * @param methodName
      *            The method name of the service being invoked.
      * @param paramTypes
      *            The types of parameters - needed for method lookup for
      *            polymorphism.
      * @param args
      *            The values to be passed to the service method.
      * @return A populated ReturnedObject - the returned object payload may be
      *         null, but the type will not be.
      * @throws InvocationTargetException
      * @throws IllegalAccessException
      */
     public ReturnedObject callWebRemoteMethod(String serviceIntfName,
           String methodName, Class[] paramTypes, Object[] args)
           throws InvocationTargetException, IllegalAccessException,
           SecurityException {
  
        // Find the component we're calling
        Component component = Component.forName(serviceIntfName);
  
        if (component == null)
           throw new RuntimeException("No such component: " + serviceIntfName);
  
        Object instance = getServiceComponent(serviceIntfName);
        Class clz = null;
  
        if (component.getType().isSessionBean()
              && component.getBusinessInterfaces().size() > 0) {
           for (Class c : component.getBusinessInterfaces()) {
              if (c.isAnnotationPresent(EJB.LOCAL)) {
                 clz = c;
                 break;
              }
           }
  
           if (clz == null)
              throw new RuntimeException(
                    String
                          .format(
                                "Type cannot be determined for component [%s]. Please ensure that it has a local interface.",
                                component));
        }
  
        if (clz == null)
           clz = component.getBeanClass();
  
        Method method = getMethod(serviceIntfName, methodName, clz, paramTypes);
  
        Object result = method.invoke(instance, args);
        return new ReturnedObject(method.getReturnType(), result);
     }
  
     /**
      * Get the method on the class, including walking up the class heirarchy if
      * needed. Methods have to be marked as "@WebRemote" to be allowed.
      * 
      * @param methodName
      * @param clz
      * @param paramTypes
      * @return
      */
     private Method getMethod(String serviceName, String methodName, Class clz,
           Class[] paramTypes) {
        String key = getKey(serviceName, methodName, paramTypes);
        if (METHOD_CACHE.containsKey(key)) {
           return (Method) METHOD_CACHE.get(key);
        } else {
           try {
              synchronized (METHOD_CACHE) {
                 Method m = findMethod(clz, methodName, paramTypes);
                 if (m == null)
                    throw new NoSuchMethodException();
                 METHOD_CACHE.put(key, m);
                 return m;
              }
  
           } catch (NoSuchMethodException e) {
              throw new SecurityException(
                    "Unable to access a service method called ["
                          + methodName
                          + "] on class ["
                          + clz.getName()
                          + "] without the @WebRemote attribute. "
                          + "This may be a hack attempt, or someone simply neglected to use the @WebRemote attribute to indicate a method as"
                          + " remotely accessible.");
           }
        }
     }
  
     private String getKey(String serviceName, String methodName,
           Class[] paramTypes) {
        if (paramTypes == null) {
           return serviceName + "." + methodName;
        } else {
           String pTypes = "";
           for (int i = 0; i < paramTypes.length; i++) {
              pTypes += paramTypes[i].getName();
           }
           return serviceName + "." + methodName + "(" + pTypes + ")";
        }
  
     }
  
     /**
      * Recurse up the class hierarchy, looking for a compatable method that is
      * marked as "@WebRemote". If one is not found (or we hit Object.class) then
      * we barf - basically trust nothing from the client other then what we want
      * to allow them to call.
      */
     private Method findMethod(Class clz, String methodName, Class[] paramTypes)
           throws NoSuchMethodException {
        if (clz == Object.class) {
           return null;
        } else {
           Method m = clz.getMethod(methodName, paramTypes);
           if (isWebRemoteAnnotated(m)) {
              return m;
           } else {
              return findMethod(clz.getSuperclass(), methodName, paramTypes);
           }
        }
     }
  
     /**
      * Only allow methods annotated with
      * 
      * @WebRemote for security reasons.
      */
     private boolean isWebRemoteAnnotated(Method method) {
        if (method == null)
           return false;
        return method.getAnnotation(WebRemote.class) != null;
     }
  
     /**
      * Return the service component that has been bound to the given name.
      */
     protected Object getServiceComponent(String serviceIntfName) {
        return Component.getInstance(serviceIntfName);
     }
  
     /**
      * This is used for returning results to the GWT service endpoint. The class
      * is needed even if the result is null. a void.class responseType is
      * perfectly acceptable.
      * 
      * @author Michael Neale
      */
     static class ReturnedObject {
        public ReturnedObject(Class type, Object result) {
           this.returnType = type;
           this.returnedObject = result;
        }
  
        public Class returnType;
  
        public Object returnedObject;
     }
  
  }
  
  
  



More information about the jboss-cvs-commits mailing list