commit
aa067ef60d
@ -0,0 +1,74 @@ |
|||||||
|
/* |
||||||
|
* 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 jme3test.network; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Combines the server instance and a client instance into the |
||||||
|
* same JVM to show an example of, and to test, a pattern like |
||||||
|
* self-hosted multiplayer games. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public class TestChatClientAndServer { |
||||||
|
|
||||||
|
public static void main( String... args ) throws Exception { |
||||||
|
|
||||||
|
System.out.println("Starting chat server..."); |
||||||
|
TestChatServer chatServer = new TestChatServer(); |
||||||
|
chatServer.start(); |
||||||
|
|
||||||
|
System.out.println("Waiting for connections on port:" + TestChatServer.PORT); |
||||||
|
|
||||||
|
// Now launch a client
|
||||||
|
|
||||||
|
TestChatClient test = new TestChatClient("localhost"); |
||||||
|
test.setVisible(true); |
||||||
|
|
||||||
|
// Register a shutdown hook to get a message on the console when the
|
||||||
|
// app actually finishes
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread() { |
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
System.out.println("Client and server test is terminating."); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// Keep running basically forever or until the server
|
||||||
|
// shuts down
|
||||||
|
while( chatServer.isRunning() ) { |
||||||
|
synchronized (chatServer) { |
||||||
|
chatServer.wait(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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 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. If reliable=false |
||||||
|
* is specified then remote method invocation is done over UDP |
||||||
|
* instead of TCP, ie: unreliably... but faster. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
@Retention(value=RUNTIME) |
||||||
|
@Target(value=METHOD) |
||||||
|
public @interface Asynchronous { |
||||||
|
boolean reliable() default true; |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,60 @@ |
|||||||
|
/* |
||||||
|
* 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; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Internal type denoting the type of call to make when remotely |
||||||
|
* invoking methods. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public enum CallType { |
||||||
|
/** |
||||||
|
* Caller will block until a response is received and returned. |
||||||
|
*/ |
||||||
|
Synchronous, |
||||||
|
|
||||||
|
/** |
||||||
|
* Caller does not block or wait for a response. The other end |
||||||
|
* of the connection will also not send one. |
||||||
|
*/ |
||||||
|
Asynchronous, |
||||||
|
|
||||||
|
/** |
||||||
|
* Similar to asynchronous in that no response is expected or sent |
||||||
|
* but differs in that the call will be sent over UDP and so may |
||||||
|
* not make it to the other end. |
||||||
|
*/ |
||||||
|
Unreliable |
||||||
|
} |
@ -0,0 +1,112 @@ |
|||||||
|
/* |
||||||
|
* 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; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Internal information about a shared class. This is the information |
||||||
|
* that is sent over the wire for shared types. |
||||||
|
* |
||||||
|
* @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,104 @@ |
|||||||
|
/* |
||||||
|
* 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; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Internal registry of shared types and their ClassInfo and MethodInfo |
||||||
|
* objects. |
||||||
|
* |
||||||
|
* @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,137 @@ |
|||||||
|
/* |
||||||
|
* 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; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Internal information about shared methods. This is part of the data that |
||||||
|
* is passed over the wire when an object is shared. |
||||||
|
* |
||||||
|
* @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; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Used internally to remotely invoke methods on RMI shared objects. |
||||||
|
* |
||||||
|
* @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,195 @@ |
|||||||
|
/* |
||||||
|
* 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; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* A service that can be added to the client to support a simple |
||||||
|
* shared objects protocol. |
||||||
|
* |
||||||
|
* <p>Objects are shared by adding them to the RmiRegistry with one of the |
||||||
|
* share() methods. Shared objects must have a separate interface and implementation. |
||||||
|
* The interface is what the other end of the connection will use to interact |
||||||
|
* with the object and that interface class must be available on both ends of |
||||||
|
* the connection. The implementing class need only be on the sharing end.</p> |
||||||
|
* |
||||||
|
* <p>Shared objects can be accessed on the other end of the connection by |
||||||
|
* using one of the RmiRegistry's getRemoteObject() methods. These can be |
||||||
|
* used to lookup an object by class if it is a shared singleton or by name |
||||||
|
* if it was registered with a name.</p> |
||||||
|
* |
||||||
|
* <p>Note: This RMI implementation is not as advanced as Java's regular |
||||||
|
* RMI as it won't marshall shared references, ie: you can't pass |
||||||
|
* a shared objects as an argument to another shared object's method.</p> |
||||||
|
* |
||||||
|
* @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; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Shares the specified object with the server and associates it with the |
||||||
|
* specified type. Objects shared in this way are available in the connection-specific |
||||||
|
* RMI registry on the server and are not available to other connections. |
||||||
|
*/ |
||||||
|
public <T> void share( T object, Class<? super T> type ) { |
||||||
|
share(defaultChannel, object, type); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Shares the specified object with the server and associates it with the |
||||||
|
* specified type. Objects shared in this way are available in the connection-specific |
||||||
|
* RMI registry on the server and are not available to other connections. |
||||||
|
* All object related communication will be done over the specified connection |
||||||
|
* channel. |
||||||
|
*/ |
||||||
|
public <T> void share( byte channel, T object, Class<? super T> type ) { |
||||||
|
share(channel, type.getName(), object, type); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Shares the specified object with the server and associates it with the |
||||||
|
* specified name. Objects shared in this way are available in the connection-specific |
||||||
|
* RMI registry on the server and are not available to other connections. |
||||||
|
*/ |
||||||
|
public <T> void share( String name, T object, Class<? super T> type ) { |
||||||
|
share(defaultChannel, name, object, type); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Shares the specified object with the server and associates it with the |
||||||
|
* specified name. Objects shared in this way are available in the connection-specific |
||||||
|
* RMI registry on the server and are not available to other connections. |
||||||
|
* All object related communication will be done over the specified connection |
||||||
|
* channel. |
||||||
|
*/ |
||||||
|
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); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Looks up a remote object on the server by type and returns a local proxy to the |
||||||
|
* remote object that was shared on the other end of the network connection. |
||||||
|
*/ |
||||||
|
public <T> T getRemoteObject( Class<T> type ) { |
||||||
|
return rmi.getRemoteObject(type); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Looks up a remote object on the server by name and returns a local proxy to the |
||||||
|
* remote object that was shared on the other end of the network connection. |
||||||
|
*/ |
||||||
|
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,60 @@ |
|||||||
|
/* |
||||||
|
* 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>(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the HostedConnection that is responsible for any |
||||||
|
* RMI-related calls on this thread. |
||||||
|
*/ |
||||||
|
public static HostedConnection getRmiConnection() { |
||||||
|
return connection.get(); |
||||||
|
} |
||||||
|
|
||||||
|
static void setRmiConnection( HostedConnection conn ) { |
||||||
|
connection.set(conn); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,262 @@ |
|||||||
|
/* |
||||||
|
* 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; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* A service that can be added to the host to support a simple |
||||||
|
* shared objects protocol. |
||||||
|
* |
||||||
|
* <p>Objects are shared by adding them to the RmiRegistry with one of the |
||||||
|
* share() methods. Shared objects must have a separate interface and implementation. |
||||||
|
* The interface is what the other end of the connection will use to interact |
||||||
|
* with the object and that interface class must be available on both ends of |
||||||
|
* the connection. The implementing class need only be on the sharing end.</p> |
||||||
|
* |
||||||
|
* <p>Shared objects can be accessed on the other end of the connection by |
||||||
|
* using one of the RmiRegistry's getRemoteObject() methods. These can be |
||||||
|
* used to lookup an object by class if it is a shared singleton or by name |
||||||
|
* if it was registered with a name.</p> |
||||||
|
* |
||||||
|
* <p>On the hosting side, a special shardGlobal() method is provided that |
||||||
|
* will register shared objects that will automatically be provided to every |
||||||
|
* new joining client and they will all be calling the same server-side instance. |
||||||
|
* Normally, shared objects themselves are connection specific and handled |
||||||
|
* at the connection layer. The shareGlobal() space is a way to have global |
||||||
|
* resources passed directly though the need is relatively rare.</p> |
||||||
|
* |
||||||
|
* <p>Note: This RMI implementation is not as advanced as Java's regular |
||||||
|
* RMI as it won't marshall shared references, ie: you can't pass |
||||||
|
* a shared objects as an argument to another shared object's method.</p> |
||||||
|
* |
||||||
|
* @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); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Shares a server-wide object associated with the specified type. All connections |
||||||
|
* with RMI hosting started will have access to this shared object as soon as they |
||||||
|
* connect and they will all share the same instance. It is up to the shared object |
||||||
|
* to handle any multithreading that might be required. |
||||||
|
*/ |
||||||
|
public <T> void shareGlobal( T object, Class<? super T> type ) { |
||||||
|
shareGlobal(defaultChannel, type.getName(), object, type); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Shares a server-wide object associated with the specified name. All connections |
||||||
|
* with RMI hosting started will have access to this shared object as soon as they |
||||||
|
* connect and they will all share the same instance. It is up to the shared object |
||||||
|
* to handle any multithreading that might be required. |
||||||
|
*/ |
||||||
|
public <T> void shareGlobal( String name, T object, Class<? super T> type ) { |
||||||
|
shareGlobal(defaultChannel, name, object, type); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Shares a server-wide object associated with the specified name over the specified |
||||||
|
* channel. All connections with RMI hosting started will have access to this shared |
||||||
|
* object as soon as they connect and they will all share the same instance. It is up |
||||||
|
* to the shared object to handle any multithreading that might be required. |
||||||
|
* All network communcation associated with the shared object will be done over |
||||||
|
* the specified channel. |
||||||
|
*/ |
||||||
|
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); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set to true if all new connections should automatically have RMI hosting started. |
||||||
|
* Set to false if the game-specific connection setup will call startHostingOnConnection() |
||||||
|
* after some connection setup is done (for example, logging in). Note: generally |
||||||
|
* is is safe to autohost RMI as long as callers are careful about what they've added |
||||||
|
* using shareGlobal(). One reasonable use-case is to shareGlobal() some kind of login |
||||||
|
* service and nothing else. All other shared objects would then be added as connection |
||||||
|
* specific objects during successful login processing. |
||||||
|
*/ |
||||||
|
public void setAutoHost( boolean b ) { |
||||||
|
this.autoHost = b; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns true if RMI hosting is automatically started for all new connections. |
||||||
|
*/ |
||||||
|
public boolean getAutoHost() { |
||||||
|
return autoHost; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the RMI registry for the specific HostedConection. Each connection |
||||||
|
* has its own registry with its own connection-specific shared objects. |
||||||
|
*/ |
||||||
|
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,387 @@ |
|||||||
|
/* |
||||||
|
* 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); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Exposes the specified object to the other end of the connection as |
||||||
|
* the specified interface type. The object can be looked up by type |
||||||
|
* on the other end. |
||||||
|
*/ |
||||||
|
public <T> void share( T object, Class<? super T> type ) { |
||||||
|
share(defaultChannel, object, type); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Exposes, through a specific connection channel, the specified object |
||||||
|
* to the other end of the connection as the specified interface type. |
||||||
|
* The object can be looked up by type on the other end. |
||||||
|
* The specified channel will be used for all network communication |
||||||
|
* specific to this object. |
||||||
|
*/ |
||||||
|
public <T> void share( byte channel, T object, Class<? super T> type ) { |
||||||
|
share(channel, type.getName(), object, type); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Exposes the specified object to the other end of the connection as |
||||||
|
* the specified interface type and associates it with the specified name. |
||||||
|
* The object can be looked up by the associated name on the other end of |
||||||
|
* the connection. |
||||||
|
*/ |
||||||
|
public <T> void share( String name, T object, Class<? super T> type ) { |
||||||
|
share(defaultChannel, name, object, type); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Exposes, through a specific connection channel, the specified object to |
||||||
|
* the other end of the connection as the specified interface type and associates |
||||||
|
* it with the specified name. |
||||||
|
* The object can be looked up by the associated name on the other end of |
||||||
|
* the connection. |
||||||
|
* The specified channel will be used for all network communication |
||||||
|
* specific to this object. |
||||||
|
*/ |
||||||
|
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 a local object that was previously registered with share() using |
||||||
|
* just type registration. |
||||||
|
*/ |
||||||
|
public <T> T getLocalObject( Class<T> type ) { |
||||||
|
return getLocalObject(type.getName(), type); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a local object that was previously registered with share() using |
||||||
|
* name registration. |
||||||
|
*/ |
||||||
|
public <T> T getLocalObject( String name, Class<T> type ) { |
||||||
|
local.lock.readLock().lock(); |
||||||
|
try { |
||||||
|
return type.cast(local.byName.get(name)); |
||||||
|
} finally { |
||||||
|
local.lock.readLock().unlock(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Looks up a remote object by type and returns a local proxy to the remote object |
||||||
|
* that was shared on the other end of the network connection. If this is called |
||||||
|
* from a client then it is accessing a shared object registered on the server. |
||||||
|
* If this is called from the server then it is accessing a shared object registered |
||||||
|
* on the client. |
||||||
|
*/ |
||||||
|
public <T> T getRemoteObject( Class<T> type ) { |
||||||
|
return getRemoteObject(type.getName(), type); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Looks up a remote object by name and returns a local proxy to the remote object |
||||||
|
* that was shared on the other end of the network connection. If this is called |
||||||
|
* from a client then it is accessing a shared object registered on the server. |
||||||
|
* If this is called from the server then it is accessing a shared object registered |
||||||
|
* on the client. |
||||||
|
*/ |
||||||
|
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…
Reference in new issue