All Downloads are FREE. Search and download functionalities are using the official Maven repository.

it.tidalwave.actor.spi.ActorActivator Maven / Gradle / Ivy

/***********************************************************************************************************************
 *
 * SolidBlue - open source safe data
 * Copyright (C) 2011-2012 by Tidalwave s.a.s. (http://tidalwave.it)
 *
 ***********************************************************************************************************************
 *
 * 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.
 *
 ***********************************************************************************************************************
 *
 * WWW: http://solidblue.tidalwave.it
 * SCM: https://bitbucket.org/tidalwave/solidblue-src
 *
 **********************************************************************************************************************/
package it.tidalwave.actor.spi;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Provider;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import it.tidalwave.messagebus.MessageBus.Listener;
import it.tidalwave.netbeans.util.Locator;
import it.tidalwave.actor.annotation.Actor;
import it.tidalwave.actor.annotation.ListensTo;
import it.tidalwave.actor.annotation.Message;
import it.tidalwave.actor.annotation.OriginatedBy;
import it.tidalwave.actor.Collaboration;
import it.tidalwave.actor.CollaborationCompletedMessage;
import it.tidalwave.actor.CollaborationStartedMessage;
import it.tidalwave.actor.impl.DefaultCollaboration;
import it.tidalwave.actor.impl.ExecutorWithPriority;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/***********************************************************************************************************************
 * 
 * @stereotype Actor
 * 
 * @author  Fabrizio Giudici
 * @version $Id$
 *
 **********************************************************************************************************************/
@Slf4j
public class ActorActivator
  {
    @Nonnull 
    private final Class actorClass;

    @Nonnegative 
    /* package */ final int poolSize;
    
    @Getter
    private Object actorObject;
    
    private final Map mbeansMapByName = new HashMap<>();
    
    private final Provider messageBus = Locator.createProviderFor(CollaborationAwareMessageBus.class);
    
    private final List> messageBusListeners = new ArrayList<>();
    
    /* package */ ExecutorWithPriority executor;
    
    private ObjectName statsName;
    
    private ActorActivatorStats stats;

    /*******************************************************************************************************************
     * 
     * 
     * 
     ******************************************************************************************************************/
    @RequiredArgsConstructor
    class MessageListenerAdapter implements Listener
      {
        @Nonnull
        private final Method method;
        
        @Override
        public void notify (final @Nonnull Topic message) 
          {
            log.trace("notify({})", message);
            final DefaultCollaboration collaboration = DefaultCollaboration.getCollaboration(message);
            collaboration.registerPendingMessage(message); 
            stats.changePendingMessageCount(+1);
            
            final Runnable messageWorker = new Runnable()
              {
                @Override
                public void run() 
                  {
                    collaboration.bindToCurrentThread();
                    collaboration.unregisterPendingMessage(message);
                    stats.changePendingMessageCount(-1);

                    try
                      {
                        stats.incrementInvocationCount();
                        method.invoke(actorObject, message);
                      }
                    catch (Throwable t)
                      {
                        stats.incrementInvocationErrorCount();
                        log.error("Error calling {} with {}", method, message.getClass());
                        log.error("", t); 
                      }
                    finally
                      {
                        stats.incrementSuccessfulInvocationCount();
                        collaboration.unbindFromCurrentThread();
                      }
                  }
              };

            if (message.getClass().getAnnotation(Message.class).outOfBand())
              {
                executor.executeWithPriority(messageWorker);
              }
            else
              {
                executor.execute(messageWorker);
              } 
          }
      }
    
    /*******************************************************************************************************************
     * 
     * 
     * 
     ******************************************************************************************************************/
    @RequiredArgsConstructor
    class CollaborationMessageListenerAdapter implements Listener
      {
        @Nonnull
        private final Method method;
        
        @Nonnull
        private final Class type;
        
        @Override
        public void notify (final @Nonnull Topic message) 
          {
            log.trace("notify({})", message);
            final DefaultCollaboration collaboration = (DefaultCollaboration)message.getCollaboration();
            collaboration.registerPendingMessage(message); 
            stats.changePendingMessageCount(+1);
            
            final Runnable messageWorker = new Runnable()
              {
                @Override
                public void run() 
                  {
                    if (!collaboration.getOriginatingMessage().getClass().equals(type))
                      {
                        collaboration.unregisterPendingMessage(message);                        
                        stats.changePendingMessageCount(-1);
                      }
                    else
                      {
                        collaboration.bindToCurrentThread();
                        collaboration.unregisterPendingMessage(message);
                        stats.changePendingMessageCount(-1);

                        try
                          {
                            stats.incrementInvocationCount();
                            method.invoke(actorObject, message, collaboration.getOriginatingMessage());
                          }
                        catch (Throwable t)
                          {
                            stats.incrementInvocationErrorCount();
                            log.error("Error calling {} with {}", method, message.getClass());
                            log.error("", t); 
                          }
                        finally
                          {
                            stats.incrementSuccessfulInvocationCount();
                            collaboration.unbindFromCurrentThread();
                          }
                      }
                  }
              };

            executor.execute(messageWorker);
          }
      }
    
    /*******************************************************************************************************************
     * 
     * 
     * 
     ******************************************************************************************************************/
    public static ActorActivator activatorFor (final @Nonnull Class actorClass)
      {
        return new ActorActivator(actorClass, 1);
      }
            
    /*******************************************************************************************************************
     * 
     * 
     * 
     ******************************************************************************************************************/
    @Nonnull
    public ActorActivator withPoolSize (@Nonnegative int poolSize)
      {
        return new ActorActivator(actorClass, poolSize);
      }
            
    /*******************************************************************************************************************
     * 
     * 
     * 
     ******************************************************************************************************************/
    private ActorActivator (final @Nonnull Class actorClass, final @Nonnegative int poolSize)
      {
        this.actorClass = actorClass;
        this.poolSize = poolSize;
      }
    
    /*******************************************************************************************************************
     * 
     * 
     * 
     ******************************************************************************************************************/
    public void initialize() 
      {        
        try
          {
            final Actor actor = actorClass.getAnnotation(Actor.class);

            if (actor == null)
              {
                throw new IllegalArgumentException("Actor class must be annotated with @Actor: " + actorClass);  
              }

            if (!actor.threadSafe() && (poolSize != 1))
              {
                throw new IllegalArgumentException("Actors that aren't thread safe can't have pool size > 1");  
              }

            actorObject = actorClass.newInstance();
            executor = new ExecutorWithPriority(poolSize, actorClass.getSimpleName(), actor.initialPriority());
          }
        catch (InstantiationException | IllegalAccessException e)
          { 
            throw new RuntimeException(e);  
          }
        
        for (final Class clazz : getClassHierarchy(actorObject.getClass()))
          {
            for (final Method method : clazz.getDeclaredMethods())
              {
                final Annotation[][] parameterAnnotations = method.getParameterAnnotations();

                if ((parameterAnnotations.length == 1) && containsAnnotation(parameterAnnotations[0], ListensTo.class))
                  {
                    registerMessageListener(method);
                  }
                else if ((parameterAnnotations.length == 2) && containsAnnotation(parameterAnnotations[0], ListensTo.class)
                                                            && containsAnnotation(parameterAnnotations[1], OriginatedBy.class))
                  {
                    registerCollaborationListener(method);
                  }
                else if (method.getAnnotation(PostConstruct.class) != null)
                  {
                    try 
                      {
                        method.invoke(actorObject);
                      }
                    catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) // TODO: use a checked exception
                      {
                        throw new RuntimeException(e);
                      }
                  }
              }
            
            mbeansMapByName.putAll(getMBeans(actorObject, clazz));
          }
        
        registerMBeans();
      }
    
    /*******************************************************************************************************************
     * 
     * 
     * 
     ******************************************************************************************************************/
    public void dispose() 
      {
        unregisterMBeans();
        
        for (final Listener listener : messageBusListeners)
          {
            messageBus.get().unsubscribe(listener);  
          }
        
        final List hierarchy = getClassHierarchy(actorObject.getClass());
        Collections.reverse(hierarchy);
        
        for (final Class clazz : hierarchy)
          {
            for (final Method method : clazz.getDeclaredMethods())
              {
                if (method.getAnnotation(PreDestroy.class) != null)
                  {
                    try 
                      {
                        method.invoke(actorObject);
                      }
                    catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) // TODO: use a checked exception
                      {
                        throw new RuntimeException(e);
                      }
                  }
              }
          }
      }

    /*******************************************************************************************************************
     * 
     * 
     * 
     ******************************************************************************************************************/
    private  void registerMessageListener (final @Nonnull Method method)
      {
        log.info("registerMessageListener({})", method);

        addListener(method, new MessageListenerAdapter(method), (Class)method.getParameterTypes()[0]);        
      }

    /*******************************************************************************************************************
     * 
     * 
     * 
     ******************************************************************************************************************/
    private void registerCollaborationListener (final @Nonnull Method method)
      {
        log.info("registerCollaborationListener({})", method);
        final Class collaborationMessageType = method.getParameterTypes()[0];
        final Class messageType = method.getParameterTypes()[1];
        
        final Listener messageListener = collaborationMessageType.equals(CollaborationStartedMessage.class)
                                               ? new CollaborationMessageListenerAdapter(method, messageType)
                                               : new CollaborationMessageListenerAdapter(method, messageType);
        addListener(method, messageListener, collaborationMessageType);        
      }

    /*******************************************************************************************************************
     * 
     * 
     * 
     ******************************************************************************************************************/
    private  void addListener (final @Nonnull Method method, 
                                      final @Nonnull Listener messageListener,
                                      final @Nonnull Class topic) throws SecurityException 
      {
        method.setAccessible(true);
        messageBusListeners.add(messageListener);
        messageBus.get().subscribe(topic, messageListener);
      }  
    
    /*******************************************************************************************************************
     * 
     * 
     * 
     ******************************************************************************************************************/
    private void registerMBeans()
      {
        final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
        
        try 
          {
            stats = new ActorActivatorStats(this);             
            final String name = String.format("%s:type=%s",actorObject.getClass().getPackage().getName(), 
                                                           actorObject.getClass().getSimpleName());
            statsName = new ObjectName(name);
            mBeanServer.registerMBean(stats, statsName);
          }
        catch (InstanceAlreadyExistsException | MalformedObjectNameException | MBeanRegistrationException | NotCompliantMBeanException e) 
          {
            log.error("Cannot register master MBean for actor " + actorObject, e);
          }

        for (final Entry entry : mbeansMapByName.entrySet())
          {
            try 
              {
                log.info(">>>> registering MBean {}", entry);
                mBeanServer.registerMBean(entry.getValue(), entry.getKey());
              }
            catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) 
              {
                log.error("Cannot register MBean: " + entry, e);
              }
          }
      }
    
    /*******************************************************************************************************************
     * 
     * Unregisters the MBeans.
     * 
     ******************************************************************************************************************/
    private void unregisterMBeans()
      {
        final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); 

        try 
          {
            mBeanServer.unregisterMBean(statsName);
          }
        catch (InstanceNotFoundException | MBeanRegistrationException e)
          {
            log.error("Cannot register master MBean for actor " + actorObject, e);
          }
        
        for (final Entry entry : mbeansMapByName.entrySet())
          {
            try
              {
                log.info(">>>> unregistering MBean {}", entry);
                mBeanServer.unregisterMBean(entry.getKey());
              }
            catch (InstanceNotFoundException | MBeanRegistrationException e)
              {
                log.error("Cannot unregister MBean: " + entry, e);
              }
          }
      }
    
    /*******************************************************************************************************************
     * 
     * 
     * 
     ******************************************************************************************************************/
    @Nonnull
    private static Map getMBeans (final @Nonnull Object actorObject, final @Nonnull Class clazz)
      {
        final Map result = new HashMap<>();

        for (final Field field : clazz.getDeclaredFields())
          {
            if (!field.isSynthetic() && ((field.getModifiers() & Modifier.STATIC) == 0))
              {
                try 
                  {
                    field.setAccessible(true);
                    final Object value = field.get(actorObject);
                    
                    if (value != null)
                      {
                        final Class[] interfaces = value.getClass().getInterfaces();
                        //
                        // TODO: it checks only first interface - what about an annotation?
                        //
                        if ((interfaces.length > 0) && interfaces[0].getName().endsWith("MBean"))
                          {
                            final String name = String.format("%s:type=%s", clazz.getPackage().getName(), 
                                                                            value.getClass().getSimpleName());
                            result.put(new ObjectName(name), value);
                          }
                      }
                  }
                catch (IllegalArgumentException | IllegalAccessException | MalformedObjectNameException e) 
                  {
                    log.error("Cannot handle object: {}", field);
                  }
              }
          }
        
        return result;
      }

    /*******************************************************************************************************************
     * 
     * Returns the hierarchy of the given class, starting from the highest.
     * 
     ******************************************************************************************************************/
    @Nonnull
    private static List getClassHierarchy (final @Nonnull Class clazz)
      {  
        final List hierarchy = new ArrayList<>();
        
        for (Class ancestor = clazz; ancestor != null; ancestor = ancestor.getSuperclass())
          {
            hierarchy.add(0, ancestor);
          }
        
        return hierarchy;
      }
    
    /*******************************************************************************************************************
     * 
     * 
     * 
     ******************************************************************************************************************/
    private static boolean containsAnnotation (final @Nonnull Annotation[] annotations, final @Nonnull Class annotationClass)
      {
        for (final Annotation annotation : annotations) 
          {
            if (annotationClass.isAssignableFrom(annotation.getClass()))
              {
                return true;  
              }
          }
        
        return false;
      }
  } 




© 2015 - 2024 Weber Informatics LLC | Privacy Policy