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:
parent
6cb41d18fd
commit
4ab51d544a
3 changed files with 87 additions and 113 deletions
|
@ -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 ();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue