Completely changed how XML-RPC invocation is performed. XML-RPC requests are

now handled by helma.main.ApplicationManager, which dispatches requests to the
right application. Also cleaned up XML-RPC access control a big deal, including it
in the generic application properties handling.
Fixes bug 135  http://helma.org/bugs/show_bug.cgi?id=135
This commit is contained in:
hns 2002-10-25 17:08:45 +00:00
parent 6cb41d18fd
commit 4ab51d544a
3 changed files with 87 additions and 113 deletions

View file

@ -93,9 +93,6 @@ public final class Application implements IPathElement, Runnable {
protected volatile long xmlrpcCount = 0; protected volatile long xmlrpcCount = 0;
protected volatile long errorCount = 0; protected volatile long errorCount = 0;
protected static WebServer xmlrpc;
protected XmlRpcAccess xmlrpcAccess;
private String xmlrpcHandlerName;
// the URL-prefix to use for links into this application // the URL-prefix to use for links into this application
private String baseURI; private String baseURI;
@ -118,6 +115,13 @@ public final class Application implements IPathElement, Runnable {
private long lastPropertyRead = 0l; private long lastPropertyRead = 0l;
// the set of prototype/function pairs which are allowed to be called via XML-RPC
private HashSet xmlrpcAccess;
// the name under which this app serves XML-RPC requests. Defaults to the app name
private String xmlrpcHandlerName;
/** /**
* Zero argument constructor needed for RMI * Zero argument constructor needed for RMI
*/ */
@ -215,8 +219,6 @@ public final class Application implements IPathElement, Runnable {
dbSources = new Hashtable (); dbSources = new Hashtable ();
cachenode = new TransientNode ("app"); cachenode = new TransientNode ("app");
xmlrpc = helma.main.Server.getXmlRpcServer ();
xmlrpcAccess = new XmlRpcAccess (this);
} }
/** /**
@ -273,11 +275,6 @@ public final class Application implements IPathElement, Runnable {
userRootMapping.update (); userRootMapping.update ();
nmgr = new NodeManager (this, dbDir.getAbsolutePath (), props); nmgr = new NodeManager (this, dbDir.getAbsolutePath (), props);
xmlrpcHandlerName = props.getProperty ("xmlrpcHandlerName", this.name);
if (xmlrpc != null)
xmlrpc.addHandler (xmlrpcHandlerName, new XmlRpcInvoker (this));
} }
/** /**
@ -303,9 +300,6 @@ public final class Application implements IPathElement, Runnable {
worker.interrupt (); worker.interrupt ();
worker = null; worker = null;
if (xmlrpc != null && xmlrpcHandlerName != null)
xmlrpc.removeHandler (xmlrpcHandlerName);
// stop evaluators // stop evaluators
if (allThreads != null) { if (allThreads != null) {
for (Enumeration e=allThreads.elements (); e.hasMoreElements (); ) { for (Enumeration e=allThreads.elements (); e.hasMoreElements (); ) {
@ -499,6 +493,26 @@ public final class Application implements IPathElement, Runnable {
} }
/**
* Called to execute a method via XML-RPC, usally by helma.main.ApplicationManager
* which acts as default handler/request dispatcher.
*/
public Object executeXmlRpc (String method, Vector args) throws Exception {
xmlrpcCount += 1;
Object retval = null;
RequestEvaluator ev = null;
try {
// check if the properties file has been updated
updateProperties ();
// get evaluator and invoke
ev = getEvaluator ();
retval = ev.invokeXmlRpc (method, args.toArray());
} finally {
releaseEvaluator (ev);
}
return retval;
}
/** /**
* Reset the application's object cache, causing all objects to be refetched from * Reset the application's object cache, causing all objects to be refetched from
* the database. * the database.
@ -1214,11 +1228,23 @@ public final class Application implements IPathElement, Runnable {
requestTimeout = 60000l; requestTimeout = 60000l;
} }
// set base URI // set base URI
String base = props.getProperty ("baseURI"); String base = props.getProperty ("baseuri");
if (base != null) if (base != null)
setBaseURI (base); setBaseURI (base);
else if (baseURI == null) else if (baseURI == null)
baseURI = "/"; baseURI = "/";
// update the XML-RPC access list, containting prototype.method
// entries of functions that may be called via XML-RPC
String xmlrpcAccessProp = props.getProperty ("xmlrpcaccess");
HashSet xra = new HashSet ();
if (xmlrpcAccessProp != null) {
StringTokenizer st = new StringTokenizer (xmlrpcAccessProp, ",; ");
while (st.hasMoreTokens ()) {
String token = st.nextToken ().trim ();
xra.add (token.toLowerCase());
}
}
xmlrpcAccess = xra;
// if node manager exists, update it // if node manager exists, update it
if (nmgr != null) if (nmgr != null)
nmgr.updateProperties (props); nmgr.updateProperties (props);
@ -1246,6 +1272,17 @@ public final class Application implements IPathElement, Runnable {
return props; return props;
} }
/**
* Return the XML-RPC handler name for this app. The contract is to
* always return the same string, even if it has been changed in the properties file
* during runtime, so the app gets unregistered correctly.
*/
public String getXmlRpcHandlerName () {
if (xmlrpcHandlerName == null)
xmlrpcHandlerName = props.getProperty ("xmlrpcHandlerName", this.name);
return xmlrpcHandlerName;
}
/** /**
* *
*/ */
@ -1325,102 +1362,14 @@ public final class Application implements IPathElement, Runnable {
} }
/** /**
* Check if a method may be invoked via XML-RPC on a prototype * Check if a method may be invoked via XML-RPC on a prototype.
*/ */
protected void checkXmlRpcAccess (String proto, String method) throws Exception { protected void checkXmlRpcAccess (String proto, String method) throws Exception {
xmlrpcAccess.checkAccess (proto, method); String key = proto+"."+method;
// XML-RPC access items are case insensitive and stored in lower case
if (!xmlrpcAccess.contains (key.toLowerCase()))
throw new Exception ("Method "+key+" is not callable via XML-RPC");
} }
} }
/**
* XML-RPC handler class for this application.
*/
class XmlRpcInvoker implements XmlRpcHandler {
Application app;
public XmlRpcInvoker (Application app) {
this.app = app;
}
public Object execute (String method, Vector argvec) throws Exception {
app.xmlrpcCount += 1;
Object retval = null;
RequestEvaluator ev = null;
try {
ev = app.getEvaluator ();
retval = ev.invokeXmlRpc (method, argvec.toArray());
} finally {
app.releaseEvaluator (ev);
}
return retval;
}
}
/**
* XML-RPC access permission checker
*/
class XmlRpcAccess {
Application app;
Hashtable prototypes;
long lastmod;
public XmlRpcAccess (Application app) {
this.app = app;
init ();
}
public void checkAccess (String proto, String method) throws Exception {
if (app.props.lastModified () != lastmod)
init ();
Hashtable protoAccess = (Hashtable) prototypes.get (proto.toLowerCase ());
if (protoAccess == null)
throw new Exception ("Method "+method+" is not callable via XML-RPC");
if (protoAccess.get (method.toLowerCase ()) == null)
throw new Exception ("Method "+method+" is not callable via XML-RPC");
}
/*
* create internal representation of XML-RPC-Permissions. They're encoded in the app property
* file like this:
*
* xmlrpcAccess = root.sayHello, story.countMessages, user.login
*
* i.e. a prototype.method entry for each function callable via XML-RPC.
*/
private void init () {
String newAccessprop = app.props.getProperty ("xmlrpcaccess");
Hashtable newPrototypes = new Hashtable ();
if (newAccessprop != null) {
StringTokenizer st = new StringTokenizer (newAccessprop, ",; ");
while (st.hasMoreTokens ()) {
String token = st.nextToken ().trim ();
int dot = token.indexOf (".");
if (dot > -1) {
String proto = token.substring (0, dot).toLowerCase ();
String method = token.substring (dot+1).toLowerCase ();
Hashtable protoAccess = (Hashtable) newPrototypes.get (proto);
if (protoAccess == null) {
protoAccess = new Hashtable ();
newPrototypes.put (proto, protoAccess);
}
protoAccess.put (method, method);
}
}
}
this.prototypes = newPrototypes;
this.lastmod = app.props.lastModified ();
}
}

View file

@ -3,9 +3,7 @@
package helma.main; package helma.main;
import java.util.Hashtable; import java.util.*;
import java.util.Enumeration;
import java.util.Properties;
import java.io.*; import java.io.*;
import java.lang.reflect.*; import java.lang.reflect.*;
import java.rmi.*; import java.rmi.*;
@ -21,15 +19,17 @@ import org.mortbay.http.handler.*;
import org.mortbay.jetty.servlet.*; import org.mortbay.jetty.servlet.*;
import org.mortbay.util.*; import org.mortbay.util.*;
import javax.servlet.Servlet; import javax.servlet.Servlet;
import org.apache.xmlrpc.XmlRpcHandler;
/** /**
* This class is responsible for starting and stopping Helma applications. * This class is responsible for starting and stopping Helma applications.
*/ */
public class ApplicationManager { public class ApplicationManager implements XmlRpcHandler {
private Hashtable applications; private Hashtable applications;
private Hashtable xmlrpcHandlers;
private Properties mountpoints; private Properties mountpoints;
private int port; private int port;
private File hopHome; private File hopHome;
@ -43,6 +43,7 @@ public class ApplicationManager {
this.props = props; this.props = props;
this.server = server; this.server = server;
applications = new Hashtable (); applications = new Hashtable ();
xmlrpcHandlers = new Hashtable ();
mountpoints = new Properties (); mountpoints = new Properties ();
lastModified = 0; lastModified = 0;
} }
@ -93,7 +94,7 @@ public class ApplicationManager {
if (cookieDomain != null) if (cookieDomain != null)
holder.setInitParameter ("cookieDomain", cookieDomain); holder.setInitParameter ("cookieDomain", cookieDomain);
String uploadLimit = props.getProperty (appName+".uploadLimit"); String uploadLimit = props.getProperty (appName+".uploadLimit");
if (uploadLimit != null) if (uploadLimit != null)
holder.setInitParameter ("uploadLimit", uploadLimit); holder.setInitParameter ("uploadLimit", uploadLimit);
// holder.start (); // holder.start ();
context.start (); context.start ();
@ -146,6 +147,8 @@ public class ApplicationManager {
context.destroy (); context.destroy ();
} }
} }
// unregister as XML-RPC handler
xmlrpcHandlers.remove (app.getXmlRpcHandlerName());
app.stop (); app.stop ();
Server.getLogger().log ("Unregistered application "+appName); Server.getLogger().log ("Unregistered application "+appName);
} catch (Exception x) { } catch (Exception x) {
@ -189,6 +192,8 @@ public class ApplicationManager {
context.start (); context.start ();
mountpoints.setProperty (appName, pattern); mountpoints.setProperty (appName, pattern);
} }
// register as XML-RPC handler
xmlrpcHandlers.put (app.getXmlRpcHandlerName(), app);
app.start (); app.start ();
} catch (Exception x) { } catch (Exception x) {
Server.getLogger().log ("Couldn't register and start app: "+x); Server.getLogger().log ("Couldn't register and start app: "+x);
@ -239,6 +244,24 @@ public class ApplicationManager {
return (Application)applications.get(name); return (Application)applications.get(name);
} }
/**
* Implements org.apache.xmlrpc.XmlRpcHandler.execute()
*/
public Object execute (String method, Vector params) throws Exception {
int dot = method.indexOf (".");
if (dot == -1)
throw new Exception ("Method name \""+method+"\" does not specify a handler application");
if (dot == 0 || dot == method.length()-1)
throw new Exception ("\""+method+"\" is not a valid XML-RPC method name");
String handler = method.substring (0, dot);
String method2 = method.substring (dot+1);
Application app = (Application) xmlrpcHandlers.get (handler);
if (app == null)
throw new Exception ("Handler \""+handler+"\" not found for "+method);
return app.executeXmlRpc (method2, params);
}
private String getMountpoint (String appName) { private String getMountpoint (String appName) {
String mountpoint = props.getProperty (appName+".mountpoint"); String mountpoint = props.getProperty (appName+".mountpoint");
if (mountpoint == null) if (mountpoint == null)

View file

@ -359,6 +359,8 @@ import org.apache.xmlrpc.*;
// create application manager // create application manager
appManager = new ApplicationManager (rmiPort, hopHome, appsProps, this); appManager = new ApplicationManager (rmiPort, hopHome, appsProps, this);
if (xmlrpc != null)
xmlrpc.addHandler ("$default", appManager);
} catch (Exception gx) { } catch (Exception gx) {