From 4ab51d544ab9b25bc55057dc3001d08f96145e7d Mon Sep 17 00:00:00 2001 From: hns Date: Fri, 25 Oct 2002 17:08:45 +0000 Subject: [PATCH] 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 --- src/helma/framework/core/Application.java | 163 ++++++++-------------- src/helma/main/ApplicationManager.java | 35 ++++- src/helma/main/Server.java | 2 + 3 files changed, 87 insertions(+), 113 deletions(-) diff --git a/src/helma/framework/core/Application.java b/src/helma/framework/core/Application.java index 3148dd2b..978dc349 100644 --- a/src/helma/framework/core/Application.java +++ b/src/helma/framework/core/Application.java @@ -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; @@ -117,6 +114,13 @@ public final class Application implements IPathElement, Runnable { public DocApplication docApp; 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 (); - } - -} - - - - - diff --git a/src/helma/main/ApplicationManager.java b/src/helma/main/ApplicationManager.java index 8e1427dd..f8ba0536 100644 --- a/src/helma/main/ApplicationManager.java +++ b/src/helma/main/ApplicationManager.java @@ -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; } @@ -93,7 +94,7 @@ public class ApplicationManager { if (cookieDomain != null) holder.setInitParameter ("cookieDomain", cookieDomain); String uploadLimit = props.getProperty (appName+".uploadLimit"); - if (uploadLimit != null) + if (uploadLimit != null) holder.setInitParameter ("uploadLimit", uploadLimit); // holder.start (); context.start (); @@ -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) diff --git a/src/helma/main/Server.java b/src/helma/main/Server.java index 9a85ee78..4a24974c 100644 --- a/src/helma/main/Server.java +++ b/src/helma/main/Server.java @@ -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) {