Mark Brouwer wrote:

There are various possibilities to achieve what I want but as I don't
want to modify the specs for BasicEndPoint I'm thinking of a custom
dispatcher (which I already have) that will write a proper
RemoteException in the output stream based on certain conditions. The
point is how to make BasicJeriExporter aware of the interception
mechanism I require:

a) clone BasicJeriExporter and sort it out myself, allows for a very
quick and dirty solution but no reuse by others;

b) a service provider mechanism that search for the first service
provider that implements a com.sun (for now) specific interface. That
service provider can implements its own initialization mechanism to hook
in the required logic that verifies whether the object identifier
unknown to the internal export tables need custom dispatching.

c) something I haven't thought of given me being a virgin in this part
of the code/spec.

For those interested attached you will find the spec for
ObjectIdentifierInterceptor (feel free to come up with a better name)
for which currently the implementation can be specified through a system
property (good enough for me) and that is instantiated by
com.sun.jini.jeri.internal.runtime.ObjectTable, changes to ObjectTable
are in the diff.

Custom exception dispatching has been implemented and this made me very
happy, codebase evolution will require some substantial work in Seven so
no experience with the usability of the API (postObjectIdCheck) has been
obtained yet.

Another thing I realized when writing the spec is that because the
interceptor is always called even when the object identifier is in the
table it allows for the interceptor to work as a router. With this it is
possible to redirect request against one service (dispatcher) to another
service (dispatcher) running in the JVM. This allows for live upgrading
of services of a certain type. One can imagine one installs a new
version of a service, deploy [1] it and instruct the interceptor to
divert any incoming requests from the 'old' service to the new one. No
interruption from the perspective of the client, and after a while you
bring down the old one. I realize this is extremely hard for statefull
services, although not impossible, but in the past I've written a few
services for which this feature would have made sense.

[1] this assumes the possibility to reuse the server endpoint and
similar constraints on the invocation dispatcher, but that ain't a
problem in my case.
--
Mark




/*
 * $Header$
 *
 * Copyright 2007 Virgil BV.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sun.jini.jeri.internal.runtime;

import java.rmi.NoSuchObjectException;
import java.util.Collection;

import net.jini.id.Uuid;
import net.jini.jeri.InvocationDispatcher;

/**
 * Defines the contract for an interceptor of remote method invocations
 * associated with a object identifier and allows redirection from one object
 * identifier to another, adding service context elements and custom dispatching
 * in case no object identifier is found in the object table.
 * <p>
 * Interception of remote method invocation will only work for objects that
 * have been exported with DGC disabled.
 *
 * @version $Revision$ $Date$
 */
public interface ObjectIdentifierInterceptor {

    /**
     * Called by the internal object table when it is definite that no exported
     * remote object is available for the object identifier part of the remote
     * method invocation.
     * <p>
     * This method allows the creation of a custom response instead of the
     * default behavior of throwing [EMAIL PROTECTED] NoSuchObjectException} 
when there is
     * no exported object for the given object identifier.
     * <p>
     * In case a custom dispatcher is returned it will be invoked with all of
     * the permissions possessed by the object table, a thread context class
     * loader that is the class loader the invocation dispatcher returned is
     * defined within and a <code>Remote</code> object as the first argument
     * for which no assumptions can be made.
     *
     * @param id the object identifier for which a remote method has been made
     *        and that is not in the table of exported remote objects
     * @param context modifiable server context collection, to which additional
     *        service context elements can be added
     * @return a custom invocation dispatcher to be called by the dispatching
     *         code, or <code>null</code> in case the default behavior must be
     *         effective for the object calling this method
     *
     * @throws NullPointerException if the <code>id</code> or
     *         <code>context</code> argument is <code>null</code>
     */
    InvocationDispatcher objectIdUnknown(Uuid id, Collection context);

    /**
     * Called by the internal object table after the check for the specified
     * object identifier being exported took place.
     * <p>
     * This method allows you to add additional elements to the server context
     * collection that is passed in to the invocation of [EMAIL PROTECTED]
     * InvocationDispatcher#dispatch(Remote, InboundRequest, Collection)} or to
     * redirect to another object identifier in case the object identifier
     * returned by this method differs from the object identifier passed in.
     * <p>
     * For each remote method invocation made this method will only called once.
     *
     * @param id the object identifier for which a remote method has been made
     *        and that has been checked in the object table
     * @param found <code>true</code> indicates the object identifier
     *        represented by the <code>id</code> argument is currently exported
     *        according to the export table, <code>false</code> indicates it is
     *        unknown by the export table
     * @param context modifiable server context collection, to which additional
     *        service context elements can be added
     * @return object identifier for exported object to which to dispatch the
     *         remote method invocation, in case the object identifier differs
     *         from the <code>id</code> argument an additional check will be
     *         made against the export table. In case the returned object
     *         identifier is not exported a call to
     *         [EMAIL PROTECTED] #objectIdUnknown(Uuid, Collection)} is made.
     *
     * @throws NullPointerException if the <code>id</code> or
     *         <code>context</code> argument is <code>null</code>
     */
    Uuid postObjectIdCheck(Uuid id, boolean found, Collection context);
}






--- C:\Documents and Settings\marbro\Local Settings\Temp\___1726210.java        
Tue Jun 19 16:26:13 2007 UTC
+++ 
D:\workspace\cheiron\jsk_2.1\src\com\sun\jini\jeri\internal\runtime\ObjectTable.java
        Sun Jun 17 12:45:37 2007 UTC
@@ -16,6 +16,9 @@
  * 
  */
 
+/*
+ * NOTE: this file has been modified by the Cheiron Project.
+ */
 package com.sun.jini.jeri.internal.runtime;
 
 import com.sun.jini.jeri.internal.runtime.ImplRefManager.ImplRef;
@@ -31,6 +34,7 @@
 import java.rmi.server.Unreferenced;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -103,6 +107,33 @@
     /** table of references to impls exported with DGC */
     private final ImplRefManager implRefManager = new ImplRefManager();
 
+    /**
+     * Optional object table interceptor for special treatment of certain
+     * object identifiers.
+     */
+    private static final ObjectIdentifierInterceptor interceptor;
+
+    static {
+       ObjectIdentifierInterceptor instance = null;
+       String clazz =
+           System.getProperty("com.sun.jini.jeri.objectIdInterceptor");
+       if (clazz != null) {
+           try {
+               instance = (ObjectIdentifierInterceptor)
+                   Class.forName(clazz).newInstance();
+           }
+           catch (Exception e) {
+               logger.log(Level.WARNING,
+                          "unable to install configured object identifier " +
+                          "interceptor of type " + clazz, e);
+           }
+           logger.log(Level.CONFIG,
+                      "installed object identifier interceptor {0}",
+                      instance);
+       }
+       interceptor = instance;
+    }
+
     /** lock guarding keepAliveCount and keeper */
     private final Object keepAliveLock = new Object();
 
@@ -512,7 +543,7 @@
            }
        }
 
-       void dispatch(InboundRequest request)
+       void dispatch(InboundRequest request, Collection context)
            throws IOException, NoSuchObject
        {
            InvocationDispatcher id;
@@ -537,7 +568,7 @@
                    throw new NoSuchObject();
                }
 
-               dispatch(request, id, impl);
+               dispatch(request, id, impl, context);
 
            } finally {
                synchronized (lock) {
@@ -553,7 +584,8 @@
 
        private void dispatch(final InboundRequest request,
                              final InvocationDispatcher id,
-                             final Remote impl)
+                             final Remote impl,
+                             final Collection context)
            throws IOException, NoSuchObject
        {
            Thread t = Thread.currentThread();
@@ -565,12 +597,12 @@
                AccessController.doPrivileged(securityContext.wrap(
                    new PrivilegedExceptionAction() {
                        public Object run() throws IOException {
-                           dispatch0(request, id, impl);
+                           dispatch0(request, id, impl, context);
                            return null;
                        }
                    }), securityContext.getAccessControlContext());
                            
-           } catch (java.security.PrivilegedActionException e) {
+           } catch (PrivilegedActionException e) {
                throw (IOException) e.getException();
            } finally {
                if (ccl != savedCcl || savedCcl != t.getContextClassLoader()) {
@@ -581,7 +613,8 @@
 
        private void dispatch0(final InboundRequest request,
                               final InvocationDispatcher id,
-                              final Remote impl)
+                              final Remote impl,
+                              final Collection context)
            throws IOException
        {
            request.checkPermissions();
@@ -589,7 +622,6 @@
            OutputStream out = request.getResponseOutputStream();
            out.write(Jeri.OBJECT_HERE);
 
-           final Collection context = new ArrayList(5);
            request.populateContext(context);
 
            ServerContext.doWithServerContext(new Runnable() {
@@ -751,6 +783,7 @@
            try {
                InputStream in = request.getRequestInputStream();
                Uuid id = UuidFactory.read(in);
+               Collection context = new ArrayList(5);
 
                if (logger.isLoggable(Level.FINEST)) {
                    logger.log(Level.FINEST, "id={0}", id);
@@ -771,13 +804,40 @@
                    }
 
                    Target target = (Target) get(id);
+
+                   if (interceptor != null) {
+                       Uuid redirectId =
+                           interceptor.postObjectIdCheck(id, target != null,
+                                                         context);
+
+                       if (!id.equals(redirectId)) {
+                           target = (Target) get(redirectId);
+                           if (logger.isLoggable(Level.FINEST)) {
+                               logger.log(Level.FINEST,
+                                   "id {0} is redirected to {1}",
+                                   new Object[] {id, redirectId});
+                           }
+                           id = redirectId;
+                       }
+                   }
+
                    if (target == null) {
-                       logger.log(Level.FINEST, "id not in table");
+                       logger.log(Level.FINEST, "id {0} not in table", id);
                        throw new NoSuchObject();
                    }
-                   target.dispatch(request);
+                   target.dispatch(request, context);
 
                } catch (NoSuchObject e) {
+                   if (interceptor != null) {
+                       InvocationDispatcher dispatcher =
+                           interceptor.objectIdUnknown(id, context);
+                       if (dispatcher != null) {
+                           dispatchUnknownRequest(dispatcher,
+                                                  request, context);
+                           return;
+                       }
+                   }
+
                    in.close();
                    OutputStream out = request.getResponseOutputStream();
                    out.write(Jeri.NO_SUCH_OBJECT);
@@ -818,6 +878,44 @@
            }, Collections.unmodifiableCollection(context));
        }
 
+       private void dispatchUnknownRequest(final InvocationDispatcher id,
+                                           final InboundRequest request,
+                                           final Collection context)
+           throws IOException
+       {
+           OutputStream out =
+               request.getResponseOutputStream();
+           out.write(Jeri.OBJECT_HERE);
+
+           Thread t = Thread.currentThread();
+           ClassLoader ccl = id.getClass().getClassLoader();
+           ClassLoader savedCcl = t.getContextClassLoader();
+           try {
+               if (ccl != savedCcl) {
+                   t.setContextClassLoader(ccl);
+               }
+               AccessController.doPrivileged(
+                   new PrivilegedAction() {
+                       public Object run() {
+                           request.populateContext(context);
+
+                           ServerContext.doWithServerContext(new Runnable() {
+                               public void run() {
+                                   id.dispatch(new Remote() {}, request,
+                                               context);
+                               }
+                           }, Collections.unmodifiableCollection(context));
+
+                           return null;
+                       }
+                   });
+           } finally {
+               if (ccl != savedCcl || savedCcl != t.getContextClassLoader()) {
+                   t.setContextClassLoader(savedCcl);
+               }
+           }
+       }
+
        private class DgcServerImpl implements DgcServer {
 
            public long dirty(Uuid clientID,





Reply via email to