Allow any java object to be used as Helma object.
This commit is contained in:
parent
f0932b9e5a
commit
20955225d0
2 changed files with 170 additions and 39 deletions
|
@ -8,6 +8,7 @@ import java.lang.reflect.*;
|
||||||
import java.rmi.*;
|
import java.rmi.*;
|
||||||
import java.rmi.server.*;
|
import java.rmi.server.*;
|
||||||
import helma.framework.*;
|
import helma.framework.*;
|
||||||
|
import helma.scripting.*;
|
||||||
import helma.scripting.fesi.*;
|
import helma.scripting.fesi.*;
|
||||||
import helma.objectmodel.*;
|
import helma.objectmodel.*;
|
||||||
import helma.objectmodel.db.*;
|
import helma.objectmodel.db.*;
|
||||||
|
@ -33,13 +34,28 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IRep
|
||||||
|
|
||||||
// the class name of the scripting environment implementation
|
// the class name of the scripting environment implementation
|
||||||
static final String scriptEnvironmentName = "helma.scripting.fesi.Environment";
|
static final String scriptEnvironmentName = "helma.scripting.fesi.Environment";
|
||||||
|
ScriptingEnvironment scriptEnv;
|
||||||
|
|
||||||
|
// the root of the website, if a custom root object is defined.
|
||||||
|
// otherwise this is managed by the NodeManager and not cached here.
|
||||||
|
Object rootObject = null;
|
||||||
|
String rootObjectClass;
|
||||||
|
|
||||||
private String baseURI;
|
/**
|
||||||
|
* The type manager checks if anything in the application's prototype definitions
|
||||||
|
* has been updated prior to each evaluation.
|
||||||
|
*/
|
||||||
public TypeManager typemgr;
|
public TypeManager typemgr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Each application has one internal request evaluator for calling
|
||||||
|
* the scheduler and other internal functions.
|
||||||
|
*/
|
||||||
RequestEvaluator eval;
|
RequestEvaluator eval;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collections for evaluator thread pooling
|
||||||
|
*/
|
||||||
protected Stack freeThreads;
|
protected Stack freeThreads;
|
||||||
protected Vector allThreads;
|
protected Vector allThreads;
|
||||||
|
|
||||||
|
@ -69,19 +85,20 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IRep
|
||||||
protected volatile long xmlrpcCount = 0;
|
protected volatile long xmlrpcCount = 0;
|
||||||
protected volatile long errorCount = 0;
|
protected volatile long errorCount = 0;
|
||||||
|
|
||||||
|
// the URL-prefix to use for links into this application
|
||||||
|
private String baseURI;
|
||||||
|
|
||||||
private DbMapping rootMapping, userRootMapping, userMapping;
|
private DbMapping rootMapping, userRootMapping, userMapping;
|
||||||
|
|
||||||
// the root of the website, if a custom root object is defined.
|
// boolean checkSubnodes;
|
||||||
// otherwise this is managed by the NodeManager and not cached here.
|
|
||||||
IPathElement rootObject = null;
|
|
||||||
String rootObjectClass;
|
|
||||||
|
|
||||||
boolean checkSubnodes;
|
|
||||||
|
|
||||||
|
// name of respone encoding
|
||||||
String charset;
|
String charset;
|
||||||
|
|
||||||
|
// password file to use for authenticate() function
|
||||||
private CryptFile pwfile;
|
private CryptFile pwfile;
|
||||||
|
|
||||||
|
// a cache for parsed skin objects
|
||||||
CacheMap skincache = new CacheMap (100, 0.75f);
|
CacheMap skincache = new CacheMap (100, 0.75f);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -91,11 +108,21 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IRep
|
||||||
super ();
|
super ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build an application with the given name in the home directory. Server-wide properties will
|
||||||
|
* be created if the files are present, but they don't have to.
|
||||||
|
*/
|
||||||
|
public Application (String name, File home) throws RemoteException, IllegalArgumentException {
|
||||||
|
this (name, home,
|
||||||
|
new SystemProperties (new File (home, "server.properties").getAbsolutePath ()),
|
||||||
|
new SystemProperties (new File (home, "db.properties").getAbsolutePath ()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build an application with the given name, app and db properties and app base directory. The
|
* Build an application with the given name, app and db properties and app base directory. The
|
||||||
* app directories will be created if they don't exist already.
|
* app directories will be created if they don't exist already.
|
||||||
*/
|
*/
|
||||||
public Application (String name, SystemProperties sysProps, SystemProperties sysDbProps, File home)
|
public Application (String name, File home, SystemProperties sysProps, SystemProperties sysDbProps)
|
||||||
throws RemoteException, IllegalArgumentException {
|
throws RemoteException, IllegalArgumentException {
|
||||||
|
|
||||||
if (name == null || name.trim().length() == 0)
|
if (name == null || name.trim().length() == 0)
|
||||||
|
@ -104,9 +131,14 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IRep
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.home = home;
|
this.home = home;
|
||||||
|
|
||||||
|
// give the Helma Thread group a name so the threads can be recognized
|
||||||
threadgroup = new ThreadGroup ("TX-"+name);
|
threadgroup = new ThreadGroup ("TX-"+name);
|
||||||
|
|
||||||
String appHome = sysProps.getProperty ("appHome");
|
// check the system props to see if custom app directory is set.
|
||||||
|
// otherwise use <home>/apps/<appname>
|
||||||
|
String appHome = null;
|
||||||
|
if (sysProps != null)
|
||||||
|
appHome = sysProps.getProperty ("appHome");
|
||||||
if (appHome != null && !"".equals (appHome.trim()))
|
if (appHome != null && !"".equals (appHome.trim()))
|
||||||
appDir = new File (appHome);
|
appDir = new File (appHome);
|
||||||
else
|
else
|
||||||
|
@ -115,7 +147,11 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IRep
|
||||||
if (!appDir.exists())
|
if (!appDir.exists())
|
||||||
appDir.mkdirs ();
|
appDir.mkdirs ();
|
||||||
|
|
||||||
String dbHome = sysProps.getProperty ("dbHome");
|
// check the system props to see if custom embedded db directory is set.
|
||||||
|
// otherwise use <home>/db/<appname>
|
||||||
|
String dbHome = null;
|
||||||
|
if (sysProps != null)
|
||||||
|
dbHome = sysProps.getProperty ("dbHome");
|
||||||
if (dbHome != null && !"".equals (dbHome.trim()))
|
if (dbHome != null && !"".equals (dbHome.trim()))
|
||||||
dbDir = new File (dbHome);
|
dbDir = new File (dbHome);
|
||||||
else
|
else
|
||||||
|
@ -124,21 +160,25 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IRep
|
||||||
if (!dbDir.exists())
|
if (!dbDir.exists())
|
||||||
dbDir.mkdirs ();
|
dbDir.mkdirs ();
|
||||||
|
|
||||||
|
// create app-level properties
|
||||||
File propfile = new File (appDir, "app.properties");
|
File propfile = new File (appDir, "app.properties");
|
||||||
props = new SystemProperties (propfile.getAbsolutePath (), sysProps);
|
props = new SystemProperties (propfile.getAbsolutePath (), sysProps);
|
||||||
|
|
||||||
|
// create app-level db sources
|
||||||
File dbpropfile = new File (appDir, "db.properties");
|
File dbpropfile = new File (appDir, "db.properties");
|
||||||
dbProps = new SystemProperties (dbpropfile.getAbsolutePath (), sysDbProps);
|
dbProps = new SystemProperties (dbpropfile.getAbsolutePath (), sysDbProps);
|
||||||
|
|
||||||
|
// the passwd file, to be used with the authenticate() function
|
||||||
File pwf = new File (home, "passwd");
|
File pwf = new File (home, "passwd");
|
||||||
CryptFile parentpwfile = new CryptFile (pwf, null);
|
CryptFile parentpwfile = new CryptFile (pwf, null);
|
||||||
pwf = new File (appDir, "passwd");
|
pwf = new File (appDir, "passwd");
|
||||||
pwfile = new CryptFile (pwf, parentpwfile);
|
pwfile = new CryptFile (pwf, parentpwfile);
|
||||||
|
|
||||||
|
// character encoding to be used for responses
|
||||||
charset = props.getProperty ("charset", "ISO-8859-1");
|
charset = props.getProperty ("charset", "ISO-8859-1");
|
||||||
|
|
||||||
debug = "true".equalsIgnoreCase (props.getProperty ("debug"));
|
debug = "true".equalsIgnoreCase (props.getProperty ("debug"));
|
||||||
checkSubnodes = !"false".equalsIgnoreCase (props.getProperty ("subnodeChecking"));
|
// checkSubnodes = !"false".equalsIgnoreCase (props.getProperty ("subnodeChecking"));
|
||||||
|
|
||||||
// get class name of root object if defined. Otherwise native Helma objectmodel will be used.
|
// get class name of root object if defined. Otherwise native Helma objectmodel will be used.
|
||||||
rootObjectClass = props.getProperty ("rootObject");
|
rootObjectClass = props.getProperty ("rootObject");
|
||||||
|
@ -163,7 +203,7 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IRep
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finish initializing the application
|
* Get the application ready to run, initializing the evaluators and type manager.
|
||||||
*/
|
*/
|
||||||
public void init () throws DbException {
|
public void init () throws DbException {
|
||||||
|
|
||||||
|
@ -386,22 +426,43 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IRep
|
||||||
nmgr.replicateCache (add, delete);
|
nmgr.replicateCache (add, delete);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void ping () {
|
public void ping () {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the application's object cache, causing all objects to be refetched from
|
||||||
|
* the database.
|
||||||
|
*/
|
||||||
|
public void clearCache () {
|
||||||
|
nmgr.clearCache ();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the application's root element to an arbitrary object. After this is called
|
||||||
|
* with a non-null object, the helma node manager will be bypassed. This function
|
||||||
|
* can be used to script and publish any Java object structure with Helma.
|
||||||
|
*/
|
||||||
|
public void setDataRoot (Object root) {
|
||||||
|
this.rootObject = root;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method returns the root object of this application's object tree.
|
* This method returns the root object of this application's object tree.
|
||||||
*/
|
*/
|
||||||
public IPathElement getDataRoot () {
|
public Object getDataRoot () {
|
||||||
|
// if rootObject is set, immediately return it.
|
||||||
|
if (rootObject != null)
|
||||||
|
return rootObject;
|
||||||
|
// check if we ought to create a rootObject from its class name
|
||||||
if (rootObjectClass != null) {
|
if (rootObjectClass != null) {
|
||||||
// create custom root element.
|
// create custom root element.
|
||||||
// NOTE: This is but a very rough first sketch of an implementation
|
// NOTE: This is but a very rough first sketch of an implementation
|
||||||
// and needs much more care.
|
// and needs much more care.
|
||||||
if (rootObject == null) try {
|
if (rootObject == null) try {
|
||||||
Class c = Class.forName (rootObjectClass);
|
Class c = Class.forName (rootObjectClass);
|
||||||
rootObject = (IPathElement) c.newInstance ();
|
rootObject = c.newInstance ();
|
||||||
} catch (Throwable x) {
|
} catch (Throwable x) {
|
||||||
System.err.println ("ERROR CREATING ROOT OBJECT: "+x);
|
System.err.println ("ERROR CREATING ROOT OBJECT: "+x);
|
||||||
}
|
}
|
||||||
|
@ -449,11 +510,14 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IRep
|
||||||
* Return a prototype for a given node. If the node doesn't specify a prototype,
|
* Return a prototype for a given node. If the node doesn't specify a prototype,
|
||||||
* return the generic hopobject prototype.
|
* return the generic hopobject prototype.
|
||||||
*/
|
*/
|
||||||
public Prototype getPrototype (IPathElement n) {
|
public Prototype getPrototype (Object obj) {
|
||||||
String protoname = n.getPrototype ();
|
String protoname = getPrototypeName (obj);
|
||||||
if (protoname == null)
|
if (protoname == null)
|
||||||
return typemgr.getPrototype ("hopobject");
|
return typemgr.getPrototype ("hopobject");
|
||||||
return typemgr.getPrototype (protoname);
|
Prototype p = typemgr.getPrototype (protoname);
|
||||||
|
if (p == null)
|
||||||
|
p = typemgr.getPrototype ("hopobject");
|
||||||
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -584,7 +648,7 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IRep
|
||||||
*/
|
*/
|
||||||
public String getNodeHref (IPathElement elem, String actionName) {
|
public String getNodeHref (IPathElement elem, String actionName) {
|
||||||
// FIXME: will fail for non-node roots
|
// FIXME: will fail for non-node roots
|
||||||
IPathElement root = getDataRoot ();
|
Object root = getDataRoot ();
|
||||||
INode users = getUserRoot ();
|
INode users = getUserRoot ();
|
||||||
|
|
||||||
// check base uri and optional root prototype from app.properties
|
// check base uri and optional root prototype from app.properties
|
||||||
|
@ -598,26 +662,26 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IRep
|
||||||
|
|
||||||
String divider = "/";
|
String divider = "/";
|
||||||
StringBuffer b = new StringBuffer ();
|
StringBuffer b = new StringBuffer ();
|
||||||
IPathElement p = elem;
|
Object p = elem;
|
||||||
int loopWatch = 0;
|
int loopWatch = 0;
|
||||||
|
|
||||||
while (p != null && p.getParentElement () != null && p != root) {
|
while (p != null && getParentElement (p) != null && p != root) {
|
||||||
|
|
||||||
if (rootproto != null && rootproto.equals (p.getPrototype ()))
|
if (rootproto != null && rootproto.equals (getPrototypeName (p)))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
b.insert (0, divider);
|
b.insert (0, divider);
|
||||||
|
|
||||||
// users always have a canonical URL like /users/username
|
// users always have a canonical URL like /users/username
|
||||||
if ("user".equals (p.getPrototype ())) {
|
if ("user".equals (getPrototypeName (p))) {
|
||||||
b.insert (0, UrlEncoder.encode (p.getElementName ()));
|
b.insert (0, UrlEncoder.encode (getElementName (p)));
|
||||||
p = users;
|
p = users;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
b.insert (0, UrlEncoder.encode (p.getElementName ()));
|
b.insert (0, UrlEncoder.encode (getElementName (p)));
|
||||||
|
|
||||||
p = p.getParentElement ();
|
p = getParentElement (p);
|
||||||
|
|
||||||
if (loopWatch++ > 20)
|
if (loopWatch++ > 20)
|
||||||
break;
|
break;
|
||||||
|
@ -653,6 +717,58 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IRep
|
||||||
return debug;
|
return debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// The following methods minic the IPathElement interface. This allows as
|
||||||
|
/// to script any Java object: If the object implements IPathElement (as does
|
||||||
|
/// the Node class in Helma's internal objectmodel) then the corresponding
|
||||||
|
/// method is called in the object itself. Otherwise, a corresponding script function
|
||||||
|
/// is called on the object.
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the name to be used to get this element from its parent
|
||||||
|
*/
|
||||||
|
public String getElementName (Object obj) {
|
||||||
|
if (obj instanceof IPathElement)
|
||||||
|
return ((IPathElement) obj).getElementName ();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a child element of this object by name.
|
||||||
|
*/
|
||||||
|
public Object getChildElement (Object obj, String name) {
|
||||||
|
if (obj instanceof IPathElement)
|
||||||
|
return ((IPathElement) obj).getChildElement (name);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the parent element of this object.
|
||||||
|
*/
|
||||||
|
public Object getParentElement (Object obj) {
|
||||||
|
if (obj instanceof IPathElement)
|
||||||
|
return ((IPathElement) obj).getParentElement ();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the prototype to be used for this object. This will
|
||||||
|
* determine which scripts, actions and skins can be called on it
|
||||||
|
* within the Helma scripting and rendering framework.
|
||||||
|
*/
|
||||||
|
public String getPrototypeName (Object obj) {
|
||||||
|
// check if e implements the IPathElement interface
|
||||||
|
if (obj instanceof IPathElement)
|
||||||
|
// e implements the getPrototype() method
|
||||||
|
return ((IPathElement) obj).getPrototype ();
|
||||||
|
else
|
||||||
|
// use java class name as prototype name
|
||||||
|
return obj.getClass ().getName ();
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the logger object for logging generic events
|
* Get the logger object for logging generic events
|
||||||
|
@ -945,9 +1061,9 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IRep
|
||||||
* and can be switched off by adding "subnodeChecking=false" in the app.properties file.
|
* and can be switched off by adding "subnodeChecking=false" in the app.properties file.
|
||||||
* It is recommended to leave it on except you suffer severe performance problems and know what you do.
|
* It is recommended to leave it on except you suffer severe performance problems and know what you do.
|
||||||
*/
|
*/
|
||||||
public boolean doesSubnodeChecking () {
|
/* public boolean doesSubnodeChecking () {
|
||||||
return checkSubnodes;
|
return checkSubnodes;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,6 @@ public class RequestEvaluator implements Runnable {
|
||||||
static final int XMLRPC = 2; // via XML-RPC
|
static final int XMLRPC = 2; // via XML-RPC
|
||||||
static final int INTERNAL = 3; // generic function call, e.g. by scheduler
|
static final int INTERNAL = 3; // generic function call, e.g. by scheduler
|
||||||
|
|
||||||
// INode root, currentNode;
|
|
||||||
INode[] skinmanagers;
|
INode[] skinmanagers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -147,7 +146,9 @@ public class RequestEvaluator implements Runnable {
|
||||||
app.typemgr.initRequestEvaluator (this);
|
app.typemgr.initRequestEvaluator (this);
|
||||||
// System.err.println ("Type check overhead: "+(System.currentTimeMillis ()-startCheck)+" millis");
|
// System.err.println ("Type check overhead: "+(System.currentTimeMillis ()-startCheck)+" millis");
|
||||||
|
|
||||||
IPathElement root, currentElement;
|
// object refs to ressolve request path
|
||||||
|
Object root, currentElement;
|
||||||
|
|
||||||
// reset skinManager
|
// reset skinManager
|
||||||
skinmanagers = null;
|
skinmanagers = null;
|
||||||
skincache.clear ();
|
skincache.clear ();
|
||||||
|
@ -279,20 +280,19 @@ public class RequestEvaluator implements Runnable {
|
||||||
if (pathItems[i].length () == 0)
|
if (pathItems[i].length () == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
currentElement = currentElement.getChildElement (pathItems[i]);
|
currentElement = app.getChildElement (currentElement, pathItems[i]);
|
||||||
|
|
||||||
// add object to request path if suitable
|
// add object to request path if suitable
|
||||||
if (currentElement != null) {
|
if (currentElement != null) {
|
||||||
// add to reqPath array
|
// add to reqPath array
|
||||||
current = getElementWrapper (currentElement);
|
current = getElementWrapper (currentElement);
|
||||||
reqPath.putProperty (reqPath.size(), current);
|
reqPath.putProperty (reqPath.size(), current);
|
||||||
String pt = currentElement.getPrototype ();
|
String pt = app.getPrototypeName (currentElement);
|
||||||
if (pt != null) {
|
if (pt != null) {
|
||||||
// if a prototype exists, add also by prototype name
|
// if a prototype exists, add also by prototype name
|
||||||
reqPath.putHiddenProperty (pt, current);
|
reqPath.putHiddenProperty (pt, current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -652,7 +652,7 @@ public class RequestEvaluator implements Runnable {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Object invokeFunction (IPathElement node, String functionName, Object[] args)
|
public synchronized Object invokeFunction (Object node, String functionName, Object[] args)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
ESObject obj = null;
|
ESObject obj = null;
|
||||||
if (node == null)
|
if (node == null)
|
||||||
|
@ -859,20 +859,31 @@ public class RequestEvaluator implements Runnable {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ESObject getElementWrapper (IPathElement e) {
|
public ESObject getElementWrapper (Object e) {
|
||||||
|
|
||||||
|
// check if e is an instance of a helma objectmodel node.
|
||||||
if (e instanceof INode)
|
if (e instanceof INode)
|
||||||
return getNodeWrapper ((INode) e);
|
return getNodeWrapper ((INode) e);
|
||||||
|
|
||||||
String protoname = e.getPrototype ();
|
// Gotta find out the prototype name to use for this object...
|
||||||
|
String prototypeName = null;
|
||||||
|
// check if e implements the IPathElement interface
|
||||||
|
if (e instanceof IPathElement)
|
||||||
|
// e implements the getPrototype() method
|
||||||
|
prototypeName = ((IPathElement) e).getPrototype ();
|
||||||
|
else
|
||||||
|
// use java class name as prototype name
|
||||||
|
prototypeName = e.getClass ().getName ();
|
||||||
|
|
||||||
|
ObjectPrototype op = getPrototype (prototypeName);
|
||||||
|
|
||||||
ObjectPrototype op = getPrototype (protoname);
|
|
||||||
if (op == null)
|
if (op == null)
|
||||||
op = esNodePrototype;
|
op = esNodePrototype;
|
||||||
|
|
||||||
return new ESGenericObject (op, evaluator, e);
|
return new ESGenericObject (op, evaluator, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a script wrapper for an implemntation of helma.objectmodel.INode
|
* Get a script wrapper for an implemntation of helma.objectmodel.INode
|
||||||
*/
|
*/
|
||||||
|
@ -948,6 +959,10 @@ public class RequestEvaluator implements Runnable {
|
||||||
prototypes.put (protoName, op);
|
prototypes.put (protoName, op);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to use for caching skins in a Hashtable.
|
||||||
|
* The key consists out of two strings: prototype name and skin name.
|
||||||
|
*/
|
||||||
final class SkinKey {
|
final class SkinKey {
|
||||||
|
|
||||||
final String first, second;
|
final String first, second;
|
||||||
|
|
Loading…
Add table
Reference in a new issue