فهرست منبع

Adding an initial implementation for a service manager module
with custom subclasses and service interfaces for client
services and server-side services (HostedServices).
This code is a copy and refactoring of code I developed for
Mythruna... it worked there but I haven't tested it yet in its
new form. Things may change as I integrate this more closely
with the core Client and Server classes. I wanted to get it
into source control first.
Also included an RPC service implementation which can serve
as the underpinning for other things.
Coming soon: serializer registration service and a simple
RMI service based on the RPC layer.

Paul Speed 10 سال پیش
والد
کامیت
1eb2ba7276

+ 51 - 0
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<ClientServiceManager> 
+                                            implements ClientService { 
+    
+    protected AbstractClientService() {
+    }
+   
+}

+ 70 - 0
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<HostedServiceManager> 
+                                            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) {
+    }
+    
+}

+ 111 - 0
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<S extends ServiceManager> implements Service<S> {
+    
+    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 extends Service<S>> T getService( Class<T> 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 + "]";
+    }
+}

+ 72 - 0
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<ClientServiceManager> {
+    
+    /**
+     *  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 ); 
+}

+ 87 - 0
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<ClientServiceManager> {
+    
+    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);
+    }
+}

+ 74 - 0
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<HostedServiceManager>, 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 ); 
+}

+ 127 - 0
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<HostedServiceManager> {
+    
+    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);
+        }
+    }
+}

+ 67 - 0
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<S> {
+    
+    /**
+     *  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 ); 
+}

+ 160 - 0
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<T> {
+
+    private List<Service<T>> services = new CopyOnWriteArrayList<Service<T>>();
+    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<Service<T>> 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<T> 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<T> 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 <S extends Service<T>> 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 <S extends Service<T>> 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<T> s : services ) {
+            s.terminate(getParent());
+        }               
+    }
+ 
+    /**
+     *  Retrieves the first service of the specified type.
+     */    
+    public <S extends Service<T>> S getService( Class<S> type ) {
+        for( Service s : services ) {
+            if( type.isInstance(s) ) {
+                return type.cast(s);
+            }
+        }
+        return null;
+    }
+ 
+    @Override   
+    public String toString() {
+        return getClass().getName() + "[services=" + services + "]";
+    }
+}
+

+ 123 - 0
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);
+    }
+
+}

+ 247 - 0
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<Short, RpcHandler> handlers = new ConcurrentHashMap<Short, RpcHandler>();
+    
+    /**
+     *  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<Long, ResponseHolder> responses = new ConcurrentHashMap<Long, ResponseHolder>(); 
+ 
+    /**
+     *  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;
+        }
+    }
+}

+ 52 - 0
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 );
+}
+
+

+ 227 - 0
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().
+     *  
+     *  <p>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.</p>
+     */
+    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);
+    }
+
+}
+

+ 98 - 0
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 
+                                          + "]";
+    }
+}

+ 89 - 0
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
+                                          + "]";
+    }
+}