* 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.HelmaExtension;
import helma.framework.*;
import helma.framework.repository.ResourceComparator;
import helma.framework.repository.Repository;
import helma.framework.repository.FileResource;
import helma.framework.repository.FileRepository;
import helma.framework.repository.*;
import helma.main.Server;
import helma.objectmodel.*;
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
* requests from the Web server or XML-RPC port and dispatches them to
* the evaluators.
@ -167,6 +164,8 @@ public final class Application implements IPathElement, Runnable {
private ResourceComparator resourceComparator;
private Resource currentCodeResource;
/**
* Simple constructor for dead application instances.
*/
@ -1768,6 +1767,28 @@ public final class Application implements IPathElement, Runnable {
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
*/

View file

@ -21,13 +21,11 @@ import helma.objectmodel.db.DbSource;
import helma.util.CronJob;
import helma.util.SystemMap;
import helma.util.WrappedMap;
import helma.framework.repository.Repository;
import helma.framework.repository.FileRepository;
import helma.framework.repository.SingleFileRepository;
import helma.framework.repository.ZipRepository;
import helma.framework.repository.*;
import java.io.File;
import java.io.Serializable;
import java.io.IOException;
import java.util.*;
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.
*/
public void addRepository(Object obj) {
Resource current = app.getCurrentCodeResource();
Repository parent = current == null ?
null : current.getRepository().getRootRepository();
Repository rep;
if (obj instanceof String) {
String path = (String) obj;
@ -151,12 +152,12 @@ public class ApplicationBean implements Serializable {
throw new RuntimeException("Repository path does not exist: " + obj);
}
if (file.isDirectory()) {
rep = new FileRepository(file);
rep = new FileRepository(file, parent);
} else if (file.isFile()) {
if (file.getName().endsWith(".zip")) {
rep = new ZipRepository(file);
rep = new ZipRepository(file, parent);
} else {
rep = new SingleFileRepository(file);
rep = new SingleFileRepository(file, parent);
}
} else {
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);
}
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.ResourceTracker;
import helma.framework.repository.FileResource;
import helma.scripting.ScriptingEngine;
import java.io.*;
import java.util.*;
@ -112,11 +113,20 @@ public final class Prototype {
/**
* Adds an repository to the list of repositories
* @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)) {
repositories.add(repository);
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();
for (int i = 0; i < resources.length; i++) {
String name = resources[i].getName();
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]));
}
}
updatedResources |= checkResource(resources[i], null);
}
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
@ -358,15 +376,21 @@ public final class Prototype {
* Return an iterator over this prototype's code resoruces. Synchronized
* to not return a collection in a transient state where it is just being
* updated by the type manager.
*
* @return an iterator of this prototype's code resources
*/
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
* to not return a collection in a transient state where it is just being
* updated by the type manager.
*
* @return an iterator over this prototype's skin resources
*/
public Iterator getSkinResources() {
return skins.iterator();

View file

@ -126,7 +126,7 @@ public final class TypeManager {
lastCheck = System.currentTimeMillis();
}
private void checkRepository(Repository repository) throws IOException {
protected void checkRepository(Repository repository, boolean update) throws IOException {
Repository[] list = repository.getRepositories();
for (int i = 0; i < list.length; i++) {
@ -143,7 +143,7 @@ public final class TypeManager {
// this is an embedded top-level script repository
if (app.addRepository(list[i])) {
// repository is new, check it
checkRepository(list[i]);
checkRepository(list[i], update);
}
} else {
// it's an prototype
@ -156,7 +156,7 @@ public final class TypeManager {
if (isValidTypeName(name))
createPrototype(name, list[i]);
} else {
proto.addRepository(list[i]);
proto.addRepository(list[i], update);
}
}
}
@ -193,7 +193,7 @@ public final class TypeManager {
((Long) lastRepoScan.get(repository)).longValue() : 0;
if (repository.lastModified() != lastScan) {
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
* repository
* @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,
// see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4117557
if (dir.isAbsolute()) {

View file

@ -25,6 +25,7 @@ import java.util.LinkedList;
public class SingleFileRepository implements Repository {
final Resource res;
final Repository parent;
final Repository[] repositories;
final LinkedList resources = new LinkedList();
final LinkedList allResources = new LinkedList();
@ -35,7 +36,7 @@ public class SingleFileRepository implements Repository {
* @param initArgs absolute path to the script file
*/
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
*/
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);
allResources.add(res);
isScriptFile = file.getName().endsWith(".js");
@ -101,7 +112,7 @@ public class SingleFileRepository implements Repository {
* @return the parent repository
*/
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
* repository
* @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);
}

View file

@ -17,6 +17,7 @@
package helma.scripting;
import helma.framework.IPathElement;
import helma.framework.repository.Resource;
import helma.framework.core.Application;
import helma.framework.core.RequestEvaluator;
import java.io.OutputStream;
@ -143,4 +144,12 @@ public interface ScriptingEngine {
* @throws IOException
*/
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;
wrappercache = new WeakCacheMap(500);
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.setCompileFunctionsWithDynamicScope(true);
@ -144,12 +150,22 @@ public final class RhinoCore implements ScopeProvider {
Scriptable numberProto = ScriptableObject.getClassPrototype(global, "Number");
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) {
System.err.println("Cannot initialize interpreter");
System.err.println("Error: " + e);
e.printStackTrace();
throw new RuntimeException(e.getMessage());
throw new RuntimeException(e.getMessage(), e);
} finally {
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.
*
@ -763,6 +760,19 @@ public final class RhinoCore implements ScopeProvider {
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
////////////////////////////////////////////////
@ -776,6 +786,9 @@ public final class RhinoCore implements ScopeProvider {
String sourceName = code.getName();
Reader reader = null;
Resource previousCurrentResource = app.getCurrentCodeResource();
app.setCurrentCodeResource(code);
try {
Scriptable op = type.objProto;
@ -809,6 +822,7 @@ public final class RhinoCore implements ScopeProvider {
}
// e.printStackTrace();
} finally {
app.setCurrentCodeResource(previousCurrentResource);
if (reader != null) {
try {
reader.close();
@ -877,6 +891,8 @@ public final class RhinoCore implements ScopeProvider {
if (objProto instanceof PropertyRecorder) {
((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;
}
// mark this type as updated
lastUpdate = frameworkProto.lastCodeUpdate();
// mark this type as updated again so it reflects
// resources added during compilation
// lastUpdate = frameworkProto.lastCodeUpdate();
// If this prototype defines a postCompile() function, call it
Context cx = Context.getCurrentContext();

View file

@ -20,6 +20,7 @@ import helma.doc.DocApplication;
import helma.extensions.ConfigurationException;
import helma.extensions.HelmaExtension;
import helma.framework.*;
import helma.framework.repository.Resource;
import helma.framework.core.*;
import helma.main.Server;
import helma.objectmodel.*;
@ -40,7 +41,7 @@ import java.lang.ref.WeakReference;
*/
public class RhinoEngine implements ScriptingEngine {
// map for Application to RhinoCore binding
static Map coreMap;
static final Map coreMap = new WeakHashMap();
// the application we're running in
public Application app;
@ -81,7 +82,7 @@ public class RhinoEngine implements ScriptingEngine {
public synchronized void init(Application app, RequestEvaluator reval) {
this.app = app;
this.reval = reval;
core = getRhinoCore(app);
initRhinoCore(app);
context = Context.enter();
context.setCompileFunctionsWithDynamicScope(true);
context.setApplicationClassLoader(app.getClassLoader());
@ -120,24 +121,23 @@ public class RhinoEngine implements ScriptingEngine {
}
}
static synchronized RhinoCore getRhinoCore(Application app) {
RhinoCore core = null;
if (coreMap == null) {
coreMap = new WeakHashMap();
} else {
/**
* Initialize the RhinoCore instance for this engine and application.
* @param app the application we belong to
*/
private synchronized void initRhinoCore(Application app) {
synchronized (coreMap) {
WeakReference ref = (WeakReference) coreMap.get(app);
if (ref != null) {
core = (RhinoCore) ref.get();
}
}
if (core == null) {
core = new RhinoCore(app);
coreMap.put(app, new WeakReference(core));
if (core == null) {
core = new RhinoCore(app);
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
*/