org.openspaces.remoting.SpaceRemotingServiceExporter.java Source code

Java tutorial

Introduction

Here is the source code for org.openspaces.remoting.SpaceRemotingServiceExporter.java

Source

/*
 * Copyright (c) 2008-2016, GigaSpaces Technologies, Inc. All Rights Reserved.
 *
 * Licensed 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.openspaces.remoting;

import com.gigaspaces.internal.reflection.IMethod;
import com.gigaspaces.internal.reflection.ReflectionUtil;
import com.gigaspaces.internal.reflection.standard.StandardMethod;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jini.rio.boot.ServiceClassLoader;
import org.openspaces.core.GigaSpace;
import org.openspaces.core.cluster.ClusterInfo;
import org.openspaces.core.cluster.ClusterInfoAware;
import org.openspaces.events.EventTemplateProvider;
import org.openspaces.events.SpaceDataEventListener;
import org.openspaces.pu.service.ServiceDetails;
import org.openspaces.pu.service.ServiceDetailsProvider;
import org.openspaces.pu.service.ServiceMonitors;
import org.openspaces.pu.service.ServiceMonitorsProvider;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.remoting.RemoteLookupFailureException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.util.Assert;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Exports a list of services (beans) as remote services with the Space as the transport layer. All
 * the interfaces each service implements are registered as lookup names (matching {@link
 * SpaceRemotingInvocation#getLookupName()} which are then used to lookup the actual service when a
 * remote invocation is received. The correct service and its method are then executed and a {@link
 * org.openspaces.remoting.SpaceRemotingResult} is written back to the space. The remote result can
 * either hold the return value (or <code>null</code> in case of void return value) or an exception
 * that was thrown by the service.
 *
 * <p>The exporter implements {@link org.openspaces.events.SpaceDataEventListener} which means that
 * it acts as a listener to data events and should be used with the different event containers such
 * as {@link org.openspaces.events.polling.SimplePollingEventListenerContainer}. This method of
 * execution is called <b>event driven</b> remote execution ({@link EventDrivenSpaceRemotingProxyFactoryBean}).
 *
 * <p>It also implements {@link org.openspaces.events.EventTemplateProvider} which means that within
 * the event container configuration there is no need to configure the template, as it uses the one
 * provided by this exported.
 *
 * <p>Last, the exporter provides services to executor based remoting ({@link
 * org.openspaces.remoting.ExecutorSpaceRemotingProxyFactoryBean}).
 *
 * <p>By default, the exporter will also autowire and post process all the arguments passed,
 * allowing to inject them with "server" side beans using Spring {@link
 * org.springframework.beans.factory.annotation.Autowired} annotation for example. Note, this
 * variables must be defined as <code>transient</code> so they won't be passed back to the client.
 * This can be disabled by setting {@link #setDisableAutowiredArguments(boolean)} to
 * <code>true</code>.
 *
 * @author kimchy
 * @see org.openspaces.events.polling.SimplePollingEventListenerContainer
 * @see SpaceRemotingEntry
 * @see EventDrivenSpaceRemotingProxyFactoryBean
 */
public class SpaceRemotingServiceExporter implements SpaceDataEventListener<SpaceRemotingEntry>, InitializingBean,
        ApplicationContextAware, BeanNameAware, EventTemplateProvider, ClusterInfoAware, ApplicationListener,
        ServiceDetailsProvider, ServiceMonitorsProvider {

    public static final String DEFAULT_ASYNC_INTERFACE_SUFFIX = "Async";

    private static final Log logger = LogFactory.getLog(SpaceRemotingServiceExporter.class);

    final private SpaceRemotingEntryFactory remotingEntryFactory = new SpaceRemotingEntryMetadataFactory();

    private List<Object> services = new ArrayList<Object>();

    final private List<ServiceInfo> servicesInfo = new ArrayList<ServiceInfo>();

    private boolean useFastReflection = true;

    final private IdentityHashMap<Object, ServiceInfo> serviceToServiceInfoMap = new IdentityHashMap<Object, ServiceInfo>();

    final private AtomicLong processed = new AtomicLong();

    final private AtomicLong failed = new AtomicLong();

    private ApplicationContext applicationContext;

    private String beanName;

    final private Map<String, Object> interfaceToService = new HashMap<String, Object>();

    private String asyncInterfaceSuffix = DEFAULT_ASYNC_INTERFACE_SUFFIX;

    private boolean fifo = false;

    private boolean disableAutowiredArguments = false;

    private ServiceExecutionAspect serviceExecutionAspect;

    private String templateLookupName;

    private ClusterInfo clusterInfo;

    private Map<String, Map<RemotingUtils.MethodHash, IMethod>> methodInvocationLookup;

    // for backward comp
    final private MethodInvocationCache methodInvocationCache = new MethodInvocationCache();

    private volatile boolean initialized = false;

    final private CountDownLatch initializationLatch = new CountDownLatch(1);

    /**
     * Sets the list of services that will be exported as remote services. Each service will have
     * all of its interfaces registered as lookups (mapping to {@link SpaceRemotingEntry#getLookupName()}
     * which will then be used to invoke the correct service.
     */
    public void setServices(List<Object> services) {
        this.services = services;
    }

    /**
     * For async based execution of remote services, this is one of the options to enable this by
     * using two different interfaces. The first is the actual "server side" interface (sync), and
     * the other has the same interface name just with an "async suffix" to it. The exporter will
     * identify the async suffix, and will perform the invocation on the actual interface.
     *
     * <p>This setter allows to set the async suffix which by default is <code>Async</code>.
     */
    public void setAsyncInterfaceSuffix(String asyncInterfaceSuffix) {
        this.asyncInterfaceSuffix = asyncInterfaceSuffix;
    }

    /**
     * Sets the template used to read async invocation (the {@link SpaceRemotingEntry}) to be fifo.
     * Works in with setting the {@link EventDrivenSpaceRemotingProxyFactoryBean} fifo flag to
     * <code>true</code> and allows for remoting to work in fifo mode without needing to set the
     * whole Space to work in fifo mode.
     */
    public void setFifo(boolean fifo) {
        this.fifo = fifo;
    }

    /**
     * Controls if executing the service should use fast reflection or not.
     */
    public void setUseFastReflection(boolean userFastReflection) {
        this.useFastReflection = userFastReflection;
    }

    /**
     * Allows to disable (by default it is enabled) the autowiring of method arguments with beans
     * that exists within the server side context.
     */
    public void setDisableAutowiredArguments(boolean disableAutowiredArguments) {
        this.disableAutowiredArguments = disableAutowiredArguments;
    }

    /**
     * Allows to inject a service execution callback.
     */
    public void setServiceExecutionAspect(ServiceExecutionAspect serviceExecutionAspect) {
        this.serviceExecutionAspect = serviceExecutionAspect;
    }

    /**
     * Allows to narrow down the async polling container to perform a lookup only on specific lookup
     * name (which is usually the interface that will be used to proxy it on the client side).
     * Defaults to match on all async remoting invocations.
     *
     * <p>This option allows to create several polling container, each for different service that
     * will perform the actual invocation.
     */
    public void setTemplateLookupName(String templateLookupName) {
        this.templateLookupName = templateLookupName;
    }

    /**
     * Application context injected by Spring
     */
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    public void setBeanName(String name) {
        this.beanName = name;
    }

    /**
     * Cluster Info injected
     */
    public void setClusterInfo(ClusterInfo clusterInfo) {
        this.clusterInfo = clusterInfo;
    }

    public void addService(String beanId, Object service) throws IllegalStateException {
        if (initialized) {
            throw new IllegalStateException("Can't add a service once the exporter has initialized");
        }
        this.servicesInfo.add(new ServiceInfo(beanId, service.getClass().getName(), service));
    }

    public void afterPropertiesSet() throws Exception {
        if (beanName == null) {
            beanName = "serviceExporter";
        }
    }

    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        if (applicationEvent instanceof ContextRefreshedEvent) {
            Assert.notNull(services, "services property is required");
            ClassLoader origClassLoader = Thread.currentThread().getContextClassLoader();
            if (origClassLoader instanceof ServiceClassLoader
                    && origClassLoader.getParent() instanceof ServiceClassLoader) {
                // Since the refreshable context causes the ContextRefreshEvent as well, under its own new class loader
                Thread.currentThread().setContextClassLoader(origClassLoader.getParent());
            }
            try {
                // go over the services and create the interface to service lookup
                int naCounter = 0;
                for (Object service : services) {
                    if (service instanceof ServiceRef) {
                        String ref = ((ServiceRef) service).getRef();
                        service = applicationContext.getBean(ref);
                        this.servicesInfo.add(new ServiceInfo(ref, service.getClass().getName(), service));
                    } else {
                        this.servicesInfo
                                .add(new ServiceInfo("NA" + (++naCounter), service.getClass().getName(), service));
                    }
                }
                methodInvocationLookup = new HashMap<String, Map<RemotingUtils.MethodHash, IMethod>>();
                for (ServiceInfo serviceInfo : servicesInfo) {
                    Set<Class> interfaces = ReflectionUtil
                            .getAllInterfacesForClassAsSet(serviceInfo.getService().getClass());
                    for (Class<?> anInterface : interfaces) {
                        interfaceToService.put(anInterface.getName(), serviceInfo.getService());
                        methodInvocationLookup.put(anInterface.getName(),
                                RemotingUtils.buildHashToMethodLookupForInterface(anInterface, useFastReflection));
                        // for backward comp
                        methodInvocationCache.addService(anInterface, serviceInfo.getService(), useFastReflection);
                    }

                    serviceToServiceInfoMap.put(serviceInfo.getService(), serviceInfo);
                }
                initialized = true;
                initializationLatch.countDown();
            } finally {
                Thread.currentThread().setContextClassLoader(origClassLoader);
            }
        }
    }

    /**
     * The template used for receiving events. Defaults to all objects that are of type {@link
     * SpaceRemotingEntry}.
     */
    public Object getTemplate() {
        SpaceRemotingEntry remotingEntry = remotingEntryFactory.createEntry();
        remotingEntry.setInvocation(Boolean.TRUE);
        remotingEntry.setFifo(fifo);
        remotingEntry.setLookupName(templateLookupName);
        if (logger.isDebugEnabled()) {
            logger.debug("Registering async remoting service template [" + remotingEntry + "]");
        }
        return remotingEntry;
    }

    public ServiceDetails[] getServicesDetails() {
        ArrayList<RemotingServiceDetails.RemoteService> remoteServices = new ArrayList<RemotingServiceDetails.RemoteService>();
        for (ServiceInfo serviceInfo : servicesInfo) {
            remoteServices.add(
                    new RemotingServiceDetails.RemoteService(serviceInfo.getBeanId(), serviceInfo.getClassName()));
        }
        return new ServiceDetails[] { new RemotingServiceDetails(beanName,
                remoteServices.toArray(new RemotingServiceDetails.RemoteService[remoteServices.size()])) };
    }

    public ServiceMonitors[] getServicesMonitors() {
        ArrayList<RemotingServiceMonitors.RemoteServiceStats> remoteServiceStats = new ArrayList<RemotingServiceMonitors.RemoteServiceStats>();
        for (ServiceInfo serviceInfo : servicesInfo) {
            remoteServiceStats.add(new RemotingServiceMonitors.RemoteServiceStats(serviceInfo.getBeanId(),
                    serviceInfo.getProcessed().get(), serviceInfo.getFailures().get()));
        }
        return new ServiceMonitors[] {
                new RemotingServiceMonitors(beanName, processed.get(), failed.get(), remoteServiceStats
                        .toArray(new RemotingServiceMonitors.RemoteServiceStats[remoteServiceStats.size()])) };
    }

    /**
     * Receives a {@link SpaceRemotingEntry} which holds all the relevant invocation information.
     * Looks up (based on {@link SpaceRemotingEntry#getLookupName()} the interface the service is
     * registered against (which is the interface the service implements) and then invokes the
     * relevant method within it using the provided method name and arguments. Write the result
     * value or invocation exception back to the space using {@link SpaceRemotingEntry}.
     *
     * @param remotingEntry The remote entry object
     * @param gigaSpace     The GigaSpace interface
     * @param txStatus      A transactional status
     * @param source        An optional source event information
     */
    public void onEvent(SpaceRemotingEntry remotingEntry, GigaSpace gigaSpace, TransactionStatus txStatus,
            Object source) throws RemoteAccessException {

        waitTillInitialized();

        String lookupName = remotingEntry.getLookupName();
        if (lookupName.endsWith(asyncInterfaceSuffix)) {
            lookupName = lookupName.substring(0, lookupName.length() - asyncInterfaceSuffix.length());
        }

        Object service = interfaceToService.get(lookupName);
        if (service == null) {
            // we did not get an interface, maybe it is a bean name?
            try {
                service = applicationContext.getBean(lookupName);
            } catch (NoSuchBeanDefinitionException e) {
                // do nothing, write back a proper exception
            }
            if (service == null) {
                writeResponse(gigaSpace, remotingEntry, new RemoteLookupFailureException(
                        "Failed to find service for lookup [" + remotingEntry.getLookupName() + "]"));
                return;
            }
        }

        autowireArguments(service, remotingEntry.getArguments());

        IMethod method = null;
        try {
            if (remotingEntry instanceof HashedSpaceRemotingEntry
                    && ((HashedSpaceRemotingEntry) remotingEntry).getMethodHash() != null) {
                method = methodInvocationLookup.get(lookupName)
                        .get(((HashedSpaceRemotingEntry) remotingEntry).getMethodHash());
            }
            if (method == null) {
                method = methodInvocationCache.findMethod(lookupName, service, remotingEntry.getMethodName(),
                        remotingEntry.getArguments());
            }
        } catch (Exception e) {
            failedExecution(service);
            writeResponse(gigaSpace, remotingEntry, new RemoteLookupFailureException("Failed to find method ["
                    + remotingEntry.getMethodName() + "] for lookup [" + remotingEntry.getLookupName() + "]", e));
            return;
        }
        try {
            Object retVal;
            if (serviceExecutionAspect != null) {
                retVal = serviceExecutionAspect.invoke(remotingEntry, new InternalMethodInvocation(method),
                        service);
            } else {
                retVal = method.invoke(service, remotingEntry.getArguments());
            }
            writeResponse(gigaSpace, remotingEntry, retVal);
            processedExecution(service);
        } catch (InvocationTargetException e) {
            failedExecution(service);
            writeResponse(gigaSpace, remotingEntry, e.getTargetException());
        } catch (IllegalAccessException e) {
            failedExecution(service);
            writeResponse(gigaSpace, remotingEntry, new RemoteLookupFailureException("Failed to access method ["
                    + remotingEntry.getMethodName() + "] for lookup [" + remotingEntry.getLookupName() + "]", e));
        } catch (Throwable e) {
            failedExecution(service);
            writeResponse(gigaSpace, remotingEntry, e);
        }
    }

    private void writeResponse(GigaSpace gigaSpace, SpaceRemotingEntry remotingEntry, Throwable e) {
        if (remotingEntry.getOneWay() == null || !remotingEntry.getOneWay()) {
            SpaceRemotingEntry result = remotingEntry.buildResult(e);
            if (clusterInfo != null) {
                result.setInstanceId(clusterInfo.getInstanceId());
            }
            gigaSpace.write(result);
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("Remoting execution is configured as one way and an exception was thrown", e);
            }
        }
    }

    private void writeResponse(GigaSpace gigaSpace, SpaceRemotingEntry remotingEntry, Object retVal) {
        if (remotingEntry.getOneWay() == null || !remotingEntry.getOneWay()) {
            SpaceRemotingEntry result = remotingEntry.buildResult(retVal);
            if (clusterInfo != null) {
                result.setInstanceId(clusterInfo.getInstanceId());
            }
            gigaSpace.write(result);
        }
    }

    private void autowireArguments(Object service, Object[] args) {
        if (disableAutowiredArguments) {
            return;
        }
        if (args == null) {
            return;
        }
        if (shouldAutowire(service)) {
            for (Object arg : args) {
                if (arg == null) {
                    continue;
                }
                AutowireCapableBeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory();
                beanFactory.autowireBeanProperties(arg, AutowireCapableBeanFactory.AUTOWIRE_NO, false);
                beanFactory.initializeBean(arg, arg.getClass().getName());
            }
        }
    }

    private boolean shouldAutowire(Object service) {
        if (service instanceof AutowireArgumentsMarker) {
            return true;
        }
        if (service.getClass().isAnnotationPresent(AutowireArguments.class)) {
            return true;
        }
        for (Class clazz : service.getClass().getInterfaces()) {
            if (clazz.isAnnotationPresent(AutowireArguments.class)) {
                return true;
            }
        }
        return false;
    }

    // Executor execution

    public Object invokeExecutor(ExecutorRemotingTask task) throws Throwable {
        waitTillInitialized();
        String lookupName = task.getLookupName();
        if (lookupName.endsWith(asyncInterfaceSuffix)) {
            lookupName = lookupName.substring(0, lookupName.length() - asyncInterfaceSuffix.length());
        }

        Object service = interfaceToService.get(lookupName);
        if (service == null) {
            // we did not get an interface, maybe it is a bean name?
            try {
                service = applicationContext.getBean(lookupName);
            } catch (NoSuchBeanDefinitionException e) {
                // do nothing, write back a proper exception
                throw new RemoteLookupFailureException("Failed to find service for lookup [" + lookupName + "]", e);
            }
            if (service == null) {
                throw new RemoteLookupFailureException("Failed to find service for lookup [" + lookupName + "]");
            }
        }

        autowireArguments(service, task.getArguments());

        IMethod method = null;
        try {
            if (task.getMethodHash() != null) {
                method = methodInvocationLookup.get(lookupName).get(task.getMethodHash());
            }
            if (method == null) {
                method = methodInvocationCache.findMethod(lookupName, service, task.getMethodName(),
                        task.getArguments());
            }
        } catch (Exception e) {
            failedExecution(service);
            throw new RemoteLookupFailureException("Failed to find method [" + task.getMethodName()
                    + "] for lookup [" + task.getLookupName() + "]", e);
        }
        try {
            Object retVal;
            if (serviceExecutionAspect != null) {
                retVal = serviceExecutionAspect.invoke(task, new InternalMethodInvocation(method), service);
            } else {
                retVal = method.invoke(service, task.getArguments());
            }
            processedExecution(service);
            return retVal;
        } catch (InvocationTargetException e) {
            failedExecution(service);
            throw e.getTargetException();
        } catch (IllegalAccessException e) {
            failedExecution(service);
            throw new RemoteLookupFailureException("Failed to access method [" + task.getMethodName()
                    + "] for lookup [" + task.getLookupName() + "]");
        }
    }

    private void processedExecution(Object service) {
        processed.incrementAndGet();
        serviceToServiceInfoMap.get(service).getProcessed().incrementAndGet();
    }

    private void failedExecution(Object service) {
        failed.incrementAndGet();
        serviceToServiceInfoMap.get(service).getFailures().incrementAndGet();
    }

    private void waitTillInitialized() throws RemoteLookupFailureException {
        if (initialized) {
            return;
        }
        try {
            initializationLatch.await(60, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RemoteLookupFailureException(
                    "Space remoting service exporter interrupted while waiting for initialization", e);
        }
        if (!initialized) {
            throw new RemoteLookupFailureException("Space remoting service exporter not initialized yet");
        }
    }

    /**
     * Holds a cache of method reflection information per service (interface). If there is a single
     * method within the interface with the same name and number of parameters, then the cached
     * version will be used. If there are more than one method with the name and number of
     * parameteres, then the Java reflection <code>getMethod</code> will be used.
     *
     * <p>Note, as a side effect, if we are using cached methods, we support executing interfaces
     * that declare a super type as a parameter, and invocation will be done using a sub type. This
     * does not work with Java reflection getMethod as it only returns exact match for argument
     * types.
     *
     * <p>Also note, this cache is *not* thread safe. The idea here is that this cache is initlaized
     * at startup and then never updated.
     */
    private static class MethodInvocationCache {

        private final Map<String, MethodsCacheEntry> serviceToMethodCacheMap = new HashMap<String, MethodsCacheEntry>();

        public IMethod findMethod(String lookupName, Object service, String methodName, Object[] arguments)
                throws NoSuchMethodException {
            int numberOfParameters = 0;
            if (arguments != null) {
                numberOfParameters = arguments.length;
            }
            IMethod invocationMethod;
            IMethod[] methods = serviceToMethodCacheMap.get(lookupName).getMethodCacheEntry(methodName)
                    .getMethod(numberOfParameters);
            if (methods != null && methods.length == 1) {
                //we can do caching
                invocationMethod = methods[0];
            } else {
                if (arguments == null) {
                    arguments = new Object[0];
                }
                Class<?>[] argumentTypes = new Class<?>[arguments.length];
                for (int i = 0; i < arguments.length; i++) {
                    argumentTypes[i] = (arguments[i] != null ? arguments[i].getClass() : Object.class);
                }

                invocationMethod = new StandardMethod(service.getClass().getMethod(methodName, argumentTypes));
            }
            return invocationMethod;
        }

        public void addService(Class serviceInterface, Object service, boolean useFastReflection) {
            MethodsCacheEntry methodsCacheEntry = new MethodsCacheEntry();
            serviceToMethodCacheMap.put(serviceInterface.getName(), methodsCacheEntry);
            methodsCacheEntry.addService(service.getClass(), useFastReflection);
        }

        private static class MethodsCacheEntry {

            private Map<String, MethodCacheEntry> methodNameMap = new HashMap<String, MethodCacheEntry>();

            public MethodCacheEntry getMethodCacheEntry(String methodName) {
                return methodNameMap.get(methodName);
            }

            public void addService(Class service, boolean useFastReflection) {
                Method[] methods = service.getMethods();
                for (Method method : methods) {
                    MethodCacheEntry methodCacheEntry = methodNameMap.get(method.getName());
                    if (methodCacheEntry == null) {
                        methodCacheEntry = new MethodCacheEntry();
                        methodNameMap.put(method.getName(), methodCacheEntry);
                    }
                    methodCacheEntry.addMethod(method, useFastReflection);
                }
            }
        }

        private static class MethodCacheEntry {

            private Map<Integer, IMethod[]> parametersPerMethodMap = new HashMap<Integer, IMethod[]>();

            public IMethod[] getMethod(int numberOfParams) {
                return parametersPerMethodMap.get(numberOfParams);
            }

            public void addMethod(Method method, boolean useFastReflection) {
                IMethod fastMethod;
                if (useFastReflection) {
                    fastMethod = ReflectionUtil.createMethod(method);
                } else {
                    fastMethod = new StandardMethod(method);
                }
                IMethod[] list = parametersPerMethodMap.get(method.getParameterTypes().length);
                if (list == null) {
                    list = new IMethod[] { fastMethod };
                } else {
                    IMethod[] tempList = new IMethod[list.length + 1];
                    System.arraycopy(list, 0, tempList, 0, list.length);
                    tempList[list.length] = fastMethod;
                    list = tempList;
                }
                parametersPerMethodMap.put(method.getParameterTypes().length, list);
            }
        }
    }

    private static class ServiceInfo {
        private final String beanId;
        private final String className;
        private final Object service;
        private final AtomicLong processed = new AtomicLong();
        private final AtomicLong failures = new AtomicLong();

        private ServiceInfo(String beanId, String className, Object service) {
            this.beanId = beanId;
            this.className = className;
            this.service = service;
        }

        public String getBeanId() {
            return beanId;
        }

        public String getClassName() {
            return className;
        }

        public Object getService() {
            return service;
        }

        public AtomicLong getProcessed() {
            return processed;
        }

        public AtomicLong getFailures() {
            return failures;
        }
    }

    private static class InternalMethodInvocation implements ServiceExecutionAspect.MethodInvocation {
        private final IMethod method;

        private InternalMethodInvocation(IMethod method) {
            this.method = method;
        }

        public Object invoke(Object obj, Object... args)
                throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            return method.invoke(obj, args);
        }

        @Override
        public Method getMethod() {
            return method.getMethod();
        }
    }
}