Added a basic RMI service that uses the RPC service.

I'll add some more javadoc in a sec.
cleanup_build_scripts
Paul Speed 9 years ago
parent 95603c46c4
commit 95aa2d72d0
  1. 54
      jme3-networking/src/main/java/com/jme3/network/service/rmi/Asynchronous.java
  2. 43
      jme3-networking/src/main/java/com/jme3/network/service/rmi/CallType.java
  3. 111
      jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfo.java
  4. 103
      jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfoRegistry.java
  5. 136
      jme3-networking/src/main/java/com/jme3/network/service/rmi/MethodInfo.java
  6. 87
      jme3-networking/src/main/java/com/jme3/network/service/rmi/RemoteObjectHandler.java
  7. 147
      jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiClientService.java
  8. 56
      jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiContext.java
  9. 203
      jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiHostedService.java
  10. 344
      jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiRegistry.java

@ -0,0 +1,54 @@
/*
* Copyright (c) 2015 jMonkeyEngine
* 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 'jMonkeyEngine' 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.jme3.network.service.rmi;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
/**
* Indicates that a given method should be executed asynchronously
* through the RMI service. This must annotate the method on the
* shared interface for it to have an effect.
*
* @author Paul Speed
*/
@Retention(value=RUNTIME)
@Target(value=METHOD)
public @interface Asynchronous {
boolean reliable() default true;
}

@ -0,0 +1,43 @@
/*
* Copyright (c) 2015 jMonkeyEngine
* 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 'jMonkeyEngine' 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.jme3.network.service.rmi;
/**
*
*
* @author Paul Speed
*/
public enum CallType {
Synchronous, Asynchronous, Unreliable
}

@ -0,0 +1,111 @@
/*
* Copyright (c) 2015 jMonkeyEngine
* 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 'jMonkeyEngine' 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.jme3.network.service.rmi;
import com.jme3.network.serializing.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
*
*
* @author Paul Speed
*/
@Serializable
public final class ClassInfo {
private String name;
private short typeId;
private MethodInfo[] methods;
/**
* For serialization only.
*/
public ClassInfo() {
}
public ClassInfo( short typeId, Class type ) {
this.typeId = typeId;
this.name = type.getName();
this.methods = toMethodInfo(type, type.getMethods());
}
public String getName() {
return name;
}
public Class getType() {
try {
return Class.forName(name);
} catch( ClassNotFoundException e ) {
throw new RuntimeException("Error finding class for:" + this, e);
}
}
public short getId() {
return typeId;
}
public MethodInfo getMethod( short id ) {
return methods[id];
}
public MethodInfo getMethod( Method m ) {
for( MethodInfo mi : methods ) {
if( mi.matches(m) ) {
return mi;
}
}
return null;
}
private MethodInfo[] toMethodInfo( Class type, Method[] methods ) {
List<MethodInfo> result = new ArrayList<MethodInfo>();
short methodId = 0;
for( Method m : methods ) {
// Simple... add all methods exposed through the interface
result.add(new MethodInfo(methodId++, m));
}
return result.toArray(new MethodInfo[result.size()]);
}
public MethodInfo[] getMethods() {
return methods;
}
@Override
public String toString() {
return "ClassInfo[" + name + "]";
}
}

@ -0,0 +1,103 @@
/*
* Copyright (c) 2015 jMonkeyEngine
* 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 'jMonkeyEngine' 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.jme3.network.service.rmi;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
*
*
* @author Paul Speed
*/
public class ClassInfoRegistry {
//private final LoadingCache<Class, ClassInfo> cache; // Guava version
private final Map<Class, ClassInfo> cache = new HashMap<Class, ClassInfo>();
private final AtomicInteger nextClassId = new AtomicInteger();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public ClassInfoRegistry() {
//this.cache = CacheBuilder.newBuilder().build(new ClassInfoLoader()); // Guava version
}
public ClassInfo getClassInfo( Class type ) {
//return cache.getUnchecked(type); // Guava version
// More complicated without guava
lock.readLock().lock();
try {
ClassInfo result = cache.get(type);
if( result != null ) {
return result;
}
// Else we need to create it and store it... so grab the write
// lock
lock.readLock().unlock();
lock.writeLock().lock();
try {
// Note: it's technically possible that a race with another thread
// asking for the same class already created one between our read unlock
// and our write lock. No matter as it's cheap to create one and does
// no harm. Code is simpler without the double-check.
result = new ClassInfo((short)nextClassId.getAndIncrement(), type);
cache.put(type, result);
// Regrab the read lock before leaving... kind of unnecessary but
// it makes the method cleaner and widens the gap of lock races.
// Downgrading a write lock to read is ok.
lock.readLock().lock();
return result;
} finally {
// Unlock the write lock while still holding onto read
lock.writeLock().unlock();
}
} finally {
lock.readLock().unlock();
}
}
/*
would be more straight-forward with guava Guava version
private class ClassInfoLoader extends CacheLoader<Class, ClassInfo> {
@Override
public ClassInfo load( Class type ) {
return new ClassInfo((short)nextClassId.getAndIncrement(), type);
}
}*/
}

@ -0,0 +1,136 @@
/*
* Copyright (c) 2015 jMonkeyEngine
* 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 'jMonkeyEngine' 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.jme3.network.service.rmi;
import com.jme3.network.serializing.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.jws.Oneway;
/**
*
*
* @author Paul Speed
*/
@Serializable
public final class MethodInfo {
public static final MethodInfo NULL_INFO = new MethodInfo();
private String representation;
private short id;
private CallType callType;
private transient Method method;
/**
* For serialization only.
*/
public MethodInfo() {
}
public MethodInfo( short id, Method m ) {
this.id = id;
this.method = m;
this.representation = methodToString(m);
this.callType = getCallType(m);
}
public Object invoke( Object target, Object... parms ) {
try {
return method.invoke(target, parms);
} catch (IllegalAccessException e) {
throw new RuntimeException("Error invoking:" + method + " on:" + target, e);
} catch (IllegalArgumentException e) {
throw new RuntimeException("Error invoking:" + method + " on:" + target, e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error invoking:" + method + " on:" + target, e);
}
}
public short getId() {
return id;
}
public CallType getCallType() {
return callType;
}
public boolean matches( Method m ) {
return representation.equals(methodToString(m));
}
public static String methodToString( Method m ) {
StringBuilder sb = new StringBuilder();
for( Class t : m.getParameterTypes() ) {
if( sb.length() > 0 )
sb.append(", ");
sb.append(t.getName());
}
return m.getReturnType().getName() + " " + m.getName() + "(" + sb + ")";
}
public static CallType getCallType( Method m ) {
if( m.getReturnType() != Void.TYPE )
return CallType.Synchronous;
if( m.getAnnotation(Oneway.class) != null )
return CallType.Asynchronous;
if( m.getAnnotation(Asynchronous.class) == null )
return CallType.Synchronous;
Asynchronous async = m.getAnnotation(Asynchronous.class);
return async.reliable() ? CallType.Asynchronous : CallType.Unreliable;
}
@Override
public int hashCode() {
return representation.hashCode();
}
@Override
public boolean equals( Object o ) {
if( o == this ) {
return true;
}
if( o == null || o.getClass() != getClass() ) {
return false;
}
MethodInfo other = (MethodInfo)o;
return representation.equals(other.representation);
}
@Override
public String toString() {
return "MethodInfo[#" + getId() + ", callType=" + callType + ", " + representation + "]";
}
}

@ -0,0 +1,87 @@
/*
* Copyright (c) 2015 jMonkeyEngine
* 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 'jMonkeyEngine' 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.jme3.network.service.rmi;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
*
*
* @author Paul Speed
*/
public class RemoteObjectHandler implements InvocationHandler {
private final RmiRegistry rmi;
private final byte channel;
private final short objectId;
private final ClassInfo typeInfo;
private final Map<Method, MethodInfo> methodIndex = new ConcurrentHashMap<Method, MethodInfo>();
public RemoteObjectHandler( RmiRegistry rmi, byte channel, short objectId, ClassInfo typeInfo ) {
this.rmi = rmi;
this.channel = channel;
this.objectId = objectId;
this.typeInfo = typeInfo;
}
protected MethodInfo getMethodInfo( Method method ) {
MethodInfo mi = methodIndex.get(method);
if( mi == null ) {
mi = typeInfo.getMethod(method);
if( mi == null ) {
mi = MethodInfo.NULL_INFO;
}
methodIndex.put(method, mi);
}
return mi == MethodInfo.NULL_INFO ? null : mi;
}
@Override
public Object invoke(Object o, Method method, Object[] os) throws Throwable {
MethodInfo mi = getMethodInfo(method);
if( mi == null ) {
// Try to invoke locally
return method.invoke(this, os);
}
return rmi.invokeRemote(channel, objectId, mi.getId(), mi.getCallType(), os);
}
@Override
public String toString() {
return "RemoteObject[#" + objectId + ", " + typeInfo.getName() + "]";
}
}

@ -0,0 +1,147 @@
/*
* Copyright (c) 2015 jMonkeyEngine
* 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 'jMonkeyEngine' 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.jme3.network.service.rmi;
import com.jme3.network.MessageConnection;
import com.jme3.network.service.AbstractClientService;
import com.jme3.network.service.ClientServiceManager;
import com.jme3.network.service.rpc.RpcClientService;
import java.util.ArrayList;
import java.util.List;
/**
*
*
* @author Paul Speed
*/
public class RmiClientService extends AbstractClientService {
private RpcClientService rpc;
private byte defaultChannel;
private short rmiObjectId;
private RmiRegistry rmi;
private volatile boolean isStarted = false;
private final List<ObjectInfo> pending = new ArrayList<ObjectInfo>();
public RmiClientService() {
this((short)-1, (byte)MessageConnection.CHANNEL_DEFAULT_RELIABLE);
}
public RmiClientService( short rmiObjectId, byte defaultChannel ) {
this.defaultChannel = defaultChannel;
this.rmiObjectId = rmiObjectId;
}
public <T> void share( T object, Class<? super T> type ) {
share(defaultChannel, object, type);
}
public <T> void share( byte channel, T object, Class<? super T> type ) {
share(channel, type.getName(), object, type);
}
public <T> void share( String name, T object, Class<? super T> type ) {
share(defaultChannel, name, object, type);
}
public <T> void share( byte channel, String name, T object, Class<? super T> type ) {
if( !isStarted ) {
synchronized(pending) {
if( !isStarted ) {
pending.add(new ObjectInfo(channel, name, object, type));
return;
}
}
}
// Else we can add it directly.
rmi.share(channel, name, object, type);
}
public <T> T getRemoteObject( Class<T> type ) {
return rmi.getRemoteObject(type);
}
public <T> T getRemoteObject( String name, Class<T> type ) {
return rmi.getRemoteObject(name, type);
}
@Override
protected void onInitialize( ClientServiceManager s ) {
rpc = getService(RpcClientService.class);
if( rpc == null ) {
throw new RuntimeException("RmiClientService requires RpcClientService");
}
// Register it now so that it is available when the
// server starts to send us stuff. Waiting until start()
// is too late in this case.
rmi = new RmiRegistry(rpc.getRpcConnection(), rmiObjectId, defaultChannel);
}
@Override
public void start() {
super.start();
// Register all of the classes that have been waiting.
synchronized(pending) {
for( ObjectInfo info : pending ) {
rmi.share(info.channel, info.name, info.object, info.type);
}
pending.clear();
isStarted = true;
}
}
private class ObjectInfo {
byte channel;
String name;
Object object;
Class type;
public ObjectInfo( byte channel, String name, Object object, Class type ) {
this.channel = channel;
this.name = name;
this.object = object;
this.type = type;
}
@Override
public String toString() {
return "ObjectInfo[" + channel + ", " + name + ", " + object + ", " + type + "]";
}
}
}

@ -0,0 +1,56 @@
/*
* Copyright (c) 2015 jMonkeyEngine
* 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 'jMonkeyEngine' 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.jme3.network.service.rmi;
import com.jme3.network.HostedConnection;
/**
* Keeps track of the current connection performing a particular
* RMI call. RMI-based services can use this to find out which
* connection is calling a particular method without having to
* pass additional problematic data on the method calls.
*
* @author Paul Speed
*/
public class RmiContext {
private static final ThreadLocal<HostedConnection> connection = new ThreadLocal<HostedConnection>();
public static HostedConnection getRmiConnection() {
return connection.get();
}
static void setRmiConnection( HostedConnection conn ) {
connection.set(conn);
}
}

@ -0,0 +1,203 @@
/*
* Copyright (c) 2015 jMonkeyEngine
* 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 'jMonkeyEngine' 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.jme3.network.service.rmi;
import com.jme3.network.HostedConnection;
import com.jme3.network.MessageConnection;
import com.jme3.network.Server;
import com.jme3.network.serializing.Serializer;
import com.jme3.network.service.AbstractHostedService;
import com.jme3.network.service.HostedServiceManager;
import com.jme3.network.service.rpc.RpcHostedService;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
*
* @author Paul Speed
*/
public class RmiHostedService extends AbstractHostedService {
static final Logger log = Logger.getLogger(RpcHostedService.class.getName());
public static final String ATTRIBUTE_NAME = "rmi";
private RpcHostedService rpcService;
private short rmiId;
private byte defaultChannel;
private boolean autoHost;
private final Map<String, GlobalShare> globalShares = new ConcurrentHashMap<String, GlobalShare>();
public RmiHostedService() {
this((short)-1, (byte)MessageConnection.CHANNEL_DEFAULT_RELIABLE, true);
}
public RmiHostedService( short rmiId, byte defaultChannel, boolean autoHost ) {
this.rmiId = rmiId;
this.defaultChannel = defaultChannel;
this.autoHost = autoHost;
Serializer.registerClasses(ClassInfo.class, MethodInfo.class);
}
public <T> void shareGlobal( T object, Class<? super T> type ) {
shareGlobal(defaultChannel, type.getName(), object, type);
}
public <T> void shareGlobal( String name, T object, Class<? super T> type ) {
shareGlobal(defaultChannel, name, object, type);
}
public <T> void shareGlobal( byte channel, String name, T object, Class<? super T> type ) {
GlobalShare share = new GlobalShare(channel, object, type);
GlobalShare existing = globalShares.put(name, share);
if( existing != null ) {
// Shouldn't need to do anything actually.
}
// Go through all of the children
for( HostedConnection conn : getServer().getConnections() ) {
RmiRegistry child = getRmiRegistry(conn);
if( child == null ) {
continue;
}
child.share(channel, name, object, type);
}
}
public void setAutoHost( boolean b ) {
this.autoHost = b;
}
public boolean getAutoHost() {
return autoHost;
}
public RmiRegistry getRmiRegistry( HostedConnection hc ) {
return hc.getAttribute(ATTRIBUTE_NAME);
}
/**
* Sets up RMI hosting services for the hosted connection allowing
* getRmiRegistry() to return a valid RmiRegistry object.
* This method is called automatically for all new connections if
* autohost is set to true.
*/
public void startHostingOnConnection( HostedConnection hc ) {
if( log.isLoggable(Level.FINEST) ) {
log.log(Level.FINEST, "startHostingOnConnection:{0}", hc);
}
RmiRegistry rmi = new RmiRegistry(hc, rpcService.getRpcConnection(hc),
rmiId, defaultChannel);
hc.setAttribute(ATTRIBUTE_NAME, rmi);
// Register any global shares
for( Map.Entry<String, GlobalShare> e : globalShares.entrySet() ) {
GlobalShare share = e.getValue();
rmi.share(share.channel, e.getKey(), share.object, share.type);
}
}
/**
* Removes any RMI hosting services associated with the specified
* connection. Calls to getRmiRegistry() will return null for
* this connection.
* This method is called automatically for all leaving connections if
* autohost is set to true.
*/
public void stopHostingOnConnection( HostedConnection hc ) {
RmiRegistry rmi = hc.getAttribute(ATTRIBUTE_NAME);
if( rmi == null ) {
return;
}
if( log.isLoggable(Level.FINEST) ) {
log.log(Level.FINEST, "stopHostingOnConnection:{0}", hc);
}
hc.setAttribute(ATTRIBUTE_NAME, null);
//rpc.close();
}
@Override
protected void onInitialize( HostedServiceManager s ) {
this.rpcService = getService(RpcHostedService.class);
if( rpcService == null ) {
throw new RuntimeException("RmiHostedService requires RpcHostedService");
}
}
/**
* Called internally when a new connection is detected for
* the server. If the current autoHost property is true then
* startHostingOnConnection(hc) is called.
*/
@Override
public void connectionAdded(Server server, HostedConnection hc) {
if( log.isLoggable(Level.FINEST) ) {
log.log(Level.FINEST, "connectionAdded({0}, {1})", new Object[]{server, hc});
}
if( autoHost ) {
startHostingOnConnection(hc);
}
}
/**
* Called internally when an existing connection is leaving
* the server. If the current autoHost property is true then
* stopHostingOnConnection(hc) is called.
*/
@Override
public void connectionRemoved(Server server, HostedConnection hc) {
if( log.isLoggable(Level.FINEST) ) {
log.log(Level.FINEST, "connectionRemoved({0}, {1})", new Object[]{server, hc});
}
if( autoHost ) {
stopHostingOnConnection(hc);
}
}
private class GlobalShare {
byte channel;
Object object;
Class type;
public GlobalShare( byte channel, Object object, Class type ) {
this.channel = channel;
this.object = object;
this.type = type;
}
}
}

@ -0,0 +1,344 @@
/*
* Copyright (c) 2015 jMonkeyEngine
* 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 'jMonkeyEngine' 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.jme3.network.service.rmi;
import com.jme3.network.HostedConnection;
import com.jme3.network.MessageConnection;
import com.jme3.network.service.rpc.RpcConnection;
import com.jme3.network.service.rpc.RpcHandler;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
*
* @author Paul Speed
*/
public class RmiRegistry {
static final Logger log = Logger.getLogger(RmiRegistry.class.getName());
// RPC IDs for calling our remote endpoint
private static final short NEW_CLASS = 0;
private static final short ADD_OBJECT = 1;
private static final short REMOVE_OBJECT = 2;
private RpcConnection rpc;
private short rmiId;
private byte defaultChannel;
private final RmiHandler rmiHandler = new RmiHandler();
private final ClassInfoRegistry classCache = new ClassInfoRegistry();
private final AtomicInteger nextObjectId = new AtomicInteger();
private final ObjectIndex<SharedObject> local = new ObjectIndex<SharedObject>();
private final ObjectIndex<Object> remote = new ObjectIndex<Object>();
// Only used on the server to provide thread-local context for
// local RMI calls.
private HostedConnection context;
public RmiRegistry( RpcConnection rpc, short rmiId, byte defaultChannel ) {
this(null, rpc, rmiId, defaultChannel);
}
public RmiRegistry( HostedConnection context, RpcConnection rpc, short rmiId, byte defaultChannel ) {
this.context = context;
this.rpc = rpc;
this.rmiId = rmiId;
this.defaultChannel = defaultChannel;
rpc.registerHandler(rmiId, rmiHandler);
}
public <T> void share( T object, Class<? super T> type ) {
share(defaultChannel, object, type);
}
public <T> void share( byte channel, T object, Class<? super T> type ) {
share(channel, type.getName(), object, type);
}
public <T> void share( String name, T object, Class<? super T> type ) {
share(defaultChannel, name, object, type);
}
public <T> void share( byte channel, String name, T object, Class<? super T> type ) {
ClassInfo typeInfo = classCache.getClassInfo(type);
local.lock.writeLock().lock();
try {
// First see if we've told the remote end about this class
// before
if( local.classes.put(typeInfo.getId(), typeInfo) == null ) {
// It's new
rpc.callAsync(defaultChannel, rmiId, NEW_CLASS, typeInfo);
// Because type info IDs are global to the class cache,
// we could in theory keep a global index that we broadcast
// on first connection setup... we need only prepopulate
// the index in that case.
}
// See if we already shared an object under that name
SharedObject existing = local.byName.remove(name);
if( existing != null ) {
local.byId.remove(existing.objectId);
rpc.removeHandler(existing.objectId, rmiHandler);
// Need to delete the old one from the remote end
rpc.callAsync(defaultChannel, rmiId, REMOVE_OBJECT, existing.objectId);
// We don't reuse the ID because it's kind of dangerous.
// Churning through a new ID is our safety net for accidents.
}
SharedObject newShare = new SharedObject(name, object, type, typeInfo);
local.byName.put(name, newShare);
local.byId.put(newShare.objectId, newShare);
// Make sure we are setup to receive the remote method calls through
// the RPC service
rpc.registerHandler(newShare.objectId, rmiHandler);
// Let the other end know
rpc.callAsync(defaultChannel, rmiId, ADD_OBJECT, channel, newShare.objectId, name, typeInfo.getId());
// We send the ADD_OBJECT to the other end before releasing the
// lock to avoid a potential inconsistency if two threads try to
// jam the same name at the same time. Otherwise, if the timing were
// right, the remove for one object could get there before its add.
} finally {
local.lock.writeLock().unlock();
}
}
/**
* Returns an object that was previously registered with share().
*/
public <T> T getSharedObject( Class<T> type ) {
return getSharedObject(type.getName(), type);
}
/**
* Returns an object that was previously registered with share().
*/
public <T> T getSharedObject( String name, Class<T> type ) {
local.lock.readLock().lock();
try {
return type.cast(local.byName.get(name));
} finally {
local.lock.readLock().unlock();
}
}
public <T> T getRemoteObject( Class<T> type ) {
return getRemoteObject(type.getName(), type);
}
public <T> T getRemoteObject( String name, Class<T> type ) {
remote.lock.readLock().lock();
try {
return type.cast(remote.byName.get(name));
} finally {
remote.lock.readLock().unlock();
}
}
protected void addRemoteClass( ClassInfo info ) {
if( remote.classes.put(info.getId(), info) != null ) {
throw new RuntimeException("Error class already exists for ID:" + info.getId());
}
}
protected void removeRemoteObject( short objectId ) {
if( log.isLoggable(Level.FINEST) ) {
log.log(Level.FINEST, "removeRemoteObject({0})", objectId);
}
throw new UnsupportedOperationException("Removal not yet implemented.");
}
protected void addRemoteObject( byte channel, short objectId, String name, ClassInfo typeInfo ) {
if( log.isLoggable(Level.FINEST) ) {
log.finest("addRemoveObject(" + objectId + ", " + name + ", " + typeInfo + ")");
}
remote.lock.writeLock().lock();
try {
Object existing = remote.byName.get(name);
if( existing != null ) {
throw new RuntimeException("Object already registered for:" + name);
}
RemoteObjectHandler remoteHandler = new RemoteObjectHandler(this, channel, objectId, typeInfo);
Object remoteObject = Proxy.newProxyInstance(getClass().getClassLoader(),
new Class[] {typeInfo.getType()},
remoteHandler);
remote.byName.put(name, remoteObject);
remote.byId.put(objectId, remoteObject);
} finally {
remote.lock.writeLock().unlock();
}
}
protected Object invokeRemote( byte channel, short objectId, short procId, CallType callType, Object[] args ) {
if( log.isLoggable(Level.FINEST) ) {
log.finest("invokeRemote(" + channel + ", " + objectId + ", " + procId + ", "
+ callType + ", " + (args == null ? "null" : Arrays.asList(args)) + ")");
}
switch( callType ) {
case Asynchronous:
log.finest("Sending reliable asynchronous.");
rpc.callAsync(channel, objectId, procId, args);
return null;
case Unreliable:
log.finest("Sending unreliable asynchronous.");
rpc.callAsync((byte)MessageConnection.CHANNEL_DEFAULT_UNRELIABLE, objectId, procId, args);
return null;
default:
case Synchronous:
log.finest("Sending synchronous.");
Object result = rpc.callAndWait(channel, objectId, procId, args);
if( log.isLoggable(Level.FINEST) ) {
log.finest("->got:" + result);
}
return result;
}
}
/**
* Handle remote object registry updates from the other end.
*/
protected void rmiUpdate( short procId, Object[] args ) {
if( log.isLoggable(Level.FINEST) ) {
log.finest("rmiUpdate(" + procId + ", " + Arrays.asList(args) + ")");
}
switch( procId ) {
case NEW_CLASS:
addRemoteClass((ClassInfo)args[0]);
break;
case REMOVE_OBJECT:
removeRemoteObject((Short)args[0]);
break;
case ADD_OBJECT:
ClassInfo info = remote.classes.get((Short)args[3]);
addRemoteObject((Byte)args[0], (Short)args[1], (String)args[2], info);
break;
}
}
/**
* Handle the actual remote object method calls.
*/
protected Object invokeLocal( short objectId, short procId, Object[] args ) {
// Actually could use a regular concurrent map for this
// Only lock the local registry during lookup and
// not invocation. It prevents a deadlock if the invoked method
// tries to share an object. It should be safe.
SharedObject share = local.byId.get(objectId);
local.lock.readLock().lock();
try {
share = local.byId.get(objectId);
} finally {
local.lock.readLock().unlock();
}
try {
RmiContext.setRmiConnection(context);
return share.invoke(procId, args);
} finally {
RmiContext.setRmiConnection(null);
}
}
private class SharedObject {
private final short objectId;
private final String name;
private final Object object;
private final Class type;
private final ClassInfo classInfo;
public SharedObject( String name, Object object, Class type, ClassInfo classInfo ) {
this.objectId = (short)nextObjectId.incrementAndGet();
this.name = name;
this.object = object;
this.type = type;
this.classInfo = classInfo;
}
public Object invoke( short procId, Object[] args ) {
return classInfo.getMethod(procId).invoke(object, args);
}
}
private class RmiHandler implements RpcHandler {
@Override
public Object call( RpcConnection conn, short objectId, short procId, Object... args ) {
if( objectId == rmiId ) {
rmiUpdate(procId, args);
return null;
} else {
return invokeLocal(objectId, procId, args);
}
}
}
/**
* Keeps a coincident index between short ID, name, and related class info.
* There will be one of these to track our local objects and one to track
* the remote objects and a lock that can guard them.
*/
private class ObjectIndex<T> {
final Map<String, T> byName = new HashMap<String, T>();
final Map<Short, T> byId = new HashMap<Short, T>();
final Map<Short, ClassInfo> classes = new HashMap<Short, ClassInfo>();
final ReadWriteLock lock = new ReentrantReadWriteLock();
public ObjectIndex() {
}
}
}
Loading…
Cancel
Save