diff --git a/src/helma/framework/core/Application.java b/src/helma/framework/core/Application.java index fa2bfb0b..915cf859 100644 --- a/src/helma/framework/core/Application.java +++ b/src/helma/framework/core/Application.java @@ -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 */ diff --git a/src/helma/framework/core/ApplicationBean.java b/src/helma/framework/core/ApplicationBean.java index e4e545c2..363c58bd 100644 --- a/src/helma/framework/core/ApplicationBean.java +++ b/src/helma/framework/core/ApplicationBean.java @@ -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); + } } /** diff --git a/src/helma/framework/core/Prototype.java b/src/helma/framework/core/Prototype.java index 80dab7ff..517ef2a7 100644 --- a/src/helma/framework/core/Prototype.java +++ b/src/helma/framework/core/Prototype.java @@ -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(); diff --git a/src/helma/framework/core/TypeManager.java b/src/helma/framework/core/TypeManager.java index 7ee68696..ad7809f3 100644 --- a/src/helma/framework/core/TypeManager.java +++ b/src/helma/framework/core/TypeManager.java @@ -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); } } diff --git a/src/helma/framework/repository/FileRepository.java b/src/helma/framework/repository/FileRepository.java index f76719fb..cf05f9de 100644 --- a/src/helma/framework/repository/FileRepository.java +++ b/src/helma/framework/repository/FileRepository.java @@ -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()) { diff --git a/src/helma/framework/repository/SingleFileRepository.java b/src/helma/framework/repository/SingleFileRepository.java index 6c79fba9..14b86669 100644 --- a/src/helma/framework/repository/SingleFileRepository.java +++ b/src/helma/framework/repository/SingleFileRepository.java @@ -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; } /** diff --git a/src/helma/framework/repository/ZipRepository.java b/src/helma/framework/repository/ZipRepository.java index d4cb465a..ec40bbb3 100644 --- a/src/helma/framework/repository/ZipRepository.java +++ b/src/helma/framework/repository/ZipRepository.java @@ -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); } diff --git a/src/helma/scripting/ScriptingEngine.java b/src/helma/scripting/ScriptingEngine.java index 39c2a644..e8aa4f4e 100644 --- a/src/helma/scripting/ScriptingEngine.java +++ b/src/helma/scripting/ScriptingEngine.java @@ -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); } diff --git a/src/helma/scripting/rhino/RhinoCore.java b/src/helma/scripting/rhino/RhinoCore.java index 1cf14561..ce7e1944 100644 --- a/src/helma/scripting/rhino/RhinoCore.java +++ b/src/helma/scripting/rhino/RhinoCore.java @@ -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(); diff --git a/src/helma/scripting/rhino/RhinoEngine.java b/src/helma/scripting/rhino/RhinoEngine.java index cc1fcf3e..9b04e5d1 100644 --- a/src/helma/scripting/rhino/RhinoEngine.java +++ b/src/helma/scripting/rhino/RhinoEngine.java @@ -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 */