com.jme3.network.service.rmi.RmiHostedService Maven / Gradle / Ivy
Show all versions of jme3-networking Show documentation
/*
* Copyright (c) 2015-2021 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.
*
* 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.
*
* 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.
*
* 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.
*
* 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.
*
* @author Paul Speed
*/
public class RmiHostedService extends AbstractHostedService {
private 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 globalShares = new ConcurrentHashMap<>();
public RmiHostedService() {
this((short)-1, (byte)MessageConnection.CHANNEL_DEFAULT_RELIABLE, true);
}
public RmiHostedService( byte defaultChannel ) {
this((short)-1, defaultChannel, 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 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 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 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 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 HostedConnection. 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.
*/
@SuppressWarnings("unchecked")
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 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;
}
}
}