diff --git a/jme3-networking/src/main/java/com/jme3/network/service/AbstractClientService.java b/jme3-networking/src/main/java/com/jme3/network/service/AbstractClientService.java new file mode 100644 index 000000000..bd1836451 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/AbstractClientService.java @@ -0,0 +1,51 @@ +/* + * 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; + + +/** + * Convenient base class for ClientServices providing some default ClientService + * interface implementations as well as a few convenience methods + * such as getServiceManager() and getService(type). Subclasses + * must at least override the onInitialize() method to handle + * service initialization. + * + * @author Paul Speed + */ +public abstract class AbstractClientService extends AbstractService + implements ClientService { + + protected AbstractClientService() { + } + +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedService.java b/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedService.java new file mode 100644 index 000000000..789767b73 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedService.java @@ -0,0 +1,70 @@ +/* + * 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; + +import com.jme3.network.HostedConnection; +import com.jme3.network.Server; + + +/** + * Convenient base class for HostedServices providing some default HostedService + * interface implementations as well as a few convenience methods + * such as getServiceManager() and getService(type). Subclasses + * must at least override the onInitialize() method to handle + * service initialization. + * + * @author Paul Speed + */ +public abstract class AbstractHostedService extends AbstractService + implements HostedService { + + protected AbstractHostedService() { + } + + /** + * Default implementation does nothing. Implementations can + * override this to peform custom new connection behavior. + */ + @Override + public void connectionAdded(Server server, HostedConnection hc) { + } + + /** + * Default implementation does nothing. Implementations can + * override this to peform custom leaving connection behavior. + */ + @Override + public void connectionRemoved(Server server, HostedConnection hc) { + } + +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java b/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java new file mode 100644 index 000000000..84df3d81b --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java @@ -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; + + +/** + * Base class providing some default Service interface implementations + * as well as a few convenience methods such as getServiceManager() + * and getService(type). Subclasses must at least override the + * onInitialize() method to handle service initialization. + * + * @author Paul Speed + */ +public abstract class AbstractService implements Service { + + private S serviceManager; + + protected AbstractService() { + } + + /** + * Returns the ServiceManager that was passed to + * initialize() during service initialization. + */ + protected S getServiceManager() { + return serviceManager; + } + + /** + * Retrieves the first sibling service of the specified + * type. + */ + protected > T getService( Class type ) { + return type.cast(serviceManager.getService(type)); + } + + /** + * Initializes this service by keeping a reference to + * the service manager and calling onInitialize(). + */ + @Override + public final void initialize( S serviceManager ) { + this.serviceManager = serviceManager; + onInitialize(serviceManager); + } + + /** + * Called during initialize() for the subclass to perform + * implementation specific initialization. + */ + protected abstract void onInitialize( S serviceManager ); + + /** + * Default implementation does nothing. Implementations can + * override this to peform custom startup behavior. + */ + @Override + public void start() { + } + + /** + * Default implementation does nothing. Implementations can + * override this to peform custom stop behavior. + */ + @Override + public void stop() { + } + + /** + * Default implementation does nothing. Implementations can + * override this to peform custom termination behavior. + */ + @Override + public void terminate( S serviceManager ) { + } + + @Override + public String toString() { + return getClass().getName() + "[serviceManager=" + serviceManager + "]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/ClientService.java b/jme3-networking/src/main/java/com/jme3/network/service/ClientService.java new file mode 100644 index 000000000..c8057cb31 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/ClientService.java @@ -0,0 +1,72 @@ +/* + * 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; + + +/** + * Interface implemented by Client-side services that augment + * a network Client's functionality. + * + * @author Paul Speed + */ +public interface ClientService extends Service { + + /** + * Called when the service is first attached to the service + * manager. + */ + @Override + public void initialize( ClientServiceManager serviceManager ); + + /** + * Called when the service manager is started or if the + * service is added to an already started service manager. + */ + @Override + public void start(); + + /** + * Called when the service is shutting down. All services + * are stopped and any service manager resources are closed + * before the services are terminated. + */ + @Override + public void stop(); + + /** + * The service manager is fully shutting down. All services + * have been stopped and related connections closed. + */ + @Override + public void terminate( ClientServiceManager serviceManager ); +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java b/jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java new file mode 100644 index 000000000..df8957201 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java @@ -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; + +import com.jme3.network.Client; + + +/** + * Manages ClientServices on behalf of a network Client object. + * + * @author Paul Speed + */ +public class ClientServiceManager extends ServiceManager { + + private Client client; + + /** + * Creates a new ClientServiceManager for the specified network Client. + */ + public ClientServiceManager( Client client ) { + this.client = client; + } + + /** + * Returns the network Client associated with this ClientServiceManager. + */ + public Client getClient() { + return client; + } + + /** + * Returns 'this' and is what is passed to ClientService.initialize() + * and ClientService.termnate(); + */ + @Override + protected final ClientServiceManager getParent() { + return this; + } + + /** + * Adds the specified ClientService and initializes it. If the service manager + * has already been started then the service will also be started. + */ + public void addService( ClientService s ) { + super.addService(s); + } + + /** + * Removes the specified ClientService from this service manager, stopping + * and terminating it as required. If this service manager is in a + * started state then the service will be stopped. After removal, + * the service will be terminated. + */ + public void removeService( ClientService s ) { + super.removeService(s); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/HostedService.java b/jme3-networking/src/main/java/com/jme3/network/service/HostedService.java new file mode 100644 index 000000000..f522b72d6 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/HostedService.java @@ -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 com.jme3.network.service; + +import com.jme3.network.ConnectionListener; + + +/** + * Interface implemented by Server-side services that augment + * a network Server's functionality. + * + * @author Paul Speed + */ +public interface HostedService extends Service, ConnectionListener { + + /** + * Called when the service is first attached to the service + * manager. + */ + @Override + public void initialize( HostedServiceManager serviceManager ); + + /** + * Called when the service manager is started or if the + * service is added to an already started service manager. + */ + @Override + public void start(); + + /** + * Called when the service is shutting down. All services + * are stopped and any service manager resources are closed + * before the services are terminated. + */ + @Override + public void stop(); + + /** + * The service manager is fully shutting down. All services + * have been stopped and related connections closed. + */ + @Override + public void terminate( HostedServiceManager serviceManager ); +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java b/jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java new file mode 100644 index 000000000..866357852 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java @@ -0,0 +1,127 @@ +/* + * 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; + +import com.jme3.network.ConnectionListener; +import com.jme3.network.HostedConnection; +import com.jme3.network.Server; + + +/** + * Manages HostedServices on behalf of a network Server object. + * All HostedServices are automatically informed about new and + * leaving connections. + * + * @author Paul Speed + */ +public class HostedServiceManager extends ServiceManager { + + private Server server; + private ConnectionObserver connectionObserver; + + /** + * Creates a HostedServiceManager for the specified network Server. + */ + public HostedServiceManager( Server server ) { + this.server = server; + this.connectionObserver = new ConnectionObserver(); + server.addConnectionListener(connectionObserver); + } + + /** + * Returns the network Server associated with this HostedServiceManager. + */ + public Server getServer() { + return server; + } + + /** + * Returns 'this' and is what is passed to HostedService.initialize() + * and HostedService.termnate(); + */ + @Override + protected final HostedServiceManager getParent() { + return this; + } + + /** + * Adds the specified HostedService and initializes it. If the service manager + * has already been started then the service will also be started. + */ + public void addService( HostedService s ) { + super.addService(s); + } + + /** + * Removes the specified HostedService from this service manager, stopping + * and terminating it as required. If this service manager is in a + * started state then the service will be stopped. After removal, + * the service will be terminated. + */ + public void removeService( HostedService s ) { + super.removeService(s); + } + + /** + * Called internally when a new connection has been added so that the + * services can be notified. + */ + protected void addConnection( HostedConnection hc ) { + for( Service s : getServices() ) { + ((HostedService)s).connectionAdded(server, hc); + } + } + + /** + * Called internally when a connection has been removed so that the + * services can be notified. + */ + protected void removeConnection( HostedConnection hc ) { + for( Service s : getServices() ) { + ((HostedService)s).connectionRemoved(server, hc); + } + } + + protected class ConnectionObserver implements ConnectionListener { + + @Override + public void connectionAdded(Server server, HostedConnection hc) { + addConnection(hc); + } + + @Override + public void connectionRemoved(Server server, HostedConnection hc) { + removeConnection(hc); + } + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/Service.java b/jme3-networking/src/main/java/com/jme3/network/service/Service.java new file mode 100644 index 000000000..530237cea --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/Service.java @@ -0,0 +1,67 @@ +/* + * 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; + + +/** + * The base interface for managed services. + * + * @author Paul Speed + */ +public interface Service { + + /** + * Called when the service is first attached to the service + * manager. + */ + public void initialize( S serviceManager ); + + /** + * Called when the service manager is started or if the + * service is added to an already started service manager. + */ + public void start(); + + /** + * Called when the service is shutting down. All services + * are stopped and any service manager resources are closed + * before the services are terminated. + */ + public void stop(); + + /** + * The service manager is fully shutting down. All services + * have been stopped and related connections closed. + */ + public void terminate( S serviceManager ); +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java b/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java new file mode 100644 index 000000000..b8ee7d3c4 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java @@ -0,0 +1,160 @@ +/* + * 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; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * The base service manager class from which the HostedServiceManager + * and ClientServiceManager classes are derived. This manages the + * the underlying services and their life cycles. + * + * @author Paul Speed + */ +public abstract class ServiceManager { + + private List> services = new CopyOnWriteArrayList>(); + private volatile boolean started = false; + + protected ServiceManager() { + } + + /** + * Retreives the 'parent' of this service manager, usually + * a more specifically typed version of 'this' but it can be + * anything the seervices are expecting. + */ + protected abstract T getParent(); + + /** + * Returns the complete list of services managed by this + * service manager. This list is thread safe following the + * CopyOnWriteArrayList semantics. + */ + protected List> getServices() { + return services; + } + + /** + * Starts this service manager and all services that it contains. + * Any services added after the service manager has started will have + * their start() methods called. + */ + public void start() { + if( started ) { + return; + } + for( Service s : services ) { + s.start(); + } + started = true; + } + + /** + * Returns true if this service manager has been started. + */ + public boolean isStarted() { + return started; + } + + /** + * Stops all services and puts the service manager into a stopped state. + */ + public void stop() { + if( !started ) { + throw new IllegalStateException(getClass().getSimpleName() + " not started."); + } + for( Service s : services ) { + s.stop(); + } + started = false; + } + + /** + * Adds the specified service and initializes it. If the service manager + * has already been started then the service will also be started. + */ + public > void addService( S s ) { + services.add(s); + s.initialize(getParent()); + if( started ) { + s.start(); + } + } + + /** + * Removes the specified service from this service manager, stopping + * and terminating it as required. If this service manager is in a + * started state then the service will be stopped. After removal, + * the service will be terminated. + */ + public > void removeService( S s ) { + if( started ) { + s.stop(); + } + services.remove(s); + s.terminate(getParent()); + } + + /** + * Terminates all services. If the service manager has not been + * stopped yet then it will be stopped. + */ + public void terminate() { + if( started ) { + stop(); + } + for( Service s : services ) { + s.terminate(getParent()); + } + } + + /** + * Retrieves the first service of the specified type. + */ + public > S getService( Class type ) { + for( Service s : services ) { + if( type.isInstance(s) ) { + return type.cast(s); + } + } + return null; + } + + @Override + public String toString() { + return getClass().getName() + "[services=" + services + "]"; + } +} + diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcClientService.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcClientService.java new file mode 100644 index 000000000..d9ea134e1 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcClientService.java @@ -0,0 +1,123 @@ +/* + * 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.rpc; + +import com.jme3.network.Client; +import com.jme3.network.util.ObjectMessageDelegator; +import com.jme3.network.service.AbstractClientService; +import com.jme3.network.service.ClientServiceManager; + + +/** + * RPC service that can be added to a network Client to + * add RPC send/receive capabilities. Remote procedure + * calls can be made to the server and responses retrieved. + * Any remote procedure calls that the server performs for + * this connection will be received by this service and delegated + * to the appropriate RpcHandlers. + * + * @author Paul Speed + */ +public class RpcClientService extends AbstractClientService { + + private RpcConnection rpc; + private ObjectMessageDelegator delegator; + + /** + * Creates a new RpcClientService that can be registered + * with the network Client object. + */ + public RpcClientService() { + } + + /** + * Used internally to setup the RpcConnection and MessageDelegator. + */ + @Override + protected void onInitialize( ClientServiceManager serviceManager ) { + Client client = serviceManager.getClient(); + this.rpc = new RpcConnection(client); + + delegator = new ObjectMessageDelegator(rpc, true); + client.addMessageListener(delegator, delegator.getMessageTypes()); + } + + /** + * Used internally to unregister the RPC MessageDelegator that + * was previously added to the network Client. + */ + @Override + public void terminate( ClientServiceManager serviceManager ) { + Client client = serviceManager.getClient(); + client.removeMessageListener(delegator, delegator.getMessageTypes()); + } + + /** + * Performs a synchronous call on the server against the specified + * object using the specified procedure ID. Both inboud and outbound + * communication is done on the specified channel. + */ + public Object callAndWait( byte channel, short objId, short procId, Object... args ) { + return rpc.callAndWait(channel, objId, procId, args); + } + + /** + * Performs an asynchronous call on the server against the specified + * object using the specified procedure ID. Communication is done + * over the specified channel. No responses are received and none + * are waited for. + */ + public void callAsync( byte channel, short objId, short procId, Object... args ) { + rpc.callAsync(channel, objId, procId, args); + } + + /** + * Register a handler that will be called when the server + * performs a remove procedure call against this client. + * Only one handler per object ID can be registered at any given time, + * though the same handler can be registered for multiple object + * IDs. + */ + public void registerHandler( short objId, RpcHandler handler ) { + rpc.registerHandler(objId, handler); + } + + /** + * Removes a previously registered handler for the specified + * object ID. + */ + public void removeHandler( short objId, RpcHandler handler ) { + rpc.removeHandler(objId, handler); + } + +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java new file mode 100644 index 000000000..b5e66c188 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java @@ -0,0 +1,247 @@ +/* + * 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.rpc; + +import com.jme3.network.MessageConnection; +import com.jme3.network.service.rpc.msg.RpcCallMessage; +import com.jme3.network.service.rpc.msg.RpcResponseMessage; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * Wraps a message connection to provide RPC call support. This + * is used internally by the RpcClientService and RpcHostedService to manage + * network messaging. + * + * @author Paul Speed + */ +public class RpcConnection { + + static final Logger log = Logger.getLogger(RpcConnection.class.getName()); + + /** + * The underlying connection upon which RPC call messages are sent + * and RPC response messages are received. It can be a Client or + * a HostedConnection depending on the mode of the RPC service. + */ + private MessageConnection connection; + + /** + * The objectId index of RpcHandler objects that are used to perform the + * RPC calls for a particular object. + */ + private Map handlers = new ConcurrentHashMap(); + + /** + * Provides unique messages IDs for outbound synchronous call + * messages. These are then used in the responses index to + * locate the proper ResponseHolder objects. + */ + private AtomicLong sequenceNumber = new AtomicLong(); + + /** + * Tracks the ResponseHolder objects for sent message IDs. When the + * response is received, the appropriate handler is found here and the + * response or error set, thus releasing the waiting caller. + */ + private Map responses = new ConcurrentHashMap(); + + /** + * Creates a new RpcConnection for the specified network connection. + */ + public RpcConnection( MessageConnection connection ) { + this.connection = connection; + } + + /** + * Clears any pending synchronous calls causing them to + * throw an exception with the message "Closing connection". + */ + public void close() { + // Let any pending waits go free + for( ResponseHolder holder : responses.values() ) { + holder.release(); + } + } + + /** + * Performs a remote procedure call with the specified arguments and waits + * for the response. Both the outbound message and inbound response will + * be sent on the specified channel. + */ + public Object callAndWait( byte channel, short objId, short procId, Object... args ) { + + RpcCallMessage msg = new RpcCallMessage(sequenceNumber.getAndIncrement(), + channel, objId, procId, args); + + // Need to register an object so we can wait for the response. + // ...before we send it. Just in case. + ResponseHolder holder = new ResponseHolder(msg); + responses.put(msg.getMessageId(), holder); + + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "Sending:{0} on channel:{1}", new Object[]{msg, channel}); + } + connection.send(channel, msg); + + return holder.getResponse(); + } + + /** + * Performs a remote procedure call with the specified arguments but does + * not wait for a response. The outbound message is sent on the specified channel. + * There is no inbound response message. + */ + public void callAsync( byte channel, short objId, short procId, Object... args ) { + + RpcCallMessage msg = new RpcCallMessage(-1, channel, objId, procId, args); + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "Sending:{0} on channel:{1}", new Object[]{msg, channel}); + } + connection.send(channel, msg); + } + + /** + * Register a handler that can be called by the other end + * of the connection using the specified object ID. Only one + * handler per object ID can be registered at any given time, + * though the same handler can be registered for multiple object + * IDs. + */ + public void registerHandler( short objId, RpcHandler handler ) { + handlers.put(objId, handler); + } + + /** + * Removes a previously registered handler for the specified + * object ID. + */ + public void removeHandler( short objId, RpcHandler handler ) { + RpcHandler removing = handlers.get(objId); + if( handler != removing ) { + throw new IllegalArgumentException("Handler not registered for object ID:" + + objId + ", handler:" + handler ); + } + handlers.remove(objId); + } + + /** + * Called internally when an RpcCallMessage is received from + * the remote connection. + */ + public void handleMessage( RpcCallMessage msg ) { + + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "handleMessage({0})", msg); + } + RpcHandler handler = handlers.get(msg.getObjectId()); + try { + Object result = handler.call(this, msg.getObjectId(), msg.getProcedureId(), msg.getArguments()); + if( !msg.isAsync() ) { + RpcResponseMessage response = new RpcResponseMessage(msg.getMessageId(), result); + connection.send(msg.getChannel(), response); + } + } catch( Exception e ) { + if( !msg.isAsync() ) { + RpcResponseMessage response = new RpcResponseMessage(msg.getMessageId(), e); + connection.send(msg.getChannel(), response); + } else { + log.log(Level.SEVERE, "Error invoking async call for:" + msg, e); + } + } + } + + /** + * Called internally when an RpcResponseMessage is received from + * the remote connection. + */ + public void handleMessage( RpcResponseMessage msg ) { + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "handleMessage({0})", msg); + } + ResponseHolder holder = responses.remove(msg.getMessageId()); + if( holder == null ) { + return; + } + holder.setResponse(msg); + } + + /** + * Sort of like a Future, holds a locked reference to a response + * until the remote call has completed and returned a response. + */ + private class ResponseHolder { + private Object response; + private String error; + private RpcCallMessage msg; + boolean received = false; + + public ResponseHolder( RpcCallMessage msg ) { + this.msg = msg; + } + + public synchronized void setResponse( RpcResponseMessage msg ) { + this.response = msg.getResult(); + this.error = msg.getError(); + this.received = true; + notifyAll(); + } + + public synchronized Object getResponse() { + try { + while(!received) { + wait(); + } + } catch( InterruptedException e ) { + throw new RuntimeException("Interrupted waiting for respone to:" + msg, e); + } + if( error != null ) { + throw new RuntimeException("Error calling remote procedure:" + msg + "\n" + error); + } + return response; + } + + public synchronized void release() { + if( received ) { + return; + } + // Else signal an error for the callers + this.error = "Closing connection"; + this.received = true; + } + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHandler.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHandler.java new file mode 100644 index 000000000..d7d99981d --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHandler.java @@ -0,0 +1,52 @@ +/* + * 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.rpc; + + +/** + * Implementations of this interface can be registered with + * the RpcClientService or RpcHostService to handle the + * remote procedure calls for a given object or objects. + * + * @author Paul Speed + */ +public interface RpcHandler { + + /** + * Called when a remote procedure call request is received for a particular + * object from the other end of the network connection. + */ + public Object call( RpcConnection conn, short objectId, short procId, Object... args ); +} + + diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java new file mode 100644 index 000000000..4a347b5a3 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java @@ -0,0 +1,227 @@ +/* + * 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.rpc; + +import com.jme3.network.HostedConnection; +import com.jme3.network.Server; +import com.jme3.network.serializing.Serializer; +import com.jme3.network.util.SessionDataDelegator; +import com.jme3.network.service.AbstractHostedService; +import com.jme3.network.service.HostedServiceManager; +import com.jme3.network.service.rpc.msg.RpcCallMessage; +import com.jme3.network.service.rpc.msg.RpcResponseMessage; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * RPC service that can be added to a network Server to + * add RPC send/receive capabilities. For a particular + * HostedConnection, Remote procedure calls can be made to the + * associated Client and responses retrieved. Any remote procedure + * calls that the Client performs for this connection will be + * received by this service and delegated to the appropriate RpcHandlers. + * + * Note: it can be dangerous for a server to perform synchronous + * RPC calls to a client but especially so if not done as part + * of the response to some other message. ie: iterating over all + * or some HostedConnections to perform synchronous RPC calls + * will be slow and potentially block the server's threads in ways + * that can cause deadlocks or odd contention. + * + * @author Paul Speed + */ +public class RpcHostedService extends AbstractHostedService { + + private static final String ATTRIBUTE_NAME = "rpcSession"; + + static final Logger log = Logger.getLogger(RpcHostedService.class.getName()); + + private boolean autoHost; + private SessionDataDelegator delegator; + + /** + * Creates a new RPC host service that can be registered + * with the Network server and will automatically 'host' + * RPC services and each new network connection. + */ + public RpcHostedService() { + this(true); + } + + /** + * Creates a new RPC host service that can be registered + * with the Network server and will optionally 'host' + * RPC services and each new network connection depending + * on the specified 'autoHost' flag. + */ + public RpcHostedService( boolean autoHost ) { + this.autoHost = autoHost; + + // This works for me... has to be different in + // the general case + Serializer.registerClasses(RpcCallMessage.class, RpcResponseMessage.class); + } + + /** + * Used internally to setup the message delegator that will + * handle HostedConnection specific messages and forward them + * to that connection's RpcConnection. + */ + @Override + protected void onInitialize( HostedServiceManager serviceManager ) { + Server server = serviceManager.getServer(); + + // A general listener for forwarding the messages + // to the client-specific handler + this.delegator = new SessionDataDelegator(RpcConnection.class, + ATTRIBUTE_NAME, + true); + server.addMessageListener(delegator, delegator.getMessageTypes()); + + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "Registered delegator for message types:{0}", Arrays.asList(delegator.getMessageTypes())); + } + } + + /** + * When set to true, all new connections will automatically have + * RPC hosting services attached to them, meaning they can send + * and receive RPC calls. If this is set to false then it is up + * to other services to eventually call startHostingOnConnection(). + * + *

Reasons for doing this vary but usually would be because + * the client shouldn't be allowed to perform any RPC calls until + * it has provided more information. In general, this is unnecessary + * because the RpcHandler registries are not shared. Each client + * gets their own and RPC calls will fail until the appropriate + * objects have been registtered.

+ */ + public void setAutoHost( boolean b ) { + this.autoHost = b; + } + + /** + * Returns true if this service automatically attaches RPC + * hosting capabilities to new connections. + */ + public boolean getAutoHost() { + return autoHost; + } + + /** + * Retrieves the RpcConnection for the specified HostedConnection + * if that HostedConnection has had RPC services started using + * startHostingOnConnection() (or via autohosting). Returns null + * if the connection currently doesn't have RPC hosting services + * attached. + */ + public RpcConnection getRpcConnection( HostedConnection hc ) { + return hc.getAttribute(ATTRIBUTE_NAME); + } + + /** + * Sets up RPC hosting services for the hosted connection allowing + * getRpcConnection() to return a valid RPC connection 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); + } + hc.setAttribute(ATTRIBUTE_NAME, new RpcConnection(hc)); + } + + /** + * Removes any RPC hosting services associated with the specified + * connection. Calls to getRpcConnection() will return null for + * this connection. The connection's RpcConnection is also closed, + * releasing any waiting synchronous calls with a "Connection closing" + * error. + * This method is called automatically for all leaving connections if + * autohost is set to true. + */ + public void stopHostingOnConnection( HostedConnection hc ) { + RpcConnection rpc = hc.getAttribute(ATTRIBUTE_NAME); + if( rpc == null ) { + return; + } + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "stopHostingOnConnection:{0}", hc); + } + hc.setAttribute(ATTRIBUTE_NAME, null); + rpc.close(); + } + + /** + * Used internally to remove the message delegator from the + * server. + */ + @Override + public void terminate(HostedServiceManager serviceManager) { + Server server = serviceManager.getServer(); + server.removeMessageListener(delegator, delegator.getMessageTypes()); + } + + /** + * 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}); + } + stopHostingOnConnection(hc); + } + +} + diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcCallMessage.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcCallMessage.java new file mode 100644 index 000000000..70f12f1e0 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcCallMessage.java @@ -0,0 +1,98 @@ +/* + * 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.rpc.msg; + +import com.jme3.network.AbstractMessage; +import com.jme3.network.serializing.Serializable; + + +/** + * Used internally to send RPC call information to + * the other end of a connection for execution. + * + * @author Paul Speed + */ +@Serializable +public class RpcCallMessage extends AbstractMessage { + + private long msgId; + private byte channel; + private short objId; + private short procId; + private Object[] args; + + public RpcCallMessage() { + } + + public RpcCallMessage( long msgId, byte channel, short objId, short procId, Object... args ) { + this.msgId = msgId; + this.channel = channel; + this.objId = objId; + this.procId = procId; + this.args = args; + } + + public long getMessageId() { + return msgId; + } + + public byte getChannel() { + return channel; + } + + public boolean isAsync() { + return msgId == -1; + } + + public short getObjectId() { + return objId; + } + + public short getProcedureId() { + return procId; + } + + public Object[] getArguments() { + return args; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[#" + msgId + ", channel=" + channel + + (isAsync() ? ", async" : ", sync") + + ", objId=" + objId + + ", procId=" + procId + + ", args.length=" + args.length + + "]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcResponseMessage.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcResponseMessage.java new file mode 100644 index 000000000..efb0def6a --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcResponseMessage.java @@ -0,0 +1,89 @@ +/* + * 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.rpc.msg; + +import com.jme3.network.AbstractMessage; +import com.jme3.network.serializing.Serializable; +import java.io.PrintWriter; +import java.io.StringWriter; + + +/** + * Used internally to send an RPC call's response back to + * the caller. + * + * @author Paul Speed + */ +@Serializable +public class RpcResponseMessage extends AbstractMessage { + + private long msgId; + private Object result; + private String error; + + public RpcResponseMessage() { + } + + public RpcResponseMessage( long msgId, Object result ) { + this.msgId = msgId; + this.result = result; + } + + public RpcResponseMessage( long msgId, Throwable t ) { + this.msgId = msgId; + + StringWriter sOut = new StringWriter(); + PrintWriter out = new PrintWriter(sOut); + t.printStackTrace(out); + out.close(); + this.error = sOut.toString(); + } + + public long getMessageId() { + return msgId; + } + + public Object getResult() { + return result; + } + + public String getError() { + return error; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[#" + msgId + ", result=" + result + + "]"; + } +}