/*
* 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;
import java.lang.reflect.Method;
import java.util.concurrent.Executor;
import com.indyforge.foxnet.rmi.binding.LocalBinding;
import com.indyforge.foxnet.rmi.binding.LocalObject;
import com.indyforge.foxnet.rmi.binding.RemoteBinding;
import com.indyforge.foxnet.rmi.binding.RemoteObject;
import com.indyforge.foxnet.rmi.binding.registry.DynamicRegistry;
import com.indyforge.foxnet.rmi.binding.registry.StaticRegistry;
import com.indyforge.foxnet.rmi.util.Future;
/**
* An invoker manager represents a connection. It can lookup invokers and
* manages bindings.
*
* @author Christopher Probst
*/
public abstract class InvokerManager {
/*
* The close future of this invoker manager.
*/
private final Future closeFuture = new Future();
/*
* The dynamic registry of this invoker manager.
*/
private final DynamicRegistry dynamicRegistry = new DynamicRegistry();
/*
* The static registry of this invoker manager.
*/
private final StaticRegistry staticRegistry;
/*
* The dynamic proxy timeout.
*/
private volatile long dynamicProxyTimeout;
/**
* Replaces the given remote object with a proxy if necessary.
*
* @param remoteObject
* The remote object you want to check.
* @return the given object or a proxy.
*/
private Object replaceRemoteObjectWithProxy(Object remoteObject) {
if (!(remoteObject instanceof RemoteObject)) {
return remoteObject;
}
// Create a new invoker
return new Invoker(this, new RemoteBinding((RemoteObject) remoteObject,
true)).proxyTimeout(dynamicProxyTimeout).proxy();
}
/**
* Replaces the given proxy with a local object. The remote side will know
* which "real"-object belongs to the given local object.
*
* @param proxy
* The proxy you want to check.
* @return the given object or a local object.
*/
private Object replaceProxyWithLocalObject(Object proxy) {
// Try to get proxy invoker
Invoker invoker = Invoker.of(proxy);
// Obviously not a proxy which was created by us
if (invoker == null || !equals(invoker.manager())) {
return proxy;
} else {
// Get the remote binding
RemoteBinding remoteBinding = invoker.binding();
// Create a new local object based on the proxy
return new LocalObject(remoteBinding.id(),
remoteBinding.isDynamic());
}
}
/**
* Replaces the given local object with the "real"-object.
*
* @param localObject
* The local object you want to check.
* @return the given object or the "real"-object.
*/
private Object replaceLocalObject(Object localObject) {
// Not for us...
if (!(localObject instanceof LocalObject)) {
return localObject;
}
// Convert
LocalObject local = (LocalObject) localObject;
// Lookup the binding
LocalBinding binding = local.isDynamic() ? dynamicRegistry.get(local
.id()) : staticRegistry.get(local.id());
// Check the binding
if (binding == null) {
throw new IllegalArgumentException("The local argument object "
+ "is not part of the given registries");
} else {
// Otherwise replace with real target
return binding.target();
}
}
/**
* Sends the invocation to the remote side.
*
* @param invocation
* The invocation you want to send.
*/
protected abstract void sendInvocation(Invocation invocation);
/**
* @param proxy
* The proxy.
* @return the invoker manager instance which manages the invoker of the
* given proxy or null.
*/
public static InvokerManager of(Object proxy) {
Invoker invoker = Invoker.of(proxy);
return invoker != null ? invoker.manager() : null;
}
/**
* Creates a new invoker manager using the given argument.
*
* @param staticRegistry
* The static registry of this invoker manager.
*/
public InvokerManager(StaticRegistry staticRegistry) {
if (staticRegistry == null) {
throw new NullPointerException("staticRegistry");
}
// Save the static registry
this.staticRegistry = staticRegistry;
}
/**
* Converts a remote argument to a local argument.
*
* @param remoteArgument
* The remote argument.
* @return the local argument.
*/
public Object remoteToLocal(Object remoteArgument) {
// Replace remote OR local object
return replaceLocalObject(replaceRemoteObjectWithProxy(remoteArgument));
}
/**
* Converts a local argument to a remote argument.
*
* @param localArgument
* The local argument.
* @return the remote argument.
*/
public Object localToRemote(Object localArgument) {
// Do we send back a remote OR a known proxy ?
return dynamicRegistry
.replaceRemote(replaceProxyWithLocalObject(localArgument));
}
/**
* @see InvokerManager#localToRemote(Object)
*/
public Object[] localsToRemotes(Object... localArguments) {
if (localArguments != null) {
for (int i = 0; i < localArguments.length; i++) {
localArguments[i] = localToRemote(localArguments[i]);
}
}
return localArguments;
}
/**
* @see InvokerManager#remoteToLocal(Object)
*/
public Object[] remotesToLocals(Object... remoteArguments) {
if (remoteArguments != null) {
for (int i = 0; i < remoteArguments.length; i++) {
remoteArguments[i] = remoteToLocal(remoteArguments[i]);
}
}
return remoteArguments;
}
/**
* @return the dynamic proxy timeout which is used when creating dynamic
* proxy objects.
*/
public long dynamicProxyTimeout() {
return dynamicProxyTimeout;
}
/**
* Sets the dynamic proxy timeout which is used when creating dynamic proxy
* objects.
*
* @param dynamicProxyTimeout
* The new dynamic proxy timeout. A value <= 0 means waiting
* without a limit.
* @return this for chaining.
*/
public InvokerManager dynamicProxyTimeout(long dynamicProxyTimeout) {
this.dynamicProxyTimeout = dynamicProxyTimeout;
return this;
}
/**
* This method will handle the invocation request using the given executor.
* If the invocation is finished the future will be notified (If the future
* was not null).
*
* @param message
* The invocation message.
* @param executor
* The executor.
* @param future
* The future or null.
*/
public void handleInvocation(final InvocationMessage message,
Executor executor, final Future future) {
if (message == null) {
throw new NullPointerException("message");
} else if (executor == null) {
throw new NullPointerException("executor");
}
// Get the correct binding
final LocalBinding binding = message.isDynamic() ? dynamicRegistry
.get(message.bindingId()) : staticRegistry.get(message
.bindingId());
// Check the binding
if (binding == null) {
// Fail if future exists...
if (future != null) {
// Fail
future.fail(new IllegalArgumentException(
"Requested binding with id (" + message.bindingId()
+ ") does not exist"));
}
} else if (!binding.containsMethodId(message.methodId())) {
// Fail the future...
if (future != null) {
// Fail
future.fail(new IllegalArgumentException("Method id ("
+ message.methodId() + ") does not exist"));
}
} else {
// Get final method from binding!
final Method method = binding.methods().get(message.methodId());
/*
* Check the return value and the future.
*/
if (method.getReturnType() != void.class && future == null) {
throw new IllegalStateException("The method does return a "
+ "non-void value but you have non specified a future");
}
/*
* Create a new invocation and execute it using the given method
* context.
*/
binding.executeInMethodContext(executor, message.methodId(),
new Runnable() {
@Override
public void run() {
try {
/*
* Resolve all remote and local objects and
* invoke method.
*/
Object result = method.invoke(binding.target(),
remotesToLocals(message.arguments()));
if (future != null) {
// Succeed the future with the filtered
// result
future.succeed(localToRemote(result));
}
} catch (Throwable e) {
if (future != null) {
// Fail
future.fail(e);
}
}
}
});
}
}
/**
* @return the close future which is notifies when the invoker manager is
* closed.
*/
public Future closeFuture() {
return closeFuture;
}
/**
* @return the dynamic registry.
*/
public DynamicRegistry dynamicReg() {
return dynamicRegistry;
}
/**
* @return the static registry.
*/
public StaticRegistry staticReg() {
return staticRegistry;
}
/**
* Gets the proxy with the given name.
*
* @param name
* The name of the target you want to lookup.
* @return the proxy.
* @throws LookupException
* If the lookup failed.
*/
public Object lookupProxy(String name) throws LookupException {
return lookupInvoker(name).proxy();
}
/**
* Gets the invoker with the given name.
*
* @param name
* The name of the target you want to lookup.
* @return the invoker.
* @throws LookupException
* If the lookup failed.
*/
public abstract Invoker lookupInvoker(String name) throws LookupException;
/**
* Gets the names of all remote bindings.
*
* @return an array which contains the names.
* @throws LookupException
* If the lookup failed.
*/
public abstract String[] lookupNames() throws LookupException;
/**
* Closes this invoker manager.
*
* @return the close future.
*/
public abstract Future close();
}