Package org.jboss.ejb3.async.impl.interceptor

Source Code of org.jboss.ejb3.async.impl.interceptor.AsynchronousClientInterceptor$AsyncInvocationTask

/*
* JBoss, Home of Professional Open Source.
* Copyright 2009, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
  *
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ejb3.async.impl.interceptor;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URI;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import org.jboss.aop.Dispatcher;
import org.jboss.aop.advice.Interceptor;
import org.jboss.aop.joinpoint.Invocation;
import org.jboss.aop.joinpoint.MethodInvocation;
import org.jboss.aop.util.PayloadKey;
import org.jboss.aspects.remoting.InvokeRemoteInterceptor;
import org.jboss.aspects.remoting.PojiProxy;
import org.jboss.ejb3.async.impl.AsyncInvocationIdUUIDImpl;
import org.jboss.ejb3.async.impl.ClientExecutorService;
import org.jboss.ejb3.async.impl.util.concurrent.ResultUnwrappingExecutorService;
import org.jboss.ejb3.async.spi.AsyncCancellableContext;
import org.jboss.ejb3.async.spi.AsyncInvocation;
import org.jboss.ejb3.async.spi.AsyncInvocationContext;
import org.jboss.ejb3.async.spi.AsyncInvocationId;
import org.jboss.logging.Logger;
import org.jboss.metadata.ejb.spec.AsyncMethodMetaData;
import org.jboss.metadata.ejb.spec.AsyncMethodsMetaData;
import org.jboss.metadata.ejb.spec.MethodParametersMetaData;
import org.jboss.remoting.InvokerLocator;
import org.jboss.security.SecurityContext;

/**
* Examines invocation metadata to determine if this
* should be handled asynchronously; if so, short-circuits and
* spawns off into a new Thread.
*
* If the invocation has been equipped with an {@link AsyncInvocationContext}
* (ie. is of type {@link AsyncInvocation}), the associated {@link ExecutorService}
* will be used.  Else we'll provide an {@link ExecutorService}
* implementation on behalf of the client.
*
* @author <a href="mailto:andrew.rubinger@jboss.org">ALR</a>
* @version $Revision: $
*/
public class AsynchronousClientInterceptor implements Interceptor, Serializable
{

   // --------------------------------------------------------------------------------||
   // Class Members ------------------------------------------------------------------||
   // --------------------------------------------------------------------------------||

   /**
    * serialVersionUID
    */
   private static final long serialVersionUID = 1L;

   /**
    * Logger
    */
   private static final Logger log = Logger.getLogger(AsynchronousClientInterceptor.class);

   /*
    * Metadata attachments flagging this invocation's already been dispatched
    */
   private static final String INVOCATION_METADATA_TAG = "ASYNC";

   private static final String INVOCATION_METADATA_ATTR = "BEEN_HERE";

   private static final String INVOCATION_METADATA_VALUE = Boolean.TRUE.toString();

   // --------------------------------------------------------------------------------||
   // Instance Members ---------------------------------------------------------------||
   // --------------------------------------------------------------------------------||

   /**
    * Asynchronous Methods to be handled by this interceptor
    */
   private final AsyncMethodsMetaData asyncMethods;

   // --------------------------------------------------------------------------------||
   // Constructor --------------------------------------------------------------------||
   // --------------------------------------------------------------------------------||

   /**
    * Constructor
    */
   public AsynchronousClientInterceptor(final AsyncMethodsMetaData asyncMethods)
   {
      assert asyncMethods != null : "Async Methods must be supplied";
      this.asyncMethods = asyncMethods;
      log.debug("Created: " + this + " to handle " + asyncMethods);
   }

   // --------------------------------------------------------------------------------||
   // Required Implementations -------------------------------------------------------||
   // --------------------------------------------------------------------------------||

   /**
    * {@inheritDoc}
    * @see org.jboss.aop.advice.Interceptor#getName()
    */
   public String getName()
   {
      return this.getClass().getSimpleName();
   }

   /**
    * {@inheritDocs}
    * @see org.jboss.aop.advice.Interceptor#invoke(org.jboss.aop.joinpoint.Invocation)
    */
   public Object invoke(final Invocation invocation) throws Throwable
   {
      // If asynchronous
      if (this.isAsyncInvocation(invocation))
      {
         // Spawn
         return this.invokeAsync(invocation);
      }
      // Regular synchronous call
      else
      {
         // Continue along the chain
         return invocation.invokeNext();
      }
   }

   // --------------------------------------------------------------------------------||
   // Internal Helper Methods --------------------------------------------------------||
   // --------------------------------------------------------------------------------||

   /**
    * Breaks off the specified invocation into
    * a queue for asynchronous processing, returning
    * a handle to the task
    */
   private Future<?> invokeAsync(final Invocation invocation)
   {
      // Get the appropriate ExecutorService
      final ExecutorService executorService = this.getAsyncExecutor(invocation);

      // Get the existing SecurityContext
      final SecurityContext sc = SecurityActions.getSecurityContext();

      // Copy the invocation (must be done for Thread safety, as we spawn this off and
      // subsequent calls can mess with the internal interceptor index)
      final Invocation nextInvocation = invocation.copy();

      // Mark that we've already been async'd, so when the invocation comes around again we don't infinite loop
      nextInvocation.getMetaData().addMetaData(INVOCATION_METADATA_TAG, INVOCATION_METADATA_ATTR,
            INVOCATION_METADATA_VALUE);

      // Make a new ID for the invocation
      final AsyncInvocationId id = new AsyncInvocationIdUUIDImpl();

      // Make the asynchronous task from the invocation
      final Callable<Object> asyncTask = new AsyncInvocationTask<Object>(nextInvocation, sc, id);

      // Short-circuit the invocation into new Thread
      final Future<Object> task;
      try
      {
         // Mark the thread w/ the UUID so it can be picked up by the ES during submit, and stuffed into the Future
         CurrentAsyncInvocation.markCurrentInvocation(id, nextInvocation);
         task = executorService.submit(asyncTask);
         if (log.isTraceEnabled())
         {
            log.trace("Submitting async invocation " + invocation + " via " + executorService);
         }
      }
      finally
      {
         // Clear the Thread
         CurrentAsyncInvocation.unmarkCurrentInvocationFromThread();
      }

      // Return
      return task;
   }

   /**
    * Determines whether the specified invocation is asynchronous
    * by inspecting its metadata
    *
    * EJB 3.1 4.5.2.2
    */
   private boolean isAsyncInvocation(final Invocation invocation)
   {
      // Precondition check
      if (log.isTraceEnabled())
      {
         log.trace("Checking to see if async: " + invocation);
      }
      assert invocation instanceof MethodInvocation : this.getClass().getName() + " supports only "
            + MethodInvocation.class.getSimpleName() + ", but has been passed: " + invocation;
      final MethodInvocation si = (MethodInvocation) invocation;

      //       See if we've already been here, if so, don't handle as async
      final String beenHere = (String) invocation.getMetaData().getMetaData(INVOCATION_METADATA_TAG,
            INVOCATION_METADATA_ATTR);
      if (beenHere != null && beenHere.equals(INVOCATION_METADATA_VALUE))
      {
         // Do not handle
         if (log.isTraceEnabled())
         {
            log.trace("Been here, not dispatching as async again");
         }
         return false;
      }

      // Get the actual method
      final Method actualMethod = si.getActualMethod();

      // Loop through the declared async methods for this EJB
      for (final AsyncMethodMetaData asyncMethod : asyncMethods)
      {
         // Name matches?
         final String invokedMethodName = actualMethod.getName();
         if (invokedMethodName.equals(asyncMethod.getMethodName()))
         {
            if (log.isTraceEnabled())
            {
               log.trace("Async method names match: " + invokedMethodName);
            }

            // Params match?
            final MethodParametersMetaData asyncParams = asyncMethod.getMethodParams();
            final Class<?>[] invokedParams = actualMethod.getParameterTypes();
            final int invokedParamsSize = invokedParams.length;
            if (asyncParams.size() != invokedParams.length)
            {
               if (log.isTraceEnabled())
               {
                  log.trace("Different async params size, no match");
               }
               return false;
            }
            for (int i = 0; i < invokedParamsSize; i++)
            {
               final String invokedParamTypeName = invokedParams[i].getName();
               final String declaredName = asyncParams.get(i);
               if (!invokedParamTypeName.equals(declaredName))
               {
                  return false;
               }
            }

            // Name and params all match
            if (log.isTraceEnabled())
            {
               log.trace("Dispatching as @Asynchronous: " + actualMethod);
            }
            return true;
         }
      }

      // Not async
      if (log.isTraceEnabled())
      {
         log.trace("Not @Asynchronous: " + invocation);
      }
      return false;
   }

   /**
    * Obtains an appropriate {@link ExecutorService} to handle the invocation
    * based upon the type of {@link Invocation} provided.  If we're got a
    * {@link AsyncInvocation}, the associated {@link ExecutorService} will be used,
    * else we'll supply a default one.
    *
    * @param invocation
    * @return
    */
   private ExecutorService getAsyncExecutor(final Invocation invocation)
   {
      // Precondition checks
      assert invocation != null : "Invocation must be specified";
     
      // If this invocation has been equipped with an associated ES
      if (invocation instanceof AsyncInvocation)
      {

         // Cast
         final AsyncInvocation asyncInvocation = (AsyncInvocation) invocation;

         // Get out the ES
         final AsyncInvocationContext context = asyncInvocation.getAsyncInvocationContext();
         assert context != null : "async invocation context of " + invocation + " was null";
         final ExecutorService es = context.getAsynchronousExecutor();
         assert es != null : ExecutorService.class.getSimpleName() + " associated with " + context + " was null";
         return es;

      }
      // Supply our own ES for the client
      else
      {
         final InvokerLocator locator = (InvokerLocator) invocation.getMetaData(InvokeRemoteInterceptor.REMOTING,
               InvokeRemoteInterceptor.INVOKER_LOCATOR);
         Object oid = invocation.getMetaData().getMetaData(Dispatcher.DISPATCHER, Dispatcher.OID);
         final PojiProxy proxy = new PojiProxy(oid, locator, new Interceptor[]{});
         final AsyncCancellableContext container = (AsyncCancellableContext) Proxy.newProxyInstance(Thread
               .currentThread().getContextClassLoader(), new Class<?>[]
         {AsyncCancellableContext.class}, proxy);
         return new ResultUnwrappingExecutorService(ClientExecutorService.INSTANCE, container);
      }
   }

   // --------------------------------------------------------------------------------||
   // Inner Classes ------------------------------------------------------------------||
   // --------------------------------------------------------------------------------||

   /**
    * Task to invoke the held invocation in a new Thread, either
    * returning the result or throwing the generated Exception
    */
   private class AsyncInvocationTask<V> implements Callable<V>
   {
      private final Invocation invocation;

      /**
       * SecurityContext to use for the invocation
       */
      private final SecurityContext sc;

      /**
       * ID of the invocation
       */
      private final AsyncInvocationId id;

      public AsyncInvocationTask(final Invocation invocation, final SecurityContext sc, final AsyncInvocationId id)
      {
         assert invocation != null : "Invocation must be supplied";
         assert id != null : "Async Invocation ID must be supplied";
         this.invocation = invocation;
         this.sc = sc;
         this.id = id;
      }

      @SuppressWarnings("unchecked")
      public V call() throws Exception
      {
         // Get existing security context
         final SecurityContext oldSc = SecurityActions.getSecurityContext();

         try
         {
            // Set new sc
            SecurityActions.setSecurityContext(this.sc);

            // Mark the current invocation both on the executing Thread and the Invocation
            CurrentAsyncInvocation.markCurrentInvocation(this.id, this.invocation);

            // Invoke
            return (V) invocation.invokeNext();
         }
         catch (Exception e)
         {
            throw e;
         }
         catch (Error e)
         {
            throw e;
         }
         catch (Throwable t)
         {
            throw new Error(t);
         }
         finally
         {
            // Replace the old security context
            SecurityActions.setSecurityContext(oldSc);

            // Unmark the current invocation both on the executing Thread and the Invocation
            CurrentAsyncInvocation.unmarkCurrentInvocation(this.invocation);
         }
      }

   }

}
TOP

Related Classes of org.jboss.ejb3.async.impl.interceptor.AsynchronousClientInterceptor$AsyncInvocationTask

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.