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 errorCount = 0;
protected static WebServer xmlrpc;
protected XmlRpcAccess xmlrpcAccess;
private String xmlrpcHandlerName;
// the URL-prefix to use for links into this application
private String baseURI;
@ -118,6 +115,13 @@ public final class Application implements IPathElement, Runnable {
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
*/
@ -215,8 +219,6 @@ public final class Application implements IPathElement, Runnable {
dbSources = new Hashtable ();
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 ();
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 = null;
if (xmlrpc != null && xmlrpcHandlerName != null)
xmlrpc.removeHandler (xmlrpcHandlerName);
// stop evaluators
if (allThreads != null) {
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
* the database.
@ -1214,11 +1228,23 @@ public final class Application implements IPathElement, Runnable {
requestTimeout = 60000l;
}
// set base URI
String base = props.getProperty ("baseURI");
String base = props.getProperty ("baseuri");
if (base != null)
setBaseURI (base);
else if (baseURI == null)
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 (nmgr != null)
nmgr.updateProperties (props);
@ -1246,6 +1272,17 @@ public final class Application implements IPathElement, Runnable {
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 {
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;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.Properties;
import java.util.*;
import java.io.*;
import java.lang.reflect.*;
import java.rmi.*;
@ -21,15 +19,17 @@ import org.mortbay.http.handler.*;
import org.mortbay.jetty.servlet.*;
import org.mortbay.util.*;
import javax.servlet.Servlet;
import org.apache.xmlrpc.XmlRpcHandler;
/**
* This class is responsible for starting and stopping Helma applications.
*/
public class ApplicationManager {
public class ApplicationManager implements XmlRpcHandler {
private Hashtable applications;
private Hashtable xmlrpcHandlers;
private Properties mountpoints;
private int port;
private File hopHome;
@ -43,6 +43,7 @@ public class ApplicationManager {
this.props = props;
this.server = server;
applications = new Hashtable ();
xmlrpcHandlers = new Hashtable ();
mountpoints = new Properties ();
lastModified = 0;
}
@ -146,6 +147,8 @@ public class ApplicationManager {
context.destroy ();
}
}
// unregister as XML-RPC handler
xmlrpcHandlers.remove (app.getXmlRpcHandlerName());
app.stop ();
Server.getLogger().log ("Unregistered application "+appName);
} catch (Exception x) {
@ -189,6 +192,8 @@ public class ApplicationManager {
context.start ();
mountpoints.setProperty (appName, pattern);
}
// register as XML-RPC handler
xmlrpcHandlers.put (app.getXmlRpcHandlerName(), app);
app.start ();
} catch (Exception x) {
Server.getLogger().log ("Couldn't register and start app: "+x);
@ -239,6 +244,24 @@ public class ApplicationManager {
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) {
String mountpoint = props.getProperty (appName+".mountpoint");
if (mountpoint == null)

View file

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