package com.rayo.server; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.jetlang.channels.Channel; import org.jetlang.channels.MemoryChannel; import org.jetlang.core.Callback; import org.jetlang.core.Disposable; import org.jetlang.fibers.Fiber; import org.jetlang.fibers.PoolFiberFactory; import com.voxeo.exceptions.NotFoundException; import com.voxeo.logging.Loggerf; public abstract class ReflectiveActor implements Actor, Callback<Object> { private static final Loggerf log = Loggerf.getLogger(ReflectiveActor.class); private Fiber fiber; private Channel<Object> channel; private boolean running = false; private Map<Class<?>, Method> targets = new HashMap<Class<?>, Method>(); private static Map<String, Method> globalMethodsCache = new ConcurrentHashMap<String, Method>(); private PoolFiberFactory fiberFactory; private Set<EventHandler> eventHandlers = new LinkedHashSet<EventHandler>(); @Override public void onMessage(Object message) { if (!running) { if (message instanceof Request) { ((Request) message).reply(new NotFoundException()); } log.info("Actor is disposed. Ignoring message. [%s]", message); return; } try { if (message instanceof EventHandler) { eventHandlers.add((EventHandler) message); } else if (message instanceof Request) { Request request = (Request) message; Object command = request.getCommand(); if (log.isDebugEnabled()) { log.debug("[%s] : Request [%s]", this, request); } try { Method method = findMethod(command); if (method == null) { log.warn("Could not find command handler for message [%s]", message); request.reply(new UnsupportedOperationException()); return; } Object result = method.invoke(this, command); request.reply(result); } catch (InvocationTargetException e) { Throwable targetException = e.getCause(); if (targetException != null) { request.reply(targetException); throw targetException; } else { request.reply(e); } throw e; } catch (Exception e) { request.reply(e); throw e; } } else { Method method = findMethod(message); if (method == null) { log.warn("Could not find command handler for message [%s]", message); return; } log.info("Message [%s]", message); method.invoke(this, message); } } catch (Throwable e) { log.error("Exception while processing command", e); if(!handleException(e)) { stop(); } } finally { flushEvents(); } } private Method findMethod(Object message) { Method method = globalMethodsCache.get(getMethodKey(message)); if (method != null) return method; Queue<Class<?>> queue = new LinkedList<Class<?>>(); queue.add(message.getClass()); while(!queue.isEmpty()) { Class<?> clz = queue.poll(); method = targets.get(clz); if (method != null) { break; } for (Class<?> iface : clz.getInterfaces()) { queue.add(iface); } Class<?> superclz = clz.getSuperclass(); if(superclz != null && !superclz.equals(Object.class)) { queue.add(superclz); } } if (method != null) { globalMethodsCache.put(getMethodKey(message), method); } return method; } private String getMethodKey(Object message) { return this.getClass().toString() + message.getClass().toString(); } /** * Continue after an exception by default * * @param throwable * @return */ protected boolean handleException(Throwable throwable) { return true; } @Override public synchronized boolean isRunning() { return running; } @Override public synchronized void start() { this.fiber = fiberFactory.create(); this.channel = new MemoryChannel<Object>(); // Subscribe ourselves to receive events channel.subscribe(fiber, this); final Method[] methods = this.getClass().getMethods(); for (final Method method : methods) { final Class<?>[] types = method.getParameterTypes(); if (types.length != 1) { continue; // method must have one parameter } if (method.getAnnotation(Message.class) == null) { continue; // method must have a Message annotation } final Class<?> commandClass = types[0]; targets.put(commandClass, method); } fiber.start(); running = true; } public void link(final ActorLink link) { fiber.add(new Disposable() { public void dispose() { link.postStop(); } }); } @Override public synchronized void stop() { if(running) { running = false; fiber.dispose(); } } @Override public synchronized boolean publish(Object message) { if (running) { channel.publish(message); } else { log.info("Actor %s is disposed. Ignoring message. [%s]", this.getClass().getSimpleName(), message); if (message instanceof Request) { ((Request) message).reply(new NotFoundException()); } } return running; } public PoolFiberFactory getFiberFactory() { return fiberFactory; } public void setFiberFactory(PoolFiberFactory fiberFactory) { this.fiberFactory = fiberFactory; } private Queue<Object> eventQueue = new LinkedList<Object>(); private void flushEvents() { while(!eventQueue.isEmpty()) { Object message = eventQueue.poll(); log.info("Event [%s]", message); for (EventHandler handler : eventHandlers) { try { handler.handle(message); } catch (Exception e) { log.error("Exception in event handler [event=%s]", message, e); } } } } public void flush() { flushEvents(); } protected void fire(Object message) { if(running) { log.info("Queued Event [%s]", message); eventQueue.offer(message); } else { log.info("Actor %s is disposed. Ignoring event. [%s]", this.getClass().getSimpleName(), message); } } public void addEventHandler(EventHandler handler) { eventHandlers.add(handler); } public void removeEventHandler(EventHandler handler) { eventHandlers.remove(handler); } public Collection<EventHandler> getEventHandlers() { return Collections.unmodifiableSet(eventHandlers); } }