All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openrdf.util.rmirouting.ChannelIfaceImpl Maven / Gradle / Ivy

The newest version!
/*
 *  Copyright (C) 2004 OntoText Lab, Sirma AI OOD
 *
 *  Address:
 *  Europe            135 Tsarigradsko Shose, Sofia 1784, Bulgaria
 *                    (IT Center Office Express, 3rd floor)
 *
 *  North America     438 Isabey Str, Suite 103, Montreal, Canada H4T 1V3
 *
 *  Phone: (+359 2) 9768 310
 *  Fax: (+359 2) 9768 311
 *
 *
 *  E-mail:                          [email protected]
 *  Web:                             http://www.ontotext.com
 *  Sirma Group International Corp.  http://www.sirma.com
 *  Sirma AI Ltd.                    http://www.sirma.bg
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.openrdf.util.rmirouting;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.rmi.NoSuchObjectException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.server.Unreferenced;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

/**
 * 

The implementation of a server side ChannelIface wraper used to handle * all the method invocation requests made by a client. It's instances can be accessed * via the standart RMI subsystem.

*

To create and support the notion of session we provide a single, inheritable server context, * into which the current session settings are stored and mantained.

*

This is necessary because, while working with Sesame remotely through some already exported * object references, you share server resources with the other users, which may work simolatenously * with Sesame either via the HTTP or RMI. So these context help so keep track of the user from * whict the cirrrent request is originated. *

An example of such different, per session, settings are current user id, its password, * currently selected reposiotry, etc. So to allow a smooth multiuser access trough all * the various access layers to a single running Sesame server. To identify the different * conteexts we use a cookie which is unique among the different sessinos, but its * value is inherited from the current one, automaticaly, when a stub is created at the server.

*

The usual way to export a stub, allowing remote access to an instance, is through * the static createStub() method of this class.

*
 * ...
 * SesameService localSesame = Sesame.getService("system.conf");
 * Remote remoteServiceStub = ChannelIfaceImpl.createStub(localSesame);
 * ...
 * 
*

While transported to the client. it can wrap this Remote ref into dynamic proxy * localy with the static ChannelIfaceInvocation.wrapIt() method.

*
 * ...
 * SesameService remoteSesame = (SesameService)ChannelIfaceInvocation.wrapIt(remote);
 * remoteSesame.login(user, pass);
 * ...
 * 
* All this wrapping is made automaticaly becasue all the remote calls are routed * trough instances of ChannelIfaceImpl and ChannelIfaceInvocation * where such wrappig/stubbing is done for all method arguments and its return value on both sides of the channel.

*

So only the first ever instance(usualy the one retieved from the RMI Registry) should be wrapped * in that manner as it is shown in the example above. *

Example on how to bind a factory to the local registry:

*
 *  ...
 *     try {
 *       SesameStartup.initialize(system_conf_path);
 *       String sFound[] = null;
 *       try {
 *         sFound = java.rmi.Naming.list("rmi://localhost:"+RMICenter.sRMIPort+"/FactoryInterface");
 *       } catch (java.rmi.ConnectException e) {
 *       }
 *       if (sFound == null || sFound.length == 0) {
 *           SesameServer.getSystemConfig().setRMIFactory("org.openrdf.sesame.server.rmi.FactoryInterfaceImpl", Integer.parseInt(RMICenter.sRMIPort));
 *           SesameServer.getSystemConfig().setRMIEnabled(true);
 *           FactoryInterfaceImpl.bind(new Integer(RMICenter.sRMIPort));
 *           ThreadLog.log("RMI binded to "+RMICenter.sRMIPort);
 *         } else {
 *           ThreadLog.log("Probably RMI binded to "+sFound[0]);
 *      }
 *  ...
 * 
* *

and a short example showing how you could use Sesame locally or remote:

*
 *  ...
 *        FactoryInterface fi = null;
 *        SesameRepository rep = null;
 *        if (bRemoteOrLocal) {
 * //remote
 *          try {
 *            fi = (FactoryInterface)ChannelIfaceInvocation.
 *                 wrapIt(java.rmi.Naming.lookup("//localhost:"+sRMIPort+"/FactoryInterface"));
 *          } catch (Throwable t) {
 *            t.printStackTrace();
 *            return;
 *          }
 *
 *          SesameService si = fi.getService();
 *          si.login("testuser", "testpass");
 *          rep = si.getRepository(sRepository);
 *        } else {
 * //local
 *          SesameStartup.initialize(sSystemConfFile);
 *          rep = SesameServer.getLocalService().getRepository(RMICenter.sRepository);
 *        }
 * // eval a simple SeRQL query, no matter where is Sesame
 *        String query = "select X from "+
 *          "{X} <rdf:type> {<rdfs:Class>} "+
 *          "using namespace "+
 *          "rdf = <!http://www.w3.org/1999/02/22-rdf-syntax-ns#> , "+
 *          "rdfs = <!http://www.w3.org/2000/01/rdf-schema#> ";
 *
 *          LocalQueryListener sysOutQL = new LocalQueryListener(-1);
 *          rep.performTableQuery(QueryLanguage.SERQL, query, sysOutQL);
 *  ...
 * 
* * @author Damyan Ognyanoff * @version 1.0 */ public class ChannelIfaceImpl implements ChannelIface, Unreferenced { // begin - static part of the definition /** * the cookie is used to identify the proper server context (if any) in which the stub was created * on the server. It is used in conjuntion with set|Prolog/Epilog|Action. * To aid the developer when switching to the proper server context. */ static ThreadLocal currentCookie = new ThreadLocal(); /** * use it to receive the value of the cookie for the current thread * @return the thread cookie * @todo: probably we should use InheritableThreadLocal instead */ static Object getCurrentCookie() { return currentCookie.get(); } /** * use it to set the thread cookie * @param cookie to set */ public static void setCurrentCookie(Object cookie) { currentCookie.set(cookie); } /** * A map keeping correspondence between local objects and their stubs at the server. * Used to resolve the object by its stub when such a stub is used as argument of the method * being invoked */ protected static HashMap map = new HashMap(); /** * associate a stub with its wraped object * @param stub that is created to wrap an object * @param inst is the wrapped object */ protected static void putRef(Remote stub, Object inst) { synchronized (map) { map.put(stub, inst); } } /** * a way to retrieve an associated instance by its wraping stub * @param stub for which the associated instance to be retrieved * @return the associated with the stub instance or null if not found in the map */ public static ChannelIfaceImpl getRef(Remote stub) { synchronized (map) { return (ChannelIfaceImpl)map.get(stub); } } /** * an object used to synchronize the access to the registered prolog/epilog actions */ private static Object SYNC_PROLOG = new Object(); /** * an RoutingAction object to be invoked just BEFORE to execute * the requested method. Used to setup the proper server context, if such swithcing is necessary. */ private static RoutingAction aProlog = null; /** * an RoutingAction object to be invoked just AFTER the execution * of a method. Used to cleanup the server context. */ private static RoutingAction anEpilog = null; /** * a way to set an aicton which can be able to setup the proper context at the server. * It should use the current thread cookie * @param newAction to be set */ public static void setPrologAction(RoutingAction newAction) { synchronized (SYNC_PROLOG) { aProlog = newAction; } } /** * a way to set an action which can cleanup the proper context at the server. * It should use the current thread cookie * @param newAction to be set */ public static void setEpilogAction(RoutingAction newAction) { synchronized (SYNC_PROLOG) { aProlog = newAction; } } /** * a map which keep correspondence between native classes and their names. * It is used to speed up, in some way, the method discovery from the description * passed as first argument of the invoke method. */ private static HashMap primitiveTypes = new HashMap(); static { primitiveTypes.put(byte.class.getName(),byte.class); primitiveTypes.put(char.class.getName(),char.class); primitiveTypes.put(double.class.getName(),double.class); primitiveTypes.put(float.class.getName(),float.class); primitiveTypes.put(int.class.getName(),int.class); primitiveTypes.put(long.class.getName(),long.class); primitiveTypes.put(short.class.getName(),short.class); primitiveTypes.put(boolean.class.getName(),boolean.class); } /** * Creates an instance of the ChannelIfaceImpl which wraps the passed object * and register it into the RMI object table, so its methods can be invoked remotely by * its RemoteRef. An association between the stub and wraped instance is stored into a * map member and can be queried later with getRef. * * @param instance to be wraped and for which an Remote stub to be created * @return the remote stub to the wraped instance. * * Note: the wraped instance should implement at least one java interface. The methods * of the all the implemented interfaces by the instance and its ancestors, down * to the Object class, will be exposed to the client and be visible and availible * for invocation. */ public static Remote createStub(Object instance) { Remote result = new ChannelIfaceImpl(instance); try { Remote stub = UnicastRemoteObject.exportObject(result); putRef(stub, result); return stub; } catch (RemoteException e) { throw new RuntimeException("error while exporting the stub!", e); } } //createStub() // END - static part of the definition /** * unreferenced would be invoked by the DGC when the last remote * reference to thist stub is being 'garbage collected'. At this point we safely * can remove the association between the stub and the wraped instance form the map. * * It is derived from the java.rmi.server.Unreferenced interface. */ public void unreferenced() { if (true == instance instanceof KeepAliveWhenUnreferenced ) return; synchronized (map) { Iterator pairs = map.entrySet().iterator(); while (pairs.hasNext()) { Map.Entry e = (Map.Entry)pairs.next(); if (e.getValue().equals(this)) { try { UnicastRemoteObject.unexportObject(this,true); } catch (NoSuchObjectException eNSO) { // we do not need any special handling here. // at least in my opinion :-) } pairs.remove(); break; } } } } //unreferenced() /** * a ref to the wrapped instance is stored here */ transient Object instance; /** * the cookie associated with the stub. it is derived from the 'global' one and can be * propagated to all the stubs created during a single remote method invocation. */ transient private Object _cookie; /** * an array of the interfaces implemented by the instance is cached here * This member is constructed within buildInterfacesTable method and * it is not a subject of modification during the lifetime of the stub. */ String[] interfaces = null; /** * the same kind of cache as the above one, but instead of the names, we keep * the actual Class objects of the implemented interfaces */ Class[] ifaceClasses = null; /** * a private method in which all the super classes are traversed up to the * Object class and all implemented interfaces are collected */ private void buildInterfacesTable() { // an dynamic storage for the classes and ther names Vector supers = new Vector(); Vector superClasses = new Vector(); // we start from the exact instance Class Class current = instance.getClass(); // loop down until we reach the Object while (!current.equals(Object.class)) { // get all the implemented interfaces by this class // these are usually ones found after the 'implements' clause of the class declatarion Class[] ifaces = current.getInterfaces(); for (int i = 0; i < ifaces.length; i++) { // keep them all Class[] ifP = ifaces[i].getInterfaces(); if (ifP != null) { for (int j = 0; j < ifP.length; j++) { if (!supers.contains(ifP[j].getName())) { supers.add(ifP[j].getName()); superClasses.add(ifP[j]); } } } if (!supers.contains(ifaces[i].getName())) { supers.add(ifaces[i].getName()); superClasses.add(ifaces[i]); } } // move down the hirarch chain current = current.getSuperclass(); } // while (class chain) // build the Array storages an dfill them interfaces = new String[supers.size()]; ifaceClasses = new Class[supers.size()]; int i3=0; for (Iterator i = supers.iterator(); i.hasNext(); i3++) { interfaces[i3] = (String)i.next(); ifaceClasses[i3] = (Class)superClasses.get(i3); } // for } //buildInterfacesTable /** * the only constructor availible. it is protected and * so the only way to obtain some instances is through the static createStub() method * * @param instance which is about to be wrapped */ protected ChannelIfaceImpl(Object instance) { // keep the instance this.instance = instance; // fint teh inmlemented interfaces buildInterfacesTable(); // and store the current cookie fro later reference this._cookie = getCurrentCookie(); } //ChannelIfaceImpl /** * getInterfaces is used by the client to obtain a list of the implemented, * by the wrapped instance, interfaces . This list is used to create a dynamic * proxy ont the client side, which is used to invoke any of the methods declared * within these interfaces. * @return an array of Strings with the class names of the implemented interfaces * @throws RemoteException */ public String[] getInterfaces() throws RemoteException { return interfaces; } /** * invoke - invokes a method of an 'instance' via reflection * @param methodDescription a description of the method being invoked * @param args - parameters opassed to the method during the call * @return the result of the invocation or null if the method returns 'void' * @throws RemoteException */ public Object invoke(Object methodDescription, Object[]args) throws RemoteException { // set the thread cookie setCurrentCookie(_cookie); // parse the chat array which holds the method description // as you can see the buffer starts with the name of the method // followed by all the class names of its arguments separated by '|' character java.util.StringTokenizer tokenizer = new java.util.StringTokenizer(new String((char[])methodDescription), "|", false); // a class array used for the method lookup Class[] paramTypes = null; if (args != null) paramTypes = new Class[args.length]; else paramTypes = new Class[0]; //at least one token should be present in the descr. - the method name // keep track of all arguments which are proxies itself so to flush their // batch- job queues. see the 'batch' method description for more on 'batch' jobs Vector batchedParams = new Vector(); final String methodName = tokenizer.nextToken(); try { int i=0; // loop through the tokens forun in the description while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); try { // try to resolve the class by the passed name paramTypes[i] = Class.forName(token); } catch (ClassNotFoundException e) { // not found - probably it is a native type so try to looku in the // 'primitiveTypes' map Object oClass = primitiveTypes.get(token); // in case we do not found it there we shoud rise the 'ClassNotFound' exception. if (oClass == null) throw e; // keep the rsolved class into the param lookup array paramTypes[i] = (Class)oClass; } // catch class not found // let chek if it is a proxy created by the server in some previous moment. // we just lookup them into the associations kept in 'map' member if (args[i] instanceof ChannelIface) { ChannelIface tryAsLocal = getRef((Remote)args[i]); if (tryAsLocal != null) { // we found it there. how lucky we are so pass its wraped the instance directly // instead of its remote wraper if (tryAsLocal instanceof ChannelIfaceImpl) args[i] = ((ChannelIfaceImpl)tryAsLocal).instance; } else { // hmm ... missing - at least we should wrap it in order to args[i] = ChannelIfaceInvocation.wrapIt(args[i]); // because the server can invoke some batchable methods we need to // remember that particular instance so to flush the batchjob queue // right after the server complete the method if (args[i] instanceof Proxy) batchedParams.add(Proxy.getInvocationHandler(args[i])); } } i++; } // if there is an registered prolog action and we have a 'good' cookie to pass // do that the prolog action if (aProlog != null && _cookie != null) { aProlog.doRoutingAction(_cookie); } // Method m = instance.getClass().getMethod(methodName, paramTypes); // m.setAccessible(true); // try to find the method description from the supportrd interfaces. This is made // because the exact instance class can be 'inaccessible' - e.g. with non-public // in such a case we cannot invoke its methods from here, but if we get the // right method from an inmplemented by the instance interface, we safely can invoke it // through reflection. Method m = null; for (int ifaceCount = 0; ifaceCount < ifaceClasses.length; ifaceCount++) { try{ m = ifaceClasses[ifaceCount].getMethod(methodName, paramTypes); } catch (NoSuchMethodException _nsme){ m = null;} if (m != null) { break; } } if (m == null) { // let's check if at least, Object.class has such signature m = Object.class.getMethod(methodName, paramTypes); // if we are unlucky let's thge Exception go up so the user should know } // finally we can invoke it and collect its return value Object result = null; // try { result = m.invoke(instance, args); // } catch (Error e) { // e.printStackTrace(); // throw new RuntimeException("Server's Error:"+e.getMessage()); // } // if we have some Proxy parameters passed, ensure that their // batched queues are flushed now Iterator iter = batchedParams.iterator(); while (iter.hasNext()) { ChannelIfaceInvocation obj = (ChannelIfaceInvocation)iter.next(); //flush the queue of that argument instance obj.addJob(null); } batchedParams.clear(); batchedParams = null; // if there is an registered epilog action - invoke it with the our current cookie if (anEpilog != null && _cookie != null) { anEpilog.doRoutingAction(_cookie); } // let explore the result a bit. If it do not implement Remote or Serializable // or it is not a natiove one - we can create a stub and return it instead if (result != null && false == result instanceof Remote && false == result.getClass().isPrimitive() && false == result instanceof java.io.Serializable) { result = ChannelIfaceImpl.createStub(result); } // if it is a wrapped proxy - e.g. it was wraped because it is not our for example // unwrap and return the remote iinstance instead. This way we avoid the possability // to wrap an instance multiple times if (result instanceof Proxy) { InvocationHandler iH = Proxy.getInvocationHandler(result); if (iH instanceof ChannelIfaceInvocation) result = ((ChannelIfaceInvocation)iH).remoteInstance; } return result; } catch (InvocationTargetException t) { // if the reflection do not goes smootly - wrap the 'cause' so the client to be // able to find out what was went wrong throw new RemoteException("While invoking "+methodName, t.getCause()); } catch (Exception t) { // in case of any 'normal' exception - route it to client by // wraping it into a remote one - so it can be safely unwraped at client side throw new RemoteException("While invoking "+methodName, t); } // note that only the Exceptions are routed back to the client // so eny Error - derived exception shouldn;t be catched by that code } //invoke /** * gotFinalized() used by the client to notify this stub thet it was * being 'garbage collected' at client side so we ca be quite sure that the * association between this stub and the wraped instance can be safely removed from the storage map * @throws RemoteException */ public void gotFinalized() throws RemoteException { unreferenced(); } /** * here we got the tricky part. * The method batch is used to pass a bunch of methods * that needs to be invoked by this stub with just one RMI call. In such a way we can reduce * the network traffic and long delays because of the time needed by the RMI * to complete a single method call. * Not all invoked methods can be a subject of such batching. We decide to * batch only methods that return 'void' and their arguments are either Serializable * or just a native ones (e.g. char, byte, long, etc.) * the client proxy can decide while it shoud commit the 'batchJob' queue. This * usually can happen if a 'non-void' method or a method with non-Seralizable' or native * arguments is being invoked. In such a case first the queue is flushed and the * that method is passed to the stub here. In order to enable this functionality the wrapped * instance should implement Batchable interface. * * @param jobs an array of Job objects that we need to process at once * each Job object has the method description and array arguments use so wen can invoke it directly * @throws RemoteException that can wrap any of the exception conditions found during the execution */ public void batch(Object[] jobs) throws RemoteException { for (int i = 0; i < jobs.length; i++) { Job job = (Job)jobs[i]; java.io.ByteArrayInputStream buf = new java.io.ByteArrayInputStream(job.serializedArgs); // java.io.BufferedInputStream bufinput = new java.io.BufferedInputStream(buf, 1024); Object[] args = null; try { java.io.ObjectInputStream inputStream = new java.io.ObjectInputStream(buf); args = (Object[])inputStream.readObject(); } catch (ClassNotFoundException ec) { ec.printStackTrace(); } catch (java.io.IOException e) { // should throw na exception here e.printStackTrace(); } // finally { // try { // bufinput.close(); // buf.close(); // } catch (java.io.IOException eee) {} // } invoke(job.methodDescription, args); } }//batch } //ChannelIfaceImpl