* Implement code injection feature to RhinoEngine/Core to allow

app.addRepository() to immediately compile new resources.
  (merge from helma_1_5 branch)
This commit is contained in:
hns 2006-10-16 13:45:53 +00:00
parent 58e9431da1
commit db45092717
10 changed files with 174 additions and 75 deletions

View file

@ -19,10 +19,7 @@ package helma.framework.core;
import helma.extensions.ConfigurationException; import helma.extensions.ConfigurationException;
import helma.extensions.HelmaExtension; import helma.extensions.HelmaExtension;
import helma.framework.*; import helma.framework.*;
import helma.framework.repository.ResourceComparator; import helma.framework.repository.*;
import helma.framework.repository.Repository;
import helma.framework.repository.FileResource;
import helma.framework.repository.FileRepository;
import helma.main.Server; import helma.main.Server;
import helma.objectmodel.*; import helma.objectmodel.*;
import helma.objectmodel.db.*; import helma.objectmodel.db.*;
@ -37,7 +34,7 @@ import java.util.ArrayList;
/** /**
* The central class of a Helma application. This class keeps a pool of so-called * The central class of a Helma application. This class keeps a pool of
* request evaluators (threads with JavaScript interpreters), waits for * request evaluators (threads with JavaScript interpreters), waits for
* requests from the Web server or XML-RPC port and dispatches them to * requests from the Web server or XML-RPC port and dispatches them to
* the evaluators. * the evaluators.
@ -167,6 +164,8 @@ public final class Application implements IPathElement, Runnable {
private ResourceComparator resourceComparator; private ResourceComparator resourceComparator;
private Resource currentCodeResource;
/** /**
* Simple constructor for dead application instances. * Simple constructor for dead application instances.
*/ */
@ -1768,6 +1767,28 @@ public final class Application implements IPathElement, Runnable {
return Collections.unmodifiableList(repositories); return Collections.unmodifiableList(repositories);
} }
/**
* Set the code resource currently being evaluated/compiled. This is used
* to set the proper parent repository when a new repository is added
* via app.addRepository().
*
* @param resource the resource being currently evaluated/compiled
*/
public void setCurrentCodeResource(Resource resource) {
currentCodeResource = resource;
}
/**
* Set the code resource currently being evaluated/compiled. This is used
* to set the proper parent repository when a new repository is added
* via app.addRepository().
* @return the resource being currently evaluated/compiled
*/
public Resource getCurrentCodeResource() {
return currentCodeResource;
}
/** /**
* Return the directory of the Helma server * Return the directory of the Helma server
*/ */

View file

@ -21,13 +21,11 @@ import helma.objectmodel.db.DbSource;
import helma.util.CronJob; import helma.util.CronJob;
import helma.util.SystemMap; import helma.util.SystemMap;
import helma.util.WrappedMap; import helma.util.WrappedMap;
import helma.framework.repository.Repository; import helma.framework.repository.*;
import helma.framework.repository.FileRepository;
import helma.framework.repository.SingleFileRepository;
import helma.framework.repository.ZipRepository;
import java.io.File; import java.io.File;
import java.io.Serializable; import java.io.Serializable;
import java.io.IOException;
import java.util.*; import java.util.*;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@ -137,6 +135,9 @@ public class ApplicationBean implements Serializable {
* @param obj the repository, relative or absolute path to the library. * @param obj the repository, relative or absolute path to the library.
*/ */
public void addRepository(Object obj) { public void addRepository(Object obj) {
Resource current = app.getCurrentCodeResource();
Repository parent = current == null ?
null : current.getRepository().getRootRepository();
Repository rep; Repository rep;
if (obj instanceof String) { if (obj instanceof String) {
String path = (String) obj; String path = (String) obj;
@ -151,12 +152,12 @@ public class ApplicationBean implements Serializable {
throw new RuntimeException("Repository path does not exist: " + obj); throw new RuntimeException("Repository path does not exist: " + obj);
} }
if (file.isDirectory()) { if (file.isDirectory()) {
rep = new FileRepository(file); rep = new FileRepository(file, parent);
} else if (file.isFile()) { } else if (file.isFile()) {
if (file.getName().endsWith(".zip")) { if (file.getName().endsWith(".zip")) {
rep = new ZipRepository(file); rep = new ZipRepository(file, parent);
} else { } else {
rep = new SingleFileRepository(file); rep = new SingleFileRepository(file, parent);
} }
} else { } else {
throw new RuntimeException("Unrecognized file type in addRepository: " + obj); throw new RuntimeException("Unrecognized file type in addRepository: " + obj);
@ -167,6 +168,11 @@ public class ApplicationBean implements Serializable {
throw new RuntimeException("Invalid argument to addRepository: " + obj); throw new RuntimeException("Invalid argument to addRepository: " + obj);
} }
app.addRepository(rep); app.addRepository(rep);
try {
app.typemgr.checkRepository(rep, true);
} catch (IOException iox) {
getLogger().error("Error checking repository " + rep, iox);
}
} }
/** /**

View file

@ -23,6 +23,7 @@ import helma.framework.repository.Resource;
import helma.framework.repository.Repository; import helma.framework.repository.Repository;
import helma.framework.repository.ResourceTracker; import helma.framework.repository.ResourceTracker;
import helma.framework.repository.FileResource; import helma.framework.repository.FileResource;
import helma.scripting.ScriptingEngine;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
@ -112,11 +113,20 @@ public final class Prototype {
/** /**
* Adds an repository to the list of repositories * Adds an repository to the list of repositories
* @param repository repository to add * @param repository repository to add
* @param update indicates whether to immediately update the prototype with the new code
* @throws IOException if reading/updating from the repository fails
*/ */
public void addRepository(Repository repository) { public void addRepository(Repository repository, boolean update) throws IOException {
if (!repositories.contains(repository)) { if (!repositories.contains(repository)) {
repositories.add(repository); repositories.add(repository);
props.addResource(repository.getResource("type.properties")); props.addResource(repository.getResource("type.properties"));
if (update) {
RequestEvaluator eval = app.getCurrentRequestEvaluator();
Iterator it = repository.getAllResources().iterator();
while (it.hasNext()) {
checkResource((Resource) it.next(), eval.scriptingEngine);
}
}
} }
} }
@ -157,21 +167,7 @@ public final class Prototype {
Resource[] resources = getResources(); Resource[] resources = getResources();
for (int i = 0; i < resources.length; i++) { for (int i = 0; i < resources.length; i++) {
String name = resources[i].getName(); updatedResources |= checkResource(resources[i], null);
if (!trackers.containsKey(name)) {
if (name.endsWith(TypeManager.templateExtension) ||
name.endsWith(TypeManager.scriptExtension) ||
name.endsWith(TypeManager.actionExtension) ||
name.endsWith(TypeManager.skinExtension)) {
updatedResources = true;
if (name.endsWith(TypeManager.skinExtension)) {
skins.add(resources[i]);
} else {
code.add(resources[i]);
}
trackers.put(resources[i].getName(), new ResourceTracker(resources[i]));
}
}
} }
if (updatedResources) { if (updatedResources) {
@ -181,6 +177,28 @@ public final class Prototype {
} }
} }
private boolean checkResource(Resource res, ScriptingEngine engine) {
String name = res.getName();
boolean updated = false;
if (!trackers.containsKey(name)) {
if (name.endsWith(TypeManager.templateExtension) ||
name.endsWith(TypeManager.scriptExtension) ||
name.endsWith(TypeManager.actionExtension) ||
name.endsWith(TypeManager.skinExtension)) {
updated = true;
if (name.endsWith(TypeManager.skinExtension)) {
skins.add(res);
} else {
if (engine != null) {
engine.injectCodeResource(lowerCaseName, res);
}
code.add(res);
}
trackers.put(res.getName(), new ResourceTracker(res));
}
}
return updated;
}
/** /**
* Returns the list of resources in this prototype's repositories. Used * Returns the list of resources in this prototype's repositories. Used
@ -358,15 +376,21 @@ public final class Prototype {
* Return an iterator over this prototype's code resoruces. Synchronized * Return an iterator over this prototype's code resoruces. Synchronized
* to not return a collection in a transient state where it is just being * to not return a collection in a transient state where it is just being
* updated by the type manager. * updated by the type manager.
*
* @return an iterator of this prototype's code resources
*/ */
public synchronized Iterator getCodeResources() { public synchronized Iterator getCodeResources() {
return code.iterator(); // we copy over to a new list, because the underlying set may grow
// during compilation through use of app.addRepository()
return new ArrayList(code).iterator();
} }
/** /**
* Return an iterator over this prototype's skin resoruces. Synchronized * Return an iterator over this prototype's skin resoruces. Synchronized
* to not return a collection in a transient state where it is just being * to not return a collection in a transient state where it is just being
* updated by the type manager. * updated by the type manager.
*
* @return an iterator over this prototype's skin resources
*/ */
public Iterator getSkinResources() { public Iterator getSkinResources() {
return skins.iterator(); return skins.iterator();

View file

@ -126,7 +126,7 @@ public final class TypeManager {
lastCheck = System.currentTimeMillis(); lastCheck = System.currentTimeMillis();
} }
private void checkRepository(Repository repository) throws IOException { protected void checkRepository(Repository repository, boolean update) throws IOException {
Repository[] list = repository.getRepositories(); Repository[] list = repository.getRepositories();
for (int i = 0; i < list.length; i++) { for (int i = 0; i < list.length; i++) {
@ -143,7 +143,7 @@ public final class TypeManager {
// this is an embedded top-level script repository // this is an embedded top-level script repository
if (app.addRepository(list[i])) { if (app.addRepository(list[i])) {
// repository is new, check it // repository is new, check it
checkRepository(list[i]); checkRepository(list[i], update);
} }
} else { } else {
// it's an prototype // it's an prototype
@ -156,7 +156,7 @@ public final class TypeManager {
if (isValidTypeName(name)) if (isValidTypeName(name))
createPrototype(name, list[i]); createPrototype(name, list[i]);
} else { } else {
proto.addRepository(list[i]); proto.addRepository(list[i], update);
} }
} }
} }
@ -193,7 +193,7 @@ public final class TypeManager {
((Long) lastRepoScan.get(repository)).longValue() : 0; ((Long) lastRepoScan.get(repository)).longValue() : 0;
if (repository.lastModified() != lastScan) { if (repository.lastModified() != lastScan) {
lastRepoScan.put(repository, new Long(repository.lastModified())); lastRepoScan.put(repository, new Long(repository.lastModified()));
checkRepository(repository); checkRepository(repository, false);
} }
} }

View file

@ -59,9 +59,9 @@ public class FileRepository extends AbstractRepository {
* Constructs a FileRepository using the given directory and top-level * Constructs a FileRepository using the given directory and top-level
* repository * repository
* @param dir directory * @param dir directory
* @param parent top-level repository * @param parent the parent repository, or null
*/ */
protected FileRepository(File dir, FileRepository parent) { public FileRepository(File dir, Repository parent) {
// make sure our directory has an absolute path, // make sure our directory has an absolute path,
// see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4117557 // see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4117557
if (dir.isAbsolute()) { if (dir.isAbsolute()) {

View file

@ -25,6 +25,7 @@ import java.util.LinkedList;
public class SingleFileRepository implements Repository { public class SingleFileRepository implements Repository {
final Resource res; final Resource res;
final Repository parent;
final Repository[] repositories; final Repository[] repositories;
final LinkedList resources = new LinkedList(); final LinkedList resources = new LinkedList();
final LinkedList allResources = new LinkedList(); final LinkedList allResources = new LinkedList();
@ -35,7 +36,7 @@ public class SingleFileRepository implements Repository {
* @param initArgs absolute path to the script file * @param initArgs absolute path to the script file
*/ */
public SingleFileRepository(String initArgs) { public SingleFileRepository(String initArgs) {
this(new File(initArgs)); this(new File(initArgs), null);
} }
/** /**
@ -43,6 +44,16 @@ public class SingleFileRepository implements Repository {
* @param file the script file * @param file the script file
*/ */
public SingleFileRepository(File file) { public SingleFileRepository(File file) {
this(file, null);
}
/**
* Constructs a SingleFileRepository using the given argument
* @param file the script file
* @param parent the parent repository, or null
*/
public SingleFileRepository(File file, Repository parent) {
this.parent = parent;
res = new FileResource(file, this); res = new FileResource(file, this);
allResources.add(res); allResources.add(res);
isScriptFile = file.getName().endsWith(".js"); isScriptFile = file.getName().endsWith(".js");
@ -101,7 +112,7 @@ public class SingleFileRepository implements Repository {
* @return the parent repository * @return the parent repository
*/ */
public Repository getParentRepository() { public Repository getParentRepository() {
return null; return parent;
} }
/** /**

View file

@ -56,8 +56,9 @@ public final class ZipRepository extends AbstractRepository {
* Constructs a ZipRepository using the given zip file as top-level * Constructs a ZipRepository using the given zip file as top-level
* repository * repository
* @param file a zip file * @param file a zip file
* @param parent the parent repository, or null
*/ */
protected ZipRepository(File file, Repository parent) { public ZipRepository(File file, Repository parent) {
this(file, parent, null); this(file, parent, null);
} }

View file

@ -17,6 +17,7 @@
package helma.scripting; package helma.scripting;
import helma.framework.IPathElement; import helma.framework.IPathElement;
import helma.framework.repository.Resource;
import helma.framework.core.Application; import helma.framework.core.Application;
import helma.framework.core.RequestEvaluator; import helma.framework.core.RequestEvaluator;
import java.io.OutputStream; import java.io.OutputStream;
@ -143,4 +144,12 @@ public interface ScriptingEngine {
* @throws IOException * @throws IOException
*/ */
public Object deserialize(InputStream in) throws IOException, ClassNotFoundException; public Object deserialize(InputStream in) throws IOException, ClassNotFoundException;
/**
* Add a code resource to a given prototype by immediately compiling and evaluating it.
*
* @param typename the type this resource belongs to
* @param resource a code resource
*/
public void injectCodeResource(String typename, Resource resource);
} }

View file

@ -84,7 +84,13 @@ public final class RhinoCore implements ScopeProvider {
this.app = app; this.app = app;
wrappercache = new WeakCacheMap(500); wrappercache = new WeakCacheMap(500);
prototypes = new Hashtable(); prototypes = new Hashtable();
}
/**
* Initialize the evaluator, making sure the minimum type information
* necessary to bootstrap the rest is parsed.
*/
protected synchronized void initialize() {
Context context = Context.enter(); Context context = Context.enter();
context.setCompileFunctionsWithDynamicScope(true); context.setCompileFunctionsWithDynamicScope(true);
@ -144,12 +150,22 @@ public final class RhinoCore implements ScopeProvider {
Scriptable numberProto = ScriptableObject.getClassPrototype(global, "Number"); Scriptable numberProto = ScriptableObject.getClassPrototype(global, "Number");
numberProto.put("format", numberProto, new NumberFormat()); numberProto.put("format", numberProto, new NumberFormat());
initialize(); Collection protos = app.getPrototypes();
for (Iterator i = protos.iterator(); i.hasNext();) {
Prototype proto = (Prototype) i.next();
initPrototype(proto);
}
// always fully initialize global prototype, because
// we always need it and there's no chance to trigger
// creation on demand.
getPrototype("global");
} catch (Exception e) { } catch (Exception e) {
System.err.println("Cannot initialize interpreter"); System.err.println("Cannot initialize interpreter");
System.err.println("Error: " + e); System.err.println("Error: " + e);
e.printStackTrace(); e.printStackTrace();
throw new RuntimeException(e.getMessage()); throw new RuntimeException(e.getMessage(), e);
} finally { } finally {
Context.exit(); Context.exit();
} }
@ -172,25 +188,6 @@ public final class RhinoCore implements ScopeProvider {
} }
} }
/**
* Initialize the evaluator, making sure the minimum type information
* necessary to bootstrap the rest is parsed.
*/
private synchronized void initialize() {
Collection protos = app.getPrototypes();
for (Iterator i = protos.iterator(); i.hasNext();) {
Prototype proto = (Prototype) i.next();
initPrototype(proto);
}
// always fully initialize global prototype, because
// we always need it and there's no chance to trigger
// creation on demand.
getPrototype("global");
}
/** /**
* Initialize a prototype info without compiling its script files. * Initialize a prototype info without compiling its script files.
* *
@ -763,6 +760,19 @@ public final class RhinoCore implements ScopeProvider {
return param; return param;
} }
/**
* Add a code resource to a given prototype by immediately compiling and evaluating it.
*
* @param typename the type this resource belongs to
* @param code a code resource
*/
public void injectCodeResource(String typename, Resource code) {
TypeInfo type = (TypeInfo) prototypes.get(typename.toLowerCase());
if (type == null || type.lastUpdate == -1)
return;
evaluate(type, code);
}
//////////////////////////////////////////////// ////////////////////////////////////////////////
// private evaluation/compilation methods // private evaluation/compilation methods
//////////////////////////////////////////////// ////////////////////////////////////////////////
@ -776,6 +786,9 @@ public final class RhinoCore implements ScopeProvider {
String sourceName = code.getName(); String sourceName = code.getName();
Reader reader = null; Reader reader = null;
Resource previousCurrentResource = app.getCurrentCodeResource();
app.setCurrentCodeResource(code);
try { try {
Scriptable op = type.objProto; Scriptable op = type.objProto;
@ -809,6 +822,7 @@ public final class RhinoCore implements ScopeProvider {
} }
// e.printStackTrace(); // e.printStackTrace();
} finally { } finally {
app.setCurrentCodeResource(previousCurrentResource);
if (reader != null) { if (reader != null) {
try { try {
reader.close(); reader.close();
@ -877,6 +891,8 @@ public final class RhinoCore implements ScopeProvider {
if (objProto instanceof PropertyRecorder) { if (objProto instanceof PropertyRecorder) {
((PropertyRecorder) objProto).startRecording(); ((PropertyRecorder) objProto).startRecording();
} }
// mark this type as updated so injectCodeResource() knows it's initialized
lastUpdate = frameworkProto.lastCodeUpdate();
} }
/** /**
@ -929,8 +945,9 @@ public final class RhinoCore implements ScopeProvider {
compiledProperties = changedProperties; compiledProperties = changedProperties;
} }
// mark this type as updated // mark this type as updated again so it reflects
lastUpdate = frameworkProto.lastCodeUpdate(); // resources added during compilation
// lastUpdate = frameworkProto.lastCodeUpdate();
// If this prototype defines a postCompile() function, call it // If this prototype defines a postCompile() function, call it
Context cx = Context.getCurrentContext(); Context cx = Context.getCurrentContext();

View file

@ -20,6 +20,7 @@ import helma.doc.DocApplication;
import helma.extensions.ConfigurationException; import helma.extensions.ConfigurationException;
import helma.extensions.HelmaExtension; import helma.extensions.HelmaExtension;
import helma.framework.*; import helma.framework.*;
import helma.framework.repository.Resource;
import helma.framework.core.*; import helma.framework.core.*;
import helma.main.Server; import helma.main.Server;
import helma.objectmodel.*; import helma.objectmodel.*;
@ -40,7 +41,7 @@ import java.lang.ref.WeakReference;
*/ */
public class RhinoEngine implements ScriptingEngine { public class RhinoEngine implements ScriptingEngine {
// map for Application to RhinoCore binding // map for Application to RhinoCore binding
static Map coreMap; static final Map coreMap = new WeakHashMap();
// the application we're running in // the application we're running in
public Application app; public Application app;
@ -81,7 +82,7 @@ public class RhinoEngine implements ScriptingEngine {
public synchronized void init(Application app, RequestEvaluator reval) { public synchronized void init(Application app, RequestEvaluator reval) {
this.app = app; this.app = app;
this.reval = reval; this.reval = reval;
core = getRhinoCore(app); initRhinoCore(app);
context = Context.enter(); context = Context.enter();
context.setCompileFunctionsWithDynamicScope(true); context.setCompileFunctionsWithDynamicScope(true);
context.setApplicationClassLoader(app.getClassLoader()); context.setApplicationClassLoader(app.getClassLoader());
@ -120,24 +121,23 @@ public class RhinoEngine implements ScriptingEngine {
} }
} }
static synchronized RhinoCore getRhinoCore(Application app) { /**
RhinoCore core = null; * Initialize the RhinoCore instance for this engine and application.
* @param app the application we belong to
if (coreMap == null) { */
coreMap = new WeakHashMap(); private synchronized void initRhinoCore(Application app) {
} else { synchronized (coreMap) {
WeakReference ref = (WeakReference) coreMap.get(app); WeakReference ref = (WeakReference) coreMap.get(app);
if (ref != null) { if (ref != null) {
core = (RhinoCore) ref.get(); core = (RhinoCore) ref.get();
} }
}
if (core == null) { if (core == null) {
core = new RhinoCore(app); core = new RhinoCore(app);
coreMap.put(app, new WeakReference(core)); core.initialize();
coreMap.put(app, new WeakReference(core));
}
} }
return core;
} }
/** /**
@ -537,6 +537,16 @@ public class RhinoEngine implements ScriptingEngine {
} }
} }
/**
* Add a code resource to a given prototype by immediately compiling and evaluating it.
*
* @param typename the type this resource belongs to
* @param resource a code resource
*/
public void injectCodeResource(String typename, Resource resource) {
core.injectCodeResource(typename, resource);
}
/** /**
* Return the application we're running in * Return the application we're running in
*/ */