/* * Copyright (C) 2011 Christopher Probst * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of the 'FoxNet RMI' nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.indyforge.foxnet.rmi.binding; import java.io.Serializable; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.concurrent.Executor; import java.util.logging.Logger; import com.indyforge.foxnet.rmi.LocalInterface; import com.indyforge.foxnet.rmi.OrderedExecution; import com.indyforge.foxnet.rmi.Remote; import com.indyforge.foxnet.rmi.RemoteInterfaces; /** * This represents an abstract local binding. * * @author Christopher Probst */ public abstract class LocalBinding extends Binding { // The logger protected final Logger logger = Logger.getLogger(getClass().getName()); /** * */ private static final long serialVersionUID = 1L; /** * Simply checks whether or not the given interface class is valid. * * @param interfaceClass * The interface class you want to check. * @return true if the interface class is valid, otherwise false. */ private static boolean isValidRemoteInterface(Class<?> interfaceClass) { return !LocalInterface.class.isAssignableFrom(interfaceClass) && !Remote.class.equals(interfaceClass); } /** * This method collects all valid remote interfaces. * * @param startClass * The start class. * @param endClass * The end class. * @param interfaces * The set with all remote interface classes. */ private static void collectAllRemoteInterfaces(Class<?> startClass, Class<?> endClass, Set<Class<?>> interfaces) { // Start with the end class Class<?> pointer = endClass; // Used to return when finished boolean finished = false; do { // The finished parameter finished = pointer == startClass; // For all interfaces for (Class<?> interfaceClass : pointer.getInterfaces()) { // Check if (isValidRemoteInterface(interfaceClass)) { // Add to set interfaces.add(interfaceClass); } } // Save pointer = pointer.getSuperclass(); } while (!finished); } /** * This method collects all remote interfaces or uses the * {@link RemoteInterfaces} annotation if present to use only the specified * intefaces. * * @param target * The remote target. * @return an array with all valid remote interfaces. */ private static Class<?>[] getRemoteInterfaces(Remote target) { if (target == null) { throw new NullPointerException("target"); } // Get annotation if present RemoteInterfaces remoteInterfaces = target.getClass().getAnnotation( RemoteInterfaces.class); // Used to collect the interfaces Set<Class<?>> uniqueInterfaces = new HashSet<Class<?>>(); /* * Use the classes from the annotation. */ if (remoteInterfaces != null && remoteInterfaces.value().length > 0) { // Check if the interfaces are valid for (Class<?> interfaceClass : remoteInterfaces.value()) { // Check if (interfaceClass.isInterface() && isValidRemoteInterface(interfaceClass) && interfaceClass.isAssignableFrom(target.getClass())) { // Add to interfaces uniqueInterfaces.add(interfaceClass); } } } else { // Collect all interfaces collectAllRemoteInterfaces(Object.class, target.getClass(), uniqueInterfaces); } // Copy to array return uniqueInterfaces.toArray(new Class<?>[uniqueInterfaces.size()]); } /** * This class implements the concept of an ordered execution queue. * * @author Christopher Probst */ public static final class OrderedExecutionQueue implements Runnable, Serializable { /** * */ private static final long serialVersionUID = 1L; // Lock the queue private final Object queueLock = new Object(); /* * Used to queue the runnables. */ private final Queue<Runnable> queue = new LinkedList<Runnable>(); /** * This method offers a runnable object. * * @param runnable * The runnable you want to add. * @return true if this queue needs to be executed. */ public boolean addOrderedExecution(Runnable runnable) { // Init here boolean needsExecution; synchronized (queueLock) { // Does this queue need to be executed ? needsExecution = queue.isEmpty(); // Offer command! queue.offer(runnable); } // Return the state return needsExecution; } /* * (non-Javadoc) * * @see java.lang.Runnable#run() */ @Override public void run() { Runnable runnable = null; for (;;) { synchronized (queueLock) { // Get command runnable = queue.element(); } // Run! runnable.run(); synchronized (queueLock) { // Remove command queue.remove(); // Leave the loop if empty! if (queue.isEmpty()) { return; } } } } } // Here we store all ordered execution queues mapped to their method ids private final Map<Integer, OrderedExecutionQueue> orderedExecutionQueues; // Here we store the remote target private final Remote target; /** * Creates a new local binding using the default * interface-collect-algorithm. * * @param id * The id of this binding. * @param target * The target of this binding. */ public LocalBinding(long id, Remote target) { this(id, target, getRemoteInterfaces(target)); } /** * Creates a new local binding. * * @param id * The id of this binding. * @param target * The target of this binding. * @param interfaces * The interface classes of the target. */ public LocalBinding(long id, Remote target, Class<?>[] interfaces) { super(id, interfaces); // Check all given interface classes for (Class<?> interfaceClass : interfaces) { if (!interfaceClass.isAssignableFrom(target.getClass())) { throw new IllegalArgumentException(interfaceClass + " is not part of the hierarchy of " + target.getClass()); } } // Save target this.target = target; // Annotation OrderedExecution oe; // Create tmp Map<Integer, OrderedExecutionQueue> tmpOrderedExecutionQueues = null; // Add all methods which for (int i = 0, l = methods().size(); i < l; i++) { // Check if OrderedExecution is present... if ((oe = methods().get(i).getAnnotation(OrderedExecution.class)) != null && oe.value()) { // Lazy setup if (tmpOrderedExecutionQueues == null) { tmpOrderedExecutionQueues = new HashMap<Integer, OrderedExecutionQueue>(); } // Create new queue tmpOrderedExecutionQueues.put(i, new OrderedExecutionQueue()); } } // Set the map orderedExecutionQueues = tmpOrderedExecutionQueues != null ? Collections .unmodifiableMap(tmpOrderedExecutionQueues) : null; } /** * @return the ordered execution queues linked to their method ids or null * if this binding does not have any ordered executions. */ public Map<Integer, OrderedExecutionQueue> orderedExecutionQueues() { return orderedExecutionQueues; } /** * Executes the given runnable within the given method context. * * @param executor * The executor. * @param methodId * The id of the method (context). * @param runnable * The runnable you want to execute. */ public void executeInMethodContext(Executor executor, int methodId, Runnable runnable) { if (runnable == null) { throw new NullPointerException("runnable"); } // Try to get the queue OrderedExecutionQueue queue = orderedExecutionQueues != null ? orderedExecutionQueues .get(methodId) : null; /* * Now execute the runnable. Either directly or the ordered execution * queue of the method if necessary. */ if (queue != null) { // Execute queue if necessary if (queue.addOrderedExecution(runnable)) { // Set to queue runnable = queue; } else { runnable = null; } } // Is there anything to invoke ? if (runnable != null) { // Invoke with executor ? if (executor != null) { executor.execute(runnable); } else { runnable.run(); } } } /** * @return the remote target. */ public Remote target() { return target; } /* * (non-Javadoc) * * @see com.indyforge.foxnet.rmi.binding.Binding#isDynamic() */ @Override public boolean isDynamic() { return this instanceof DynamicBinding; } }