All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
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;
}
}