
org.openrdf.util.rmirouting.ChannelIfaceImpl Maven / Gradle / Ivy
/*
* 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