diff --git a/src/helma/doc/DocApplication.java b/src/helma/doc/DocApplication.java index c2520ce5..72618ddf 100644 --- a/src/helma/doc/DocApplication.java +++ b/src/helma/doc/DocApplication.java @@ -17,12 +17,15 @@ package helma.doc; import helma.framework.IPathElement; +import helma.framework.repository.FileResource; +import helma.framework.repository.FileResource; import helma.main.Server; -import helma.util.SystemProperties; +import helma.util.ResourceProperties; import java.io.*; import java.util.*; + /** - * + * */ public class DocApplication extends DocDirElement { HashSet excluded; @@ -72,7 +75,7 @@ public class DocApplication extends DocDirElement { // DocFunction[] func = DocFunction.newFunctions(new File(args[0])); // for (int i=0; i 0) { + this.repositories = new ArrayList(); + this.repositories.addAll(Arrays.asList(repositories)); + resourceComparator = new ResourceComparator(this); + } else { + throw new java.lang.IllegalArgumentException("No sources defined for application: " + name); + } dbDir = customDbDir; // system-wide properties, default to null - SystemProperties sysProps; + ResourceProperties sysProps; // system-wide properties, default to null - SystemProperties sysDbProps; + ResourceProperties sysDbProps; sysProps = sysDbProps = null; home = null; @@ -202,12 +213,6 @@ public final class Application implements IPathElement, Runnable { if (server != null) { home = server.getHopHome(); - // if appDir and dbDir weren't explicitely passed, use the - // standard subdirectories of the Hop home directory - if (appDir == null) { - appDir = new File(server.getAppsHome(), name); - } - if (dbDir == null) { dbDir = new File(server.getDbHome(), name); } @@ -217,11 +222,6 @@ public final class Application implements IPathElement, Runnable { sysDbProps = server.getDbProperties(); } - // create the directories if they do not exist already - if (!appDir.exists()) { - appDir.mkdirs(); - } - if (!dbDir.exists()) { dbDir.mkdirs(); } @@ -230,32 +230,26 @@ public final class Application implements IPathElement, Runnable { threadgroup = new ThreadGroup("TX-" + name); // create app-level properties - File propfile = new File(appDir, "app.properties"); - - props = new SystemProperties(propfile.getAbsolutePath(), sysProps); + props = new ResourceProperties(this, "app.properties", sysProps); // get log names accessLogName = props.getProperty("accessLog", "helma."+name+".access"); eventLogName = props.getProperty("eventLog", "helma."+name+".event"); // create app-level db sources - File dbpropfile = new File(appDir, "db.properties"); - - dbProps = new SystemProperties(dbpropfile.getAbsolutePath(), sysDbProps); + dbProps = new ResourceProperties(this, "db.properties", sysDbProps); // the passwd file, to be used with the authenticate() function - CryptFile parentpwfile = null; + CryptResource parentpwfile = null; if (home != null) { - parentpwfile = new CryptFile(new File(home, "passwd"), null); + parentpwfile = new CryptResource(new FileResource(new File(home, "passwd")), null); } - pwfile = new CryptFile(new File(appDir, "passwd"), parentpwfile); + pwfile = new CryptResource(repositories[0].getResource("passwd"), parentpwfile); // the properties that map java class names to prototype names - File classMappingFile = new File(appDir, "class.properties"); - - classMapping = new SystemProperties(classMappingFile.getAbsolutePath()); + classMapping = new ResourceProperties(this, "class.properties"); classMapping.setIgnoreCase(false); // get class name of root object if defined. Otherwise native Helma objectmodel will be used. @@ -274,9 +268,8 @@ public final class Application implements IPathElement, Runnable { * Get the application ready to run, initializing the evaluators and type manager. */ public synchronized void init() - throws DatabaseException, MalformedURLException, - IllegalAccessException, InstantiationException, - ClassNotFoundException { + throws DatabaseException, IllegalAccessException, + InstantiationException, ClassNotFoundException { // create and init type mananger typemgr = new TypeManager(this); @@ -346,8 +339,8 @@ public final class Application implements IPathElement, Runnable { // The whole user/userroot handling is basically old // ugly obsolete crap. Don't bother. - SystemProperties p = new SystemProperties(); - String usernameField = userMapping.getNameField(); + ResourceProperties p = new ResourceProperties(); + String usernameField = (userMapping != null) ? userMapping.getNameField() : null; if (usernameField == null) { usernameField = "name"; @@ -438,6 +431,18 @@ public final class Application implements IPathElement, Runnable { return running; } + public File getAppDir() { + try { + return new File(((FileRepository) getRepositories().next()).getName()); + } catch (ClassCastException ex) { + return null; + } + } + + public ResourceComparator getResourceComparator() { + return resourceComparator; + } + /** * Returns a free evaluator to handle a request. */ @@ -554,6 +559,8 @@ public final class Application implements IPathElement, Runnable { * Execute a request coming in from a web client. */ public ResponseTrans execute(RequestTrans req) { + System.setProperty("request.start", String.valueOf(System.currentTimeMillis())); + requestCount += 1; // get user for this request's session @@ -594,7 +601,7 @@ public final class Application implements IPathElement, Runnable { throw stopped; } catch (Exception x) { errorCount += 1; - res = new ResponseTrans(req); + res = new ResponseTrans(this, req); res.writeErrorReport(name, x.getMessage()); } finally { if (primaryRequest) { @@ -808,7 +815,7 @@ public final class Application implements IPathElement, Runnable { * Return a skin for a given object. The skin is found by determining the prototype * to use for the object, then looking up the skin for the prototype. */ - public Skin getSkin(String protoname, String skinname, Object[] skinpath) { + public Skin getSkin(String protoname, String skinname, Object[] skinpath) throws IOException { Prototype proto = getPrototypeByName(protoname); if (proto == null) { @@ -1616,10 +1623,23 @@ public final class Application implements IPathElement, Runnable { } /** - * Return the directory of this application + * Searches for the index of the given repository for this app. + * The arguement must be a root argument, or -1 will be returned. + * + * @param rep one of this app's root repositories. + * @return the index of the first occurrence of the argument in this + * list; returns -1 if the object is not found. */ - public File getAppDir() { - return appDir; + public int getRepositoryIndex(Repository rep) { + return repositories.indexOf(rep); + } + + /** + * Returns the repositories of this application + * @return iterator through application repositories + */ + public Iterator getRepositories() { + return repositories.iterator(); } /** @@ -1721,7 +1741,7 @@ public final class Application implements IPathElement, Runnable { * change, too. */ public long getChecksum() { - return starttime + typemgr.getChecksum() + props.getChecksum(); + return starttime + typemgr.lastCodeUpdate() + props.getChecksum(); } /** @@ -1743,7 +1763,7 @@ public final class Application implements IPathElement, Runnable { * * @return ... */ - public SystemProperties getProperties() { + public ResourceProperties getProperties() { return props; } diff --git a/src/helma/framework/core/ApplicationBean.java b/src/helma/framework/core/ApplicationBean.java index 1ebc1471..615247ca 100644 --- a/src/helma/framework/core/ApplicationBean.java +++ b/src/helma/framework/core/ApplicationBean.java @@ -29,7 +29,7 @@ import java.util.List; import java.util.Map; /** - * + * */ public class ApplicationBean implements Serializable { Application app; @@ -490,7 +490,7 @@ public class ApplicationBean implements Serializable { File f = app.getServerDir(); if (f == null) { - f = app.getAppDir(); + return app.getAppDir().getAbsolutePath(); } return f.getAbsolutePath(); diff --git a/src/helma/framework/core/Prototype.java b/src/helma/framework/core/Prototype.java index ca180be2..2de0e603 100644 --- a/src/helma/framework/core/Prototype.java +++ b/src/helma/framework/core/Prototype.java @@ -17,9 +17,12 @@ package helma.framework.core; import helma.objectmodel.db.DbMapping; -import helma.scripting.*; import helma.util.SystemMap; -import helma.util.SystemProperties; +import helma.util.ResourceProperties; +import helma.framework.repository.Resource; +import helma.framework.repository.Repository; +import helma.framework.repository.ResourceTracker; + import java.io.*; import java.util.*; @@ -30,81 +33,77 @@ import java.util.*; * relational database table. */ public final class Prototype { + Application app; + String name; String lowerCaseName; - Application app; - File directory; - File[] files; - long lastDirectoryListing; - long checksum; - HashMap code; - HashMap zippedCode; - HashMap skins; - HashMap zippedSkins; - HashMap updatables; + + Resource[] resources; + long lastResourceListing; + + // lastCheck is the time the prototype's files were last checked + long lastChecksum; + long newChecksum; + // lastUpdate is the time at which any of the prototype's files were found updated the last time + long lastCodeUpdate; + + TreeSet code; + TreeSet skins; + + HashMap trackers; + + HashSet repositories; // a map of this prototype's skins as raw strings // used for exposing skins to application (script) code (via app.skinfiles). - SkinMap skinMap; + HashMap skinMap; + DbMapping dbmap; - // lastCheck is the time the prototype's files were last checked - private long lastChecksum; - - // lastUpdate is the time at which any of the prototype's files were - // found updated the last time - private long lastUpdate; private Prototype parent; // Tells us whether this prototype is used to script a generic Java object, // as opposed to a Helma objectmodel node object. boolean isJavaPrototype; + ResourceProperties props; + /** * Creates a new Prototype object. * * @param name the prototype's name - * @param dir the prototype directory, if known + * @param repository the first prototype's repository * @param app the application this prototype is a part of */ - public Prototype(String name, File dir, Application app) { + public Prototype(String name, Repository repository, Application app) { // app.logEvent ("Constructing Prototype "+app.getName()+"/"+name); this.app = app; this.name = name; + repositories = new HashSet(); + if (repository != null) { + repositories.add(repository); + } lowerCaseName = name.toLowerCase(); - if (dir != null) { - directory = dir; - } else { - directory = new File(app.appDir, name); - // a little bit of overkill to maintain backwards compatibility - // with lower case type names... - if (!directory.isDirectory()) { - File lowerDir = new File(app.appDir, lowerCaseName); - if (lowerDir.isDirectory()) { - directory = lowerDir; - } - } - } - // Create and register type properties file - File propfile = new File(directory, "type.properties"); - SystemProperties props = new SystemProperties(propfile.getAbsolutePath()); + props = new ResourceProperties(); + if (repository != null) { + props.addResource(repository.getResource("type.properties")); + } dbmap = new DbMapping(app, name, props); // we don't need to put the DbMapping into proto.updatables, because // dbmappings are checked separately in TypeManager.checkFiles() for // each request - code = new HashMap(); - zippedCode = new HashMap(); - skins = new HashMap(); - zippedSkins = new HashMap(); - updatables = new HashMap(); + code = new TreeSet(app.getResourceComparator()); + skins = new TreeSet(app.getResourceComparator()); - skinMap = new SkinMap(); + trackers = new HashMap(); + + skinMap = new HashMap(); isJavaPrototype = app.isJavaPrototype(name); - lastUpdate = lastChecksum = 0; + lastCodeUpdate = lastChecksum = 0; } /** @@ -115,42 +114,58 @@ public final class Prototype { } /** - * Return this prototype's directory. + * Adds an repository to the list of repositories + * @param repository repository to add */ - public File getDirectory() { - return directory; + public void addRepository(Repository repository) { + if (!repositories.contains(repository)) { + repositories.add(repository); + props.addResource(repository.getResource("type.properties")); + } + return; } /** - * Get the list of files in this prototype's directory + * Returns the list of resources in this prototype's repositories */ - public File[] getFiles() { - if ((files == null) || (directory.lastModified() != lastDirectoryListing)) { - lastDirectoryListing = directory.lastModified(); - files = directory.listFiles(); + public Resource[] getResources() { + long resourceListing = getChecksum(); - if (files == null) { - files = new File[0]; + if (resources == null || resourceListing != lastResourceListing) { + lastResourceListing = resourceListing; + ArrayList list = new ArrayList(); + Iterator iterator = repositories.iterator(); + + while (iterator.hasNext()) { + try { + list.addAll(((Repository) iterator.next()).getAllResources()); + } catch (IOException iox) { + iox.printStackTrace(); + } + } + + resources = (Resource[]) list.toArray(new Resource[list.size()]); + } + + return resources; + } + + /** + * Get a checksum over this prototype's sources + */ + public long getChecksum() { + long checksum = 0; + Iterator iterator = repositories.iterator(); + + while (iterator.hasNext()) { + try { + checksum += ((Repository) iterator.next()).getChecksum(); + } catch (IOException iox) { + iox.printStackTrace(); } } - return files; - } - - /** - * Get a checksum over the files in this prototype's directory - */ - public long getChecksum() { - // long start = System.currentTimeMillis(); - File[] f = getFiles(); - long c = directory.lastModified(); - - for (int i = 0; i < f.length; i++) - c += f[i].lastModified(); - - checksum = c; - - // System.err.println ("CHECKSUM "+name+": "+(System.currentTimeMillis()-start)); + newChecksum = checksum; return checksum; } @@ -160,7 +175,8 @@ public final class Prototype { * @return ... */ public boolean isUpToDate() { - return checksum == lastChecksum; + return (newChecksum == 0 && lastChecksum == 0) ? + false : newChecksum == lastChecksum; } /** @@ -169,7 +185,7 @@ public final class Prototype { */ public void setParentPrototype(Prototype parent) { // this is not allowed for the hopobject and global prototypes - if ("HopObject".equals(name) || "global".equals(name)) { + if ("hopobject".equals(lowerCaseName) || "global".equals(lowerCaseName)) { return; } @@ -188,11 +204,11 @@ public final class Prototype { * Check if the given prototype is within this prototype's parent chain. */ public final boolean isInstanceOf(String pname) { - if (name.equals(pname) || lowerCaseName.equals(pname)) { + if (name.equalsIgnoreCase(pname)) { return true; } - if ((parent != null) && !"HopObject".equals(parent.getName())) { + if (parent != null) { return parent.isInstanceOf(pname); } @@ -248,14 +264,8 @@ public final class Prototype { * residing in the prototype directory, not for skin files in * other locations or database stored skins. */ - public SkinFile getSkinFile(String sfname) { - SkinFile sf = (SkinFile) skins.get(sfname); - - if (sf == null) { - sf = (SkinFile) zippedSkins.get(sfname); - } - - return sf; + public Resource getSkinResource(String sname) { + return (Resource) skinMap.get(sname); } /** @@ -263,11 +273,11 @@ public final class Prototype { * residing in the prototype directory, not for skins files in * other locations or database stored skins. */ - public Skin getSkin(String sfname) { - SkinFile sf = getSkinFile(sfname); + public Skin getSkin(String sname) throws IOException { + Resource res = getSkinResource(sname); - if (sf != null) { - return sf.getSkin(); + if (res != null) { + return Skin.getSkin(res, app); } else { return null; } @@ -294,8 +304,8 @@ public final class Prototype { /** * Get the last time any script has been re-read for this prototype. */ - public long getLastUpdate() { - return lastUpdate; + public long lastCodeUpdate() { + return lastCodeUpdate; } /** @@ -304,7 +314,7 @@ public final class Prototype { * the evaluators. */ public void markUpdated() { - lastUpdate = System.currentTimeMillis(); + lastCodeUpdate = System.currentTimeMillis(); } /** @@ -313,7 +323,7 @@ public final class Prototype { */ public void markChecked() { // lastCheck = System.currentTimeMillis (); - lastChecksum = checksum; + lastChecksum = newChecksum; } /** @@ -321,145 +331,30 @@ public final class Prototype { * to not return a map in a transient state where it is just being * updated by the type manager. */ - public synchronized Map getCode() { - return (Map) code.clone(); + public synchronized Iterator getCodeResources() { + return code.iterator(); } /** - * Return a clone of this prototype's functions container. Synchronized - * to not return a map in a transient state where it is just being - * updated by the type manager. + * Add a code resource to this prototype + * + * @param res a code resource */ - public synchronized Map getZippedCode() { - return (Map) zippedCode.clone(); + public synchronized void addCodeResource(Resource res) { + code.add(res); + trackers.put(res.getName(), new ResourceTracker(res)); } - /** - * - * - * @param action ... - */ - public synchronized void addActionFile(ActionFile action) { - File f = action.getFile(); - - if (f != null) { - code.put(action.getSourceName(), action); - updatables.put(f.getName(), action); - } else { - zippedCode.put(action.getSourceName(), action); - } - } /** + * Add a skin resource to this prototype * - * - * @param template ... + * @param res a skin resource */ - public synchronized void addTemplate(Template template) { - File f = template.getFile(); - - if (f != null) { - code.put(template.getSourceName(), template); - updatables.put(f.getName(), template); - } else { - zippedCode.put(template.getSourceName(), template); - } - } - - /** - * - * - * @param funcfile ... - */ - public synchronized void addFunctionFile(FunctionFile funcfile) { - File f = funcfile.getFile(); - - if (f != null) { - code.put(funcfile.getSourceName(), funcfile); - updatables.put(f.getName(), funcfile); - } else { - zippedCode.put(funcfile.getSourceName(), funcfile); - } - } - - /** - * - * - * @param skinfile ... - */ - public synchronized void addSkinFile(SkinFile skinfile) { - File f = skinfile.getFile(); - - if (f != null) { - skins.put(skinfile.getName(), skinfile); - updatables.put(f.getName(), skinfile); - } else { - zippedSkins.put(skinfile.getName(), skinfile); - } - } - - /** - * - * - * @param action ... - */ - public synchronized void removeActionFile(ActionFile action) { - File f = action.getFile(); - - if (f != null) { - code.remove(action.getSourceName()); - updatables.remove(f.getName()); - } else { - zippedCode.remove(action.getSourceName()); - } - } - - /** - * - * - * @param funcfile ... - */ - public synchronized void removeFunctionFile(FunctionFile funcfile) { - File f = funcfile.getFile(); - - if (f != null) { - code.remove(funcfile.getSourceName()); - updatables.remove(f.getName()); - } else { - zippedCode.remove(funcfile.getSourceName()); - } - } - - /** - * - * - * @param template ... - */ - public synchronized void removeTemplate(Template template) { - File f = template.getFile(); - - if (f != null) { - code.remove(template.getSourceName()); - updatables.remove(f.getName()); - } else { - zippedCode.remove(template.getSourceName()); - } - } - - /** - * - * - * @param skinfile ... - */ - public synchronized void removeSkinFile(SkinFile skinfile) { - File f = skinfile.getFile(); - - if (f != null) { - skins.remove(skinfile.getName()); - updatables.remove(f.getName()); - } else { - zippedSkins.remove(skinfile.getName()); - } + public synchronized void addSkinResource(Resource res) { + skins.add(res); + skinMap.put(res.getShortName(), res); + trackers.put(res.getName(), new ResourceTracker(res)); } /** @@ -475,7 +370,7 @@ public final class Prototype { * @return ... */ public SkinMap getSkinMap() { - return skinMap; + return new SkinMap(); } // not yet implemented @@ -529,13 +424,17 @@ public final class Prototype { checkForUpdates(); - SkinFile sf = (SkinFile) super.get(key); + Resource res = (Resource) super.get(key); - if (sf == null) { + if (res == null) { return null; } - return sf.getSkin().getSource(); + try { + return res.getContent(); + } catch (IOException iox) { + return null; + } } public int hashCode() { @@ -589,26 +488,20 @@ public final class Prototype { app.typemgr.updatePrototype(Prototype.this); } - if (lastUpdate > lastSkinmapLoad) { + if (lastCodeUpdate > lastSkinmapLoad) { load(); } } private synchronized void load() { - if (lastUpdate == lastSkinmapLoad) { + if (lastCodeUpdate == lastSkinmapLoad) { return; } super.clear(); - // load Skins from zip files first, then from directories - for (Iterator i = zippedSkins.entrySet().iterator(); i.hasNext();) { - Map.Entry e = (Map.Entry) i.next(); - - super.put(e.getKey(), e.getValue()); - } - - for (Iterator i = skins.entrySet().iterator(); i.hasNext();) { + // load Skins + for (Iterator i = skins.iterator(); i.hasNext();) { Map.Entry e = (Map.Entry) i.next(); super.put(e.getKey(), e.getValue()); @@ -628,7 +521,7 @@ public final class Prototype { } } - lastSkinmapLoad = lastUpdate; + lastSkinmapLoad = lastCodeUpdate; } public String toString() { diff --git a/src/helma/framework/core/RequestEvaluator.java b/src/helma/framework/core/RequestEvaluator.java index f926052e..9affa87c 100644 --- a/src/helma/framework/core/RequestEvaluator.java +++ b/src/helma/framework/core/RequestEvaluator.java @@ -305,10 +305,6 @@ public final class RequestEvaluator implements Runnable { // set the req.action property, cutting off the _action suffix req.action = action.substring(0, action.length() - 7); - // set the application checksum in response to make ETag - // generation sensitive to changes in the app - res.setApplicationChecksum(app.getChecksum()); - // reset skin recursion detection counter skinDepth = 0; @@ -856,7 +852,7 @@ public final class RequestEvaluator implements Runnable { this.req = req; this.reqtype = HTTP; this.session = session; - res = new ResponseTrans(req); + res = new ResponseTrans(app, req); result = null; exception = null; } @@ -875,7 +871,7 @@ public final class RequestEvaluator implements Runnable { req = new RequestTrans(reqtypeName); req.path = functionName; session = new Session(functionName, app); - res = new ResponseTrans(req); + res = new ResponseTrans(app, req); result = null; exception = null; } diff --git a/src/helma/framework/core/Skin.java b/src/helma/framework/core/Skin.java index bb1e26d9..0e3b129f 100644 --- a/src/helma/framework/core/Skin.java +++ b/src/helma/framework/core/Skin.java @@ -17,6 +17,7 @@ package helma.framework.core; import helma.framework.*; +import helma.framework.repository.Resource; import helma.objectmodel.ConcurrencyException; import helma.util.HtmlEncoder; import helma.util.SystemMap; @@ -24,6 +25,9 @@ import helma.util.WrappedMap; import helma.util.UrlEncoded; import java.util.*; import java.io.UnsupportedEncodingException; +import java.io.Reader; +import java.io.InputStreamReader; +import java.io.IOException; /** * This represents a Helma skin, i.e. a template created from containing Macro tags @@ -80,6 +84,22 @@ public final class Skin { parse(); } + public static Skin getSkin(Resource res, Application app) throws IOException { + String encoding = app.getProperty("skinCharset"); + Reader reader; + if (encoding == null) { + reader = new InputStreamReader(res.getInputStream()); + } else { + reader = new InputStreamReader(res.getInputStream(), encoding); + } + + char[] characterBuffer = new char[(int) res.getLength()]; + int length = reader.read(characterBuffer); + + reader.close(); + return new Skin(characterBuffer, length, app); + } + /** * Parse a skin object from source text */ diff --git a/src/helma/framework/core/SkinFile.java b/src/helma/framework/core/SkinFile.java deleted file mode 100644 index 03e7174e..00000000 --- a/src/helma/framework/core/SkinFile.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Helma License Notice - * - * The contents of this file are subject to the Helma License - * Version 2.0 (the "License"). You may not use this file except in - * compliance with the License. A copy of the License is available at - * http://adele.helma.org/download/helma/license.txt - * - * Copyright 1998-2003 Helma Software. All Rights Reserved. - * - * $RCSfile$ - * $Author$ - * $Revision$ - * $Date$ - */ - -package helma.framework.core; - -import helma.util.Updatable; -import java.io.*; - -/** - * This represents a File containing a Hop skin - */ -public final class SkinFile implements Updatable { - String name; - Prototype prototype; - Application app; - File file; - Skin skin; - long lastmod = 0; - - /** - * Creates a new SkinFile object. - * - * @param file ... - * @param name ... - * @param proto ... - */ - public SkinFile(File file, String name, Prototype proto) { - this.prototype = proto; - this.file = file; - this.name = name; - this.app = proto.app; - skin = null; - } - - /** - * Create a skinfile without a file, passing the skin body directly. This is used for - * Skins contained in zipped applications. The whole update mechanism is bypassed - * by immediately setting the skin member. - */ - public SkinFile(String body, String name, Prototype proto) { - this.prototype = proto; - this.app = proto.app; - this.name = name; - this.file = null; - skin = new Skin(body, app); - } - - /** - * Create a skinfile that doesn't belong to a prototype, or at - * least it doesn't know about its prototype and isn't managed by the prototype. - */ - public SkinFile(File file, String name, Application app) { - this.app = app; - this.file = file; - this.name = name; - this.prototype = null; - skin = null; - } - - /** - * Tell the type manager whether we need an update. this is the case when - * the file has been modified or deleted. - */ - public boolean needsUpdate() { - // if skin object is null we only need to call update if the file doesn't - // exist anymore, while if the skin is initialized, we'll catch both - // cases (file deleted and file changed) by just calling lastModified(). - return (skin != null) ? (lastmod != file.lastModified()) : (!file.exists()); - } - - /** - * - */ - public void update() { - if (!file.exists()) { - // remove skin from prototype - remove(); - } else { - // we only need to update if the skin has already been initialized - if (skin != null) { - read(); - } - } - } - - private void read() { - String encoding = app.getProperty("skinCharset"); - try { - Reader reader; - if (encoding == null) { - reader = new FileReader(file); - } else { - FileInputStream in = new FileInputStream(file); - reader = new InputStreamReader(in, encoding); - } - char[] c = new char[(int) file.length()]; - int length = reader.read(c); - - reader.close(); - skin = new Skin(c, length, app); - } catch (IOException x) { - app.logEvent("Error reading Skin " + file + ": " + x); - } - - lastmod = file.lastModified(); - } - - /** - * - */ - public void remove() { - if (prototype != null) { - prototype.removeSkinFile(this); - } - } - - /** - * - * - * @return ... - */ - public File getFile() { - return file; - } - - /** - * - * - * @return ... - */ - public Skin getSkin() { - if (skin == null) { - read(); - } - - return skin; - } - - /** - * - * - * @return ... - */ - public String getName() { - return name; - } - - /** - * - * - * @return ... - */ - public String toString() { - return new StringBuffer("[SkinFile ").append(prototype.getName()) - .append("/").append(name).append("]").toString(); - } - - /** - * Override to produce hash code depending on file name - * - * @return a hash code value for this object. - */ - public int hashCode() { - return toString().hashCode(); - } - - /** - * Override to equal other SkinFile with the same source name - * - * @param obj the object to compare to - * @return true if obj is a SkinFile with the same source name - */ - public boolean equals(Object obj) { - return (obj instanceof SkinFile) && - toString().equals(obj.toString()); - } - -} diff --git a/src/helma/framework/core/SkinManager.java b/src/helma/framework/core/SkinManager.java index 61b90d37..dde4673f 100644 --- a/src/helma/framework/core/SkinManager.java +++ b/src/helma/framework/core/SkinManager.java @@ -17,6 +17,8 @@ package helma.framework.core; import helma.objectmodel.INode; +import helma.framework.repository.FileResource; + import java.io.*; import java.util.*; @@ -41,7 +43,7 @@ public final class SkinManager implements FilenameFilter { skinExtension = ".skin"; } - protected Skin getSkin(Prototype proto, String skinname, Object[] skinpath) { + protected Skin getSkin(Prototype proto, String skinname, Object[] skinpath) throws IOException { if (proto == null) { return null; } @@ -77,7 +79,7 @@ public final class SkinManager implements FilenameFilter { return null; } - protected Skin getSkinInternal(Object skinset, String prototype, String skinname) { + protected Skin getSkinInternal(Object skinset, String prototype, String skinname) throws IOException { if ((prototype == null) || (skinset == null)) { return null; } @@ -101,19 +103,16 @@ public final class SkinManager implements FilenameFilter { } else { // Skinset is interpreted as directory name from which to // retrieve the skin - File f = new File(skinset.toString(), prototype); + StringBuffer b = new StringBuffer(skinset.toString()); + b.append(File.separatorChar).append(prototype).append(File.separatorChar) + .append(skinname).append(skinExtension); - // if directory does not exist use lower case property name - if (!f.isDirectory()) { - f = new File(skinset.toString(), prototype.toLowerCase()); - } + // TODO: check for lower case prototype name for backwards compat - f = new File(f, skinname + skinExtension); + File f = new File(b.toString()); if (f.exists() && f.canRead()) { - SkinFile sf = new SkinFile(f, skinname, app); - - return sf.getSkin(); + return Skin.getSkin(new FileResource(f), app); } } @@ -131,7 +130,7 @@ public final class SkinManager implements FilenameFilter { return null; } } - + String[] skinNames = dir.list(this); if ((skinNames == null) || (skinNames.length == 0)) { @@ -144,7 +143,7 @@ public final class SkinManager implements FilenameFilter { String name = skinNames[i].substring(0, skinNames[i].length() - 5); File file = new File(dir, skinNames[i]); - map.put(name, new SkinFile(file, name, proto)); + map.put(name, (new FileResource(file))); } return map; diff --git a/src/helma/framework/core/TypeManager.java b/src/helma/framework/core/TypeManager.java index 965ea7ed..4684aff0 100644 --- a/src/helma/framework/core/TypeManager.java +++ b/src/helma/framework/core/TypeManager.java @@ -17,10 +17,12 @@ package helma.framework.core; import helma.objectmodel.db.DbMapping; -import helma.scripting.*; -import helma.util.*; +import helma.framework.repository.ZipRepository; +import helma.framework.repository.Resource; +import helma.framework.repository.Repository; +import helma.framework.repository.ResourceTracker; + import java.io.*; -import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.*; @@ -37,24 +39,16 @@ public final class TypeManager { final static String skinExtension = ".skin"; private Application app; - private File appDir; + Repository[] repositories; + long[] modified; // map of prototypes private HashMap prototypes; - // map of zipped script files - private HashMap zipfiles; + // set of Java archives private HashSet jarfiles; + private long lastCheck = 0; - private long appDirMod = 0; - - // a checksum that changes whenever something in the application files changes. - private long checksum; - - // the hopobject prototype - // private Prototype hopobjectProto; - - // the global prototype - // private Prototype globalProto; + private long lastCodeUpdate; // app specific class loader, includes jar files in the app directory private AppClassLoader loader; @@ -64,31 +58,14 @@ public final class TypeManager { * * @param app ... * - * @throws MalformedURLException ... * @throws RuntimeException ... */ - public TypeManager(Application app) throws MalformedURLException { + public TypeManager(Application app) { this.app = app; - appDir = app.appDir; - - // make sure the directories for the standard prototypes exist, - // and lament otherwise - if (appDir.list().length == 0) { - for (int i = 0; i < standardTypes.length; i++) { - File f = new File(appDir, standardTypes[i]); - - if (!f.exists() && !f.mkdir()) { - app.logEvent("Warning: directory " + f.getAbsolutePath() + - " could not be created."); - } else if (!f.isDirectory()) { - app.logEvent("Warning: " + f.getAbsolutePath() + - " is not a directory."); - } - } - } - + repositories = new Repository[app.repositories.size()]; + app.repositories.toArray(repositories); + modified = new long[repositories.length]; prototypes = new HashMap(); - zipfiles = new HashMap(); jarfiles = new HashSet(); URL helmajar = TypeManager.class.getResource("/"); @@ -139,90 +116,63 @@ public final class TypeManager { try { checkFiles(); - } catch (Exception ignore) { - } + } catch (Exception ignore) {} lastCheck = System.currentTimeMillis(); } - /** - * Run through application's prototype directories and check if - * there are any prototypes to be created. - */ - private void checkFiles() { - // check if any files have been created/removed since last time we - // checked... - if (appDir.lastModified() > appDirMod) { - appDirMod = appDir.lastModified(); - - String[] list = appDir.list(); - - if (list == null) { - throw new RuntimeException("Can't read app directory " + appDir + - " - check permissions"); - } - - for (int i = 0; i < list.length; i++) { - if (list[i].endsWith(".zip")) { - ZippedAppFile zipped = (ZippedAppFile) zipfiles.get(list[i]); - - if (zipped == null) { - File f = new File(appDir, list[i]); - - if (!f.isDirectory() && f.exists()) { - zipped = new ZippedAppFile(f, app); - zipfiles.put(list[i], zipped); - } - } - - continue; - } - - if (list[i].endsWith(".jar")) { - if (!jarfiles.contains(list[i])) { - jarfiles.add(list[i]); - - File f = new File(appDir, list[i]); - - try { - loader.addURL(new URL("file:" + f.getAbsolutePath())); - } catch (MalformedURLException ignore) { - } - } - - continue; - } - - if (list[i].indexOf('.') > -1) { - continue; - } - - Prototype proto = getPrototype(list[i]); + private void checkRepository(Repository repository) throws IOException { + Repository[] list = repository.getRepositories(); + for (int i = 0; i < list.length; i++) { + if (list[i].isScriptRoot()) { + // this is an embedded top-level script repository + checkRepository(list[i]); + } else { + // its an prototype + String name = null; + name = ((Repository) list[i]).getShortName(); + Prototype proto = getPrototype(name); // if prototype doesn't exist, create it - if ((proto == null) && isValidTypeName(list[i])) { - File f = new File(appDir, list[i]); - - if (f.isDirectory()) { - // create new prototype - createPrototype(list[i], f); - } + if ((proto == null) && isValidTypeName(name)) { + // create new prototype + createPrototype(name, (Repository) list[i]); + } else { + proto.addRepository((Repository) list[i]); } } } - // calculate this app's checksum by adding all checksums from all prototypes - long newChecksum = 0; - - // loop through zip files to check for updates - for (Iterator it = zipfiles.values().iterator(); it.hasNext();) { - ZippedAppFile zipped = (ZippedAppFile) it.next(); - - if (zipped.needsUpdate()) { - zipped.update(); + Iterator resources = repository.getResources(); + while (resources.hasNext()) { + // check for jar files to add to class loader + Resource resource = (Resource) resources.next(); + String name = resource.getName(); + if (name.endsWith(".jar")) { + if (!jarfiles.contains(name)) { + jarfiles.add(name); + loader.addURL(resource.getUrl()); + } } + } + } - newChecksum += zipped.lastmod; + /** + * Run through application's prototype sources and check if + * there are any prototypes to be created. + */ + private void checkFiles() { + // check if any files have been created/removed since last time we checked... + for (int i = 0; i < repositories.length; i++) { + try { + if (repositories[i].lastModified() > modified[i]) { + modified[i] = repositories[i].lastModified(); + + checkRepository(repositories[i]); + } + } catch (IOException iox) { + iox.printStackTrace(); + } } // loop through prototypes and check if type.properties needs updates @@ -231,9 +181,6 @@ public final class TypeManager { for (Iterator i = prototypes.values().iterator(); i.hasNext();) { Prototype proto = (Prototype) i.next(); - // calculate this app's type checksum - newChecksum += proto.getChecksum(); - // update prototype's type mapping DbMapping dbmap = proto.getDbMapping(); @@ -243,25 +190,10 @@ public final class TypeManager { // global and HopObject, which is a bit awkward... // I mean we're the type manager, so this should // be part of our job, right? + proto.props.update(); dbmap.update(); } } - - checksum = newChecksum; - } - - protected void removeZipFile(String zipname) { - zipfiles.remove(zipname); - - for (Iterator i = prototypes.values().iterator(); i.hasNext();) { - Prototype proto = (Prototype) i.next(); - - // update prototype's type mapping - DbMapping dbmap = proto.getDbMapping(); - SystemProperties props = dbmap.getProperties(); - - props.removeProps(zipname); - } } private boolean isValidTypeName(String str) { @@ -283,8 +215,8 @@ public final class TypeManager { * Return a checksum over all files in all prototypes in this application. * The checksum can be used to find out quickly if any file has changed. */ - public long getChecksum() { - return checksum; + public long lastCodeUpdate() { + return lastCodeUpdate; } /** @@ -320,12 +252,11 @@ public final class TypeManager { * Create and register a new Prototype. * * @param typename the name of the prototype - * @param dir the prototype directory if it is know, or null if we - * ought to find out by ourselves + * @param repository the first prototype source * @return the newly created prototype */ - public Prototype createPrototype(String typename, File dir) { - Prototype proto = new Prototype(typename, dir, app); + public Prototype createPrototype(String typename, Repository repository) { + Prototype proto = new Prototype(typename, repository, app); // put the prototype into our map prototypes.put(proto.getLowerCaseName(), proto); @@ -334,124 +265,86 @@ public final class TypeManager { } /** - * Update a prototype to the files in the prototype directory. - */ - public void updatePrototype(String name) { - // System.err.println ("UPDATE PROTO: "+app.getName()+"/"+name); - Prototype proto = getPrototype(name); - - updatePrototype(proto); - } - - /** - * Update a prototype to the files in the prototype directory. + * Check a prototype for new or updated resources. */ public void updatePrototype(Prototype proto) { - if ((proto == null) || proto.isUpToDate()) { - return; - } synchronized (proto) { // check again because another thread may have checked the // prototype while we were waiting for access to the synchronized section - /* if (System.currentTimeMillis() - proto.getLastCheck() < 1000) - return; */ - HashSet updateSet = null; - HashSet createSet = null; + boolean updatedResources = false; + List createdResources = null; // our plan is to do as little as possible, so first check if // anything the prototype knows about has changed on disk - for (Iterator i = proto.updatables.values().iterator(); i.hasNext();) { - Updatable upd = (Updatable) i.next(); + for (Iterator i = proto.trackers.values().iterator(); i.hasNext();) { + ResourceTracker tracker = (ResourceTracker) i.next(); - if (upd.needsUpdate()) { - if (updateSet == null) { - updateSet = new HashSet(); + try { + if (tracker.hasChanged()) { + updatedResources = true; + tracker.markClean(); } - - updateSet.add(upd); + } catch (IOException iox) { + iox.printStackTrace(); } } // next we check if files have been created or removed since last update - // if (proto.getLastCheck() < dir.lastModified ()) { - File[] list = proto.getFiles(); + Resource[] resources = proto.getResources(); - for (int i = 0; i < list.length; i++) { - String fn = list[i].getName(); - - // ignore files starting with ".". - if (fn.startsWith(".")) { - continue; - } - - if (!proto.updatables.containsKey(fn)) { - if (fn.endsWith(templateExtension) || fn.endsWith(scriptExtension) || - fn.endsWith(actionExtension) || fn.endsWith(skinExtension) || - "type.properties".equalsIgnoreCase(fn)) { - if (createSet == null) { - createSet = new HashSet(); + for (int i = 0; i < resources.length; i++) { + String name = resources[i].getName(); + if (!proto.trackers.containsKey(name)) { + if (name.endsWith(templateExtension) || + name.endsWith(scriptExtension) || + name.endsWith(actionExtension) || + name.endsWith(skinExtension)) { + if (createdResources == null) { + createdResources = new ArrayList(); } - createSet.add(list[i]); + createdResources.add(resources[i]); } } } - // } // if nothing needs to be updated, mark prototype as checked and return - if ((updateSet == null) && (createSet == null)) { + if (!updatedResources && createdResources == null) { proto.markChecked(); return; } // first go through new files and create new items - if (createSet != null) { - Object[] newFiles = createSet.toArray(); + if (createdResources != null) { + Resource[] newResources = new Resource[createdResources.size()]; + createdResources.toArray(newResources); - for (int i = 0; i < newFiles.length; i++) { - File file = (File) newFiles[i]; - String filename = file.getName(); - int dot = filename.lastIndexOf("."); - String tmpname = filename.substring(0, dot); - String srcName = getSourceName(file); - - if (filename.endsWith(templateExtension)) { - try { - Template t = new Template(file, tmpname, srcName, proto); - - proto.addTemplate(t); - } catch (Throwable x) { - app.logEvent("Error updating prototype: " + x); - } - } else if (filename.endsWith(scriptExtension)) { - try { - FunctionFile ff = new FunctionFile(file, srcName, proto); - - proto.addFunctionFile(ff); - } catch (Throwable x) { - app.logEvent("Error updating prototype: " + x); - } - } else if (filename.endsWith(actionExtension)) { - try { - ActionFile af = new ActionFile(file, tmpname, srcName, proto); - - proto.addActionFile(af); - } catch (Throwable x) { - app.logEvent("Error updating prototype: " + x); - } - } else if (filename.endsWith(skinExtension)) { - SkinFile sf = new SkinFile(file, tmpname, proto); - - proto.addSkinFile(sf); - } + for (int i = 0; i < newResources.length; i++) { + String resourceName = newResources[i].getName(); + if (resourceName.endsWith(templateExtension) || + resourceName.endsWith(scriptExtension) || + resourceName.endsWith(actionExtension)) { + try { + proto.addCodeResource(newResources[i]); + } catch (Throwable x) { + app.logEvent("Error updating prototype: " + x); + } + } else if (resourceName.endsWith(skinExtension)) { + try { + proto.addSkinResource(newResources[i]); + } catch (Throwable x) { + app.logEvent("Error updating prototype: " + x); + } + } } } // next go through existing updatables - if (updateSet != null) { + if (updatedResources) { + /* for (Iterator i = updateSet.iterator(); i.hasNext();) { Updatable upd = (Updatable) i.next(); @@ -467,21 +360,15 @@ public final class TypeManager { } } } + */ } // mark prototype as checked and updated. proto.markChecked(); proto.markUpdated(); - } - // end of synchronized (proto) - } - - private String getSourceName(File file) { - StringBuffer b = new StringBuffer(app.getName()); - b.append(":"); - b.append(file.getParentFile().getName()); - b.append("/"); - b.append(file.getName()); - return b.toString(); + lastCodeUpdate = proto.lastCodeUpdate(); + + } // end of synchronized (proto) } + } diff --git a/src/helma/framework/core/ZippedAppFile.java b/src/helma/framework/core/ZippedAppFile.java deleted file mode 100644 index fb4b7775..00000000 --- a/src/helma/framework/core/ZippedAppFile.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Helma License Notice - * - * The contents of this file are subject to the Helma License - * Version 2.0 (the "License"). You may not use this file except in - * compliance with the License. A copy of the License is available at - * http://adele.helma.org/download/helma/license.txt - * - * Copyright 1998-2003 Helma Software. All Rights Reserved. - * - * $RCSfile$ - * $Author$ - * $Revision$ - * $Date$ - */ - -package helma.framework.core; - -import helma.objectmodel.db.DbMapping; -import helma.scripting.*; -import helma.util.SystemProperties; -import helma.util.Updatable; -import java.io.*; -import java.util.*; -import java.util.zip.*; - -/** - * This represents a Zip-File which may contain other Updatables for one or more prototypes. - */ -public class ZippedAppFile implements Updatable { - Application app; - File file; - long lastmod; - // Set of updatables provided by this zip file - Set updatables; - // Set of prototypes this zip files provides updatables for - Set prototypes; - - /** - * Creates a new ZippedAppFile object. - * - * @param file ... - * @param app ... - */ - public ZippedAppFile(File file, Application app) { - this.app = app; - this.file = file; - updatables = new HashSet(); - prototypes = new HashSet(); - } - - /** - * Tell the type manager whether we need an update. this is the case when - * the file has been modified or deleted. - */ - public boolean needsUpdate() { - return !file.exists() || (lastmod != file.lastModified()); - } - - /** - * - */ - public synchronized void update() { - if (!file.exists()) { - remove(); - } else { - ZipFile zip = null; - Set newUpdatables = new HashSet(); - Set newPrototypes = new HashSet(); - - try { - lastmod = file.lastModified(); - - zip = new ZipFile(file); - - for (Enumeration en = zip.entries(); en.hasMoreElements();) { - ZipEntry entry = (ZipEntry) en.nextElement(); - String ename = entry.getName(); - StringTokenizer st = new StringTokenizer(ename, "/"); - - int tokens = st.countTokens(); - if (tokens == 1) { - String fname = st.nextToken(); - - if ("app.properties".equalsIgnoreCase(fname)) { - app.props.addProps(file.getName(), zip.getInputStream(entry)); - } else if ("db.properties".equalsIgnoreCase(fname)) { - app.dbProps.addProps(file.getName(), zip.getInputStream(entry)); - } - - } else if (tokens == 2) { - String dir = st.nextToken(); - String fname = st.nextToken(); - - // System.err.println ("ZIPENTRY: "+ dir +" ~ "+fname); - Prototype proto = app.typemgr.getPrototype(dir); - - if (proto == null) { - proto = app.typemgr.createPrototype(dir, null); - } - - if (fname.endsWith(".hac")) { - String name = fname.substring(0, fname.lastIndexOf(".")); - String srcName = getSourceName(ename); - String content = getZipEntryContent(zip, entry); - - // System.err.println ("["+content+"]"); - ActionFile act = new ActionFile(content, name, srcName, - proto); - - proto.addActionFile(act); - newUpdatables.add(act); - - } else if (fname.endsWith(".hsp")) { - String name = fname.substring(0, fname.lastIndexOf(".")); - String srcName = getSourceName(ename); - String content = getZipEntryContent(zip, entry); - - // System.err.println ("["+content+"]"); - Template tmp = new Template(content, name, srcName, proto); - - proto.addTemplate(tmp); - newUpdatables.add(tmp); - - } else if (fname.endsWith(".skin")) { - String name = fname.substring(0, fname.lastIndexOf(".")); - String content = getZipEntryContent(zip, entry); - - // System.err.println ("["+content+"]"); - SkinFile skin = new SkinFile(content, name, proto); - - proto.addSkinFile(skin); - newUpdatables.add(skin); - - } else if (fname.endsWith(".js")) { - String srcName = getSourceName(ename); - String content = getZipEntryContent(zip, entry); - - // System.err.println ("["+content+"]"); - FunctionFile ff = new FunctionFile(content, srcName, proto); - - proto.addFunctionFile(ff); - newUpdatables.add(ff); - - } else if ("type.properties".equalsIgnoreCase(fname)) { - DbMapping dbmap = proto.getDbMapping(); - SystemProperties props = dbmap.getProperties(); - - props.addProps(file.getName(), zip.getInputStream(entry)); - } - - // mark prototype as updated - newPrototypes.add(proto); - } - } - } catch (Throwable x) { - System.err.println("Error updating ZipFile: " + x); - if (app.debug) { - x.printStackTrace(); - } - } finally { - // remove updatables that have gone - updatables.removeAll(newUpdatables); - for (Iterator it = updatables.iterator(); it.hasNext();) { - ((Updatable) it.next()).remove(); - } - updatables = newUpdatables; - - // mark both old and new prototypes as updated - prototypes.addAll(newPrototypes); - for (Iterator it = prototypes.iterator(); it.hasNext();) { - ((Prototype) it.next()).markUpdated(); - } - prototypes = newPrototypes; - - try { - zip.close(); - } catch (Exception ignore) { - } - } - } - } - - /** - * - */ - public void remove() { - // remove updatables from prototypes - for (Iterator it = updatables.iterator(); it.hasNext();) { - ((Updatable) it.next()).remove(); - } - // mark affected prototypes as updated - for (Iterator it = prototypes.iterator(); it.hasNext();) { - ((Prototype) it.next()).markUpdated(); - } - - // remove self from type manager - app.typemgr.removeZipFile(file.getName()); - } - - /** - * - * - * @param zip ... - * @param entry ... - * - * @return ... - * - * @throws IOException ... - */ - public String getZipEntryContent(ZipFile zip, ZipEntry entry) - throws IOException { - int size = (int) entry.getSize(); - char[] c = new char[size]; - InputStreamReader reader = new InputStreamReader(zip.getInputStream(entry)); - - int read = 0; - while (read < size) { - int r = reader.read(c, read, size-read); - if (r == -1) - break; - read += r; - } - - return new String(c); - } - - /** - * - * - * @return ... - */ - public String toString() { - return file.getName(); - } - - - private String getSourceName(String entry) { - StringBuffer b = new StringBuffer(app.getName()); - b.append(":"); - b.append(file.getName()); - b.append("/"); - b.append(entry); - return b.toString(); - } -} diff --git a/src/helma/framework/repository/AbstractRepository.java b/src/helma/framework/repository/AbstractRepository.java new file mode 100644 index 00000000..36ee3531 --- /dev/null +++ b/src/helma/framework/repository/AbstractRepository.java @@ -0,0 +1,122 @@ +/* + * Helma License Notice + * + * The contents of this file are subject to the Helma License + * Version 2.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://adele.helma.org/download/helma/license.txt + * + * Copyright 1998-2003 Helma Software. All Rights Reserved. + * + * $RCSfile$ + * $Author$ + * $Revision$ + * $Date$ + */ + +package helma.framework.repository; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Iterator; +import java.io.IOException; + +/** + * Provides common methods and fields for the default implementations of the + * repository interface + */ +public abstract class AbstractRepository implements Repository { + + + /** + * Parent repository this repository is contained in. + */ + Repository parent; + + /** + * Holds direct child repositories + */ + Repository[] repositories; + + /** + * Holds direct resources + */ + HashMap resources; + + /** + * Cached name for faster access + */ + String name; + + /** + * Cached short name for faster access + */ + String shortName; + + /** + * + */ + public abstract void update(); + + + /** + * + * @return + */ + public String getName() { + return name; + } + + public String getShortName() { + return shortName; + } + + public Repository getRootRepository() { + if (parent == null) { + return this; + } else { + return parent.getRootRepository(); + } + } + + public Resource getResource(String name) { + update(); + + return (Resource) resources.get(getName() + "/" + name); + } + + public Iterator getResources() { + update(); + + return resources.values().iterator(); + } + + public Repository[] getRepositories() { + update(); + + return repositories; + } + + public Repository getParentRepository() { + return parent; + } + + public List getAllResources() throws IOException { + update(); + + ArrayList allResources = new ArrayList(); + allResources.addAll(resources.values()); + + for (int i = 0; i < repositories.length; i++) { + allResources.addAll(repositories[i].getAllResources()); + } + + return allResources; + } + + public String toString() { + return getName(); + } + +} diff --git a/src/helma/framework/repository/FileRepository.java b/src/helma/framework/repository/FileRepository.java new file mode 100644 index 00000000..2666fdae --- /dev/null +++ b/src/helma/framework/repository/FileRepository.java @@ -0,0 +1,165 @@ +/* + * Helma License Notice + * + * The contents of this file are subject to the Helma License + * Version 2.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://adele.helma.org/download/helma/license.txt + * + * Copyright 1998-2003 Helma Software. All Rights Reserved. + * + * $RCSfile$ + * $Author$ + * $Revision$ + * $Date$ + */ + +package helma.framework.repository; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Repository implementation for directories providing file resources + */ +public class FileRepository extends AbstractRepository { + + // Directory serving sub-repositories and file resources + private File dir; + + private long lastModified = -1; + private long lastChecksum = 0; + private long lastChecksumTime = 0; + + /** + * Defines how long the checksum of the repository will be cached + */ + private final long cacheTime = 1000L; + + /** + * Constructs a FileRepository using the given argument + * @param initArgs absolute path to the directory + */ + public FileRepository(String initArgs) { + this(new File(initArgs), null); + } + + /** + * Constructs a FileRepository using the given directory as top-level + * repository + * @param dir directory + */ + public FileRepository(File dir) { + this(dir, null); + } + + /** + * Constructs a FileRepository using the given directory and top-level + * repository + * @param dir directory + * @param parent top-level repository + */ + private FileRepository(File dir, FileRepository parent) { + this.dir = dir; + if (!dir.exists()) { + create(); + } + + if (parent == null) { + name = shortName = dir.getAbsolutePath(); + } else { + this.parent = parent; + shortName = dir.getName(); + name = parent.getName() + "/" + shortName; + } + } + + public boolean exists() { + if (dir.exists() && dir.isDirectory()) { + return true; + } else { + return false; + } + } + + public void create() { + if (!dir.exists() || !dir.isDirectory()) { + dir.mkdirs(); + } + return; + } + + /** + * Checks wether the repository is to be considered a top-level + * repository from a scripting point of view. For example, a zip + * file within a file repository is not a root repository from + * a physical point of view, but from the scripting point of view it is. + * + * @return true if the repository is to be considered a top-level script repository + */ + public boolean isScriptRoot() { + return parent == null; + } + + public long lastModified() { + return dir.lastModified(); + } + + public long getChecksum() throws IOException { + // delay checksum check if already checked recently + if (System.currentTimeMillis() > lastChecksumTime + cacheTime) { + + update(); + long checksum = lastModified; + + for (int i = 0; i < repositories.length; i++) { + checksum += repositories[i].getChecksum(); + } + + lastChecksum = checksum; + lastChecksumTime = System.currentTimeMillis(); + } + + return lastChecksum; + } + + /** + * Updates the content cache of the repository + * Gets called from within all methods returning sub-repositories or + * resources + */ + public synchronized void update() { + if (dir.lastModified() != lastModified) { + lastModified = dir.lastModified(); + + File[] list = dir.listFiles(); + + ArrayList newRepositories = new ArrayList(list.length); + HashMap newResources = new HashMap(list.length); + + for (int i = 0; i < list.length; i++) { + if (list[i].isDirectory()) { + // a nested directory aka child file repository + newRepositories.add(new FileRepository(list[i], this)); + } else if (list[i].getName().endsWith(".zip")) { + // a nested zip repository + newRepositories.add(new ZipRepository(list[i], this)); + } else if (list[i].isFile()) { + // a file resource + FileResource resource = new FileResource(list[i], this); + newResources.put(resource.getName(), resource); + } + } + + repositories = (Repository[]) + newRepositories.toArray(new Repository[newRepositories.size()]); + resources = newResources; + } + } + + public String toString() { + return new StringBuffer("FileRepository[").append(name).append("]").toString(); + } +} diff --git a/src/helma/framework/repository/FileResource.java b/src/helma/framework/repository/FileResource.java new file mode 100644 index 00000000..01bdd721 --- /dev/null +++ b/src/helma/framework/repository/FileResource.java @@ -0,0 +1,106 @@ +/* + * Helma License Notice + * + * The contents of this file are subject to the Helma License + * Version 2.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://adele.helma.org/download/helma/license.txt + * + * Copyright 1998-2003 Helma Software. All Rights Reserved. + * + * $RCSfile$ + * $Author$ + * $Revision$ + * $Date$ + */ + +package helma.framework.repository; + +import java.net.*; +import java.io.*; + +public class FileResource implements Resource { + + File file; + Repository repository; + String name; + String shortName; + + public FileResource(File file) { + this(file, null); + } + + protected FileResource(File file, FileRepository repository) { + this.file = file; + + if (repository == null) { + name = shortName = file.getName(); + } else { + this.repository = repository; + name = repository.getName() + "/" + file.getName(); + if (name.lastIndexOf(".") != -1) { + shortName = file.getName().substring(0, file.getName().lastIndexOf(".")); + } else { + shortName = file.getName(); + } + } + } + + public String getName() { + return name; + } + + public String getShortName() { + return shortName; + } + + public InputStream getInputStream() { + try { + return new FileInputStream(file); + } catch (FileNotFoundException ex) { + return null; + } + } + + public URL getUrl() { + try { + return new URL("file:" + file.getAbsolutePath()); + } catch (MalformedURLException ex) { + return null; + } + } + + public long lastModified() { + return file.lastModified(); + } + + public String getContent() { + try { + InputStream in = getInputStream(); + byte[] byteBuffer = new byte[in.available()]; + + in.read(byteBuffer); + in.close(); + + return new String(byteBuffer); + } catch (Exception ignore) { + return ""; + } + } + + public long getLength() { + return file.length(); + } + + public boolean exists() { + return file.exists(); + } + + public Repository getRepository() { + return repository; + } + + public String toString() { + return getName(); + } +} diff --git a/src/helma/framework/repository/Repository.java b/src/helma/framework/repository/Repository.java new file mode 100644 index 00000000..65bb4ffb --- /dev/null +++ b/src/helma/framework/repository/Repository.java @@ -0,0 +1,135 @@ +/* + * Helma License Notice + * + * The contents of this file are subject to the Helma License + * Version 2.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://adele.helma.org/download/helma/license.txt + * + * Copyright 1998-2003 Helma Software. All Rights Reserved. + * + * $RCSfile$ + * $Author$ + * $Revision$ + * $Date$ + */ + +package helma.framework.repository; + +import java.util.List; +import java.util.Iterator; +import java.io.IOException; + +/** + * Repository represents an abstract container of resources (e.g. code, skins, ...). + * In addition to resources, repositories may contain other repositories, building + * a hierarchical structure. + */ +public interface Repository { + + /** + * Checksum of the repository and all its content. Implementations + * should make sure + * + * @return checksum + * @throws IOException + */ + public long getChecksum() throws IOException; + + /** + * Returns the date the repository was last modified. + * + * @return last modified date + * @throws IOException + */ + public long lastModified() throws IOException; + + + /** + * Returns a specific direct resource of the repository + * + * @param resourceName name of the child resource to return + * @return specified child resource + */ + public Resource getResource(String resourceName); + + /** + * Returns all direct resources + * + * @return direct resources + * @throws IOException + */ + public Iterator getResources() throws IOException; + + /** + * Returns all direct and indirect resources + * + * @return resources recursive + * @throws IOException + */ + public List getAllResources() throws IOException; + + /** + * Returns this repository's direct child repositories + * + * @return direct repositories + * @throws IOException + */ + public Repository[] getRepositories() throws IOException; + + /** + * Checks wether the repository actually (or still) exists + * + * @return true if the repository exists + * @throws IOException + */ + public boolean exists() throws IOException; + + /** + * Creates the repository if does not exist yet + * + * @throws IOException + */ + public void create() throws IOException; + + /** + * Checks wether the repository is to be considered a top-level + * repository from a scripting point of view. For example, a zip + * file within a file repository is not a root repository from + * a physical point of view, but from the scripting point of view it is. + * + * @return true if the repository is to be considered a top-level script repository + */ + public boolean isScriptRoot(); + + /** + * Returns this repository's parent repository. + * Returns null if this repository already is the top-level repository + * + * @return the parent repository + */ + public Repository getParentRepository(); + + /** + * Returns the top-level repository this repository is contained in + * + * @return top-level repository + */ + public Repository getRootRepository(); + + /** + * Returns the name of the repository; this is a full name including all + * parent repositories. + * + * @return full name of the repository + */ + public String getName(); + + /** + * Returns the name of the repository. + * + * @return name of the repository + */ + public String getShortName(); + +} diff --git a/src/helma/framework/repository/Resource.java b/src/helma/framework/repository/Resource.java new file mode 100644 index 00000000..a2e15a4a --- /dev/null +++ b/src/helma/framework/repository/Resource.java @@ -0,0 +1,86 @@ +/* + * Helma License Notice + * + * The contents of this file are subject to the Helma License + * Version 2.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://adele.helma.org/download/helma/license.txt + * + * Copyright 1998-2003 Helma Software. All Rights Reserved. + * + * $RCSfile$ + * $Author$ + * $Revision$ + * $Date$ + */ + +package helma.framework.repository; + +import java.io.InputStream; +import java.io.IOException; +import java.net.URL; + +/** + * Resource represents a pointer to some kind of information (code, skin, ...) + * from which the content can be fetched + */ +public interface Resource { + + /** + * Returns the date the resource was last modified + * @return last modified date + */ + public long lastModified(); + + /** + * Checks wether this resource actually (still) exists + * @return true if the resource exists + */ + public boolean exists(); + + /** + * Returns the lengh of the resource's content + * @return content length + */ + public long getLength() throws IOException; + + /** + * Returns an input stream to the content of the resource + * @return content input stream + */ + public InputStream getInputStream() throws IOException; + + /** + * Returns the content of the resource + * @return content + */ + public String getContent() throws IOException; + + /** + * Returns the name of the resource; does not include the name of the + * repository the resource was fetched from + * @return name of the resource + */ + public String getName(); + + /** + * Returns the short name of the resource which is its name exclusive file + * ending if it exists + * @return short name of the resource + */ + public String getShortName(); + + /** + * Returns an url to the resource if the repository of this resource is + * able to provide urls + * @return url to the resource + */ + public URL getUrl() throws UnsupportedOperationException; + + /** + * Returns the repository the resource does belong to + * @return upper repository + */ + public Repository getRepository(); + +} diff --git a/src/helma/framework/repository/ResourceComparator.java b/src/helma/framework/repository/ResourceComparator.java new file mode 100644 index 00000000..a67d20ae --- /dev/null +++ b/src/helma/framework/repository/ResourceComparator.java @@ -0,0 +1,118 @@ +/* + * Helma License Notice + * + * The contents of this file are subject to the Helma License + * Version 2.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://adele.helma.org/download/helma/license.txt + * + * Copyright 1998-2003 Helma Software. All Rights Reserved. + * + * $RCSfile$ + * $Author$ + * $Revision$ + * $Date$ + */ + +package helma.framework.repository; + +import java.util.Comparator; +import helma.framework.core.Application; + +/** + * Sorts resources according to the order of their repositories + */ +public class ResourceComparator implements Comparator { + + // the application where the top-level repositories can be found + protected Application app; + + /** + * Constructcs a ResourceComparator sorting according to the top-level + * repositories of the given application + * @param app application that provides the top-level repositories + */ + public ResourceComparator(Application app) { + this.app = app; + } + + /** + * Compares two Repositories, Resources or RepositoryTrackers + * @param obj1 Repository, Resource or RepositoryTrackers + * @param obj2 Repository, Resource or RepositoryTrackers + * @return a negative integer, zero, or a positive integer as the + * first argument is less than, equal to, or greater than the + * second. + * @throws ClassCastException if the arguments' types prevent them from + * being compared by this Comparator. + */ + public int compare(Object obj1, Object obj2) { + if (obj1.equals(obj2)) + return 0; + + Repository rep1 = getRootRepository(obj1); + Repository rep2 = getRootRepository(obj2); + + int pos1 = app.getRepositoryIndex(rep1); + int pos2 = app.getRepositoryIndex(rep2); + + if (rep1 == rep2 || (pos1 == -1 && pos2 == -1)) { + // Same root repository, but we must not return 0 unless objects are equal + // (see JavaDoc on java.util.TreeSet) so we compare full names + return getFullName(obj1).compareTo(getFullName(obj2)); + } + + return pos1 - pos2; + } + + /** + * Checks if the comparator is equal to the given comparator + * A ResourceComparator is equal to another ResourceComparator if the + * applications they belong to are equal + * + * @param obj comparator + * @return true if the given comparator equals + */ + public boolean equals(Object obj) { + return (obj instanceof ResourceComparator) && + app == ((ResourceComparator) obj).getApplication(); + } + + /** + * Return the application we're comparing resources for + * + * @return the application instance + */ + public Application getApplication() { + return app; + } + + private Repository getRootRepository(Object obj) { + if (obj instanceof Resource) + return ((Resource) obj).getRepository() + .getRootRepository(); + if (obj instanceof ResourceTracker) + return ((ResourceTracker) obj).getResource() + .getRepository() + .getRootRepository(); + if (obj instanceof Repository) + return ((Repository) obj).getRootRepository(); + + // something we can't compare + throw new IllegalArgumentException("Can't compare "+obj); + } + + private String getFullName(Object obj) { + if (obj instanceof Resource) + return ((Resource) obj).getName(); + if (obj instanceof ResourceTracker) + return ((ResourceTracker) obj).getResource() + .getName(); + if (obj instanceof Repository) + return ((Repository) obj).getName(); + + // something we can't compare + throw new IllegalArgumentException("Can't compare "+obj); + } + +} diff --git a/src/helma/framework/repository/ResourceTracker.java b/src/helma/framework/repository/ResourceTracker.java new file mode 100644 index 00000000..4107138c --- /dev/null +++ b/src/helma/framework/repository/ResourceTracker.java @@ -0,0 +1,46 @@ +/* + * Helma License Notice + * + * The contents of this file are subject to the Helma License + * Version 2.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://adele.helma.org/download/helma/license.txt + * + * Copyright 1998-2003 Helma Software. All Rights Reserved. + * + * $RCSfile$ + * $Author$ + * $Revision$ + * $Date$ + */ + +package helma.framework.repository; + +import java.io.IOException; + +/** + * A utility class that allows Resource consumers to track changes + * on resources. + */ +public class ResourceTracker { + + Resource resource; + long lastModified; + + public ResourceTracker(Resource resource) { + this.resource = resource; + markClean(); + } + + public boolean hasChanged() throws IOException { + return lastModified != resource.lastModified(); + } + + public void markClean() { + lastModified = resource.lastModified(); + } + + public Resource getResource() { + return resource; + } +} diff --git a/src/helma/framework/repository/ZipRepository.java b/src/helma/framework/repository/ZipRepository.java new file mode 100644 index 00000000..2d8cf5c1 --- /dev/null +++ b/src/helma/framework/repository/ZipRepository.java @@ -0,0 +1,187 @@ +/* + * Helma License Notice + * + * The contents of this file are subject to the Helma License + * Version 2.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://adele.helma.org/download/helma/license.txt + * + * Copyright 1998-2003 Helma Software. All Rights Reserved. + * + * $RCSfile$ + * $Author$ + * $Revision$ + * $Date$ + */ + +package helma.framework.repository; + +import helma.util.StringUtils; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public final class ZipRepository extends AbstractRepository { + + // zip file serving sub-repositories and zip file resources + private File file; + + // the nested directory depth of this repository + private int depth; + + private long lastModified = -1; + + /** + * Constructs a ZipRespository using the given argument + * @param initArgs absolute path to the zip file + */ + public ZipRepository(String initArgs) { + this(new File(initArgs), null, null); + } + + /** + * Constructs a ZipRepository using the given zip file as top-level + * repository + * @param file a zip file + */ + protected ZipRepository(File file, Repository parent) { + this(file, parent, null); + } + + /** + * Constructs a ZipRepository using the zip entry belonging to the given + * zip file and top-level repository + * @param file a zip file + * @param zipentry zip entry + * @param parent repository + */ + private ZipRepository(File file, Repository parent, ZipEntry zipentry) { + this.file = file; + this.parent = parent; + + if (zipentry == null) { + name = shortName = file.getName(); + depth = 0; + } else { + String[] entrypath = StringUtils.split(zipentry.getName(), "/"); + depth = entrypath.length; + shortName = entrypath[depth - 1]; + name = new StringBuffer(parent.getName()) + .append('/').append(shortName).toString(); + } + } + + /** + * Returns a java.util.zip.ZipFile for this repository. It is the caller's + * responsability to call close() in it when it is no longer needed. + * @return a ZipFile for reading + * @throws IOException + */ + protected ZipFile getZipFile() throws IOException { + return new ZipFile(file); + } + + public synchronized void update() { + if (file.lastModified() != lastModified) { + lastModified = file.lastModified(); + ZipFile zipfile = null; + + try { + zipfile = getZipFile(); + Enumeration enum = zipfile.entries(); + ArrayList newRepositories = new ArrayList(); + HashMap newResources = new HashMap(); + + while (enum.hasMoreElements()) { + ZipEntry entry = (ZipEntry) enum.nextElement(); + String entryname = entry.getName(); + String[] entrypath = StringUtils.split(entryname, "/"); + + // create new repositories and resources for all entries with a + // path depth of this.depth + 1 + if (entrypath.length == depth + 1) { + if (entry.isDirectory()) { + newRepositories.add(new ZipRepository(file, this, entry)); + } else { + ZipResource resource = new ZipResource(file, entry, this); + newResources.put(resource.getName(), resource); + } + } + } + + repositories = (Repository[]) + newRepositories.toArray(new Repository[newRepositories.size()]); + resources = newResources; + + } catch (IOException ex) { + ex.printStackTrace(); + repositories = new Repository[0]; + if (resources == null) { + resources = new HashMap(); + } else { + resources.clear(); + } + + } finally { + try { + // unlocks the zip file in the underlying filesystem + zipfile.close(); + } catch (Exception ex) {} + } + } + } + + public long getChecksum() { + return file.lastModified(); + } + + public boolean exists() { + ZipFile zipfile = null; + try { + /* a ZipFile needs to be created to see if the zip file actually + exists; this is not cached to provide blocking the zip file in + the underlying filesystem */ + zipfile = getZipFile(); + return true; + } catch (IOException ex) { + return false; + } + finally { + try { + // unlocks the zip file in the underlying filesystem + zipfile.close(); + } catch (Exception ex) { + return false; + } + } + } + + public void create() { + // we do not create zip files as it makes no sense + throw new UnsupportedOperationException("create() not implemented for ZipRepository"); + } + + /** + * Checks wether the repository is to be considered a top-level + * repository from a scripting point of view. For example, a zip + * file within a file repository is not a root repository from + * a physical point of view, but from the scripting point of view it is. + * + * @return true if the repository is to be considered a top-level script repository + */ + public boolean isScriptRoot() { + return depth == 0; + } + + public long lastModified() { + return file.lastModified(); + } + + public String toString() { + return new StringBuffer("ZipRepository[").append(name).append("]").toString(); + } + +} diff --git a/src/helma/framework/repository/ZipResource.java b/src/helma/framework/repository/ZipResource.java new file mode 100644 index 00000000..da17cbf4 --- /dev/null +++ b/src/helma/framework/repository/ZipResource.java @@ -0,0 +1,139 @@ +/* + * Helma License Notice + * + * The contents of this file are subject to the Helma License + * Version 2.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://adele.helma.org/download/helma/license.txt + * + * Copyright 1998-2003 Helma Software. All Rights Reserved. + * + * $RCSfile$ + * $Author$ + * $Revision$ + * $Date$ + */ + +package helma.framework.repository; + +import java.io.*; +import java.net.URL; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public final class ZipResource implements Resource { + + private ZipEntry zipentry; + private File zipfile; + private ZipRepository repository; + private String name; + private String shortName; + + protected ZipResource(File zipfile, ZipEntry zipentry, ZipRepository repository) { + this.zipentry = zipentry; + this.zipfile = zipfile; + this.repository = repository; + + String entryname = zipentry.getName(); + int lastDot = entryname.lastIndexOf('.'); + int lastSlash = entryname.lastIndexOf('/'); + + if (lastDot != -1 && lastDot > lastSlash) { + shortName = entryname.substring(lastSlash + 1, lastDot); + } else { + shortName = entryname.substring(lastSlash + 1); + } + + StringBuffer buf = new StringBuffer(repository.getName()) + .append('/').append(shortName); + if (lastDot != -1 && lastDot > lastSlash) { + name = buf.append(entryname.substring(lastDot)).toString(); + } else { + name = buf.toString(); + } + } + + public long lastModified() { + return zipfile.lastModified(); + } + + public InputStream getInputStream() throws IOException { + ZipFile zipfile = null; + try { + zipfile = repository.getZipFile(); + int size = (int) zipentry.getSize(); + byte[] buf = new byte[size]; + InputStream in = zipfile.getInputStream(zipentry); + int read = 0; + while (read < size) { + int r = in.read(buf, read, size-read); + if (r == -1) + break; + read += r; + } + in.close(); + return new ByteArrayInputStream(buf); + } finally { + zipfile.close(); + } + } + + public boolean exists() { + ZipFile zipfile = null; + try { + zipfile = repository.getZipFile(); + return (zipfile.getEntry(zipentry.getName()) != null); + } catch (Exception ex) { + return false; + } finally { + try { + zipfile.close(); + } catch (Exception ex) {} + } + } + + public String getContent() throws IOException { + ZipFile zipfile = null; + try { + zipfile = repository.getZipFile(); + InputStreamReader in = new InputStreamReader(zipfile.getInputStream(zipentry)); + int size = (int) zipentry.getSize(); + char[] buf = new char[size]; + int read = 0; + while (read < size) { + int r = in.read(buf, read, size-read); + if (r == -1) + break; + read += r; + } + in.close(); + return new String(buf); + } finally { + zipfile.close(); + } + } + + public String getName() { + return name; + } + + public String getShortName() { + return shortName; + } + + public URL getUrl() { + throw new UnsupportedOperationException("getUrl() not implemented for ZipResource"); + } + + public long getLength() { + return zipentry.getSize(); + } + + public Repository getRepository() { + return repository; + } + + public String toString() { + return getName(); + } +} diff --git a/src/helma/main/ApplicationManager.java b/src/helma/main/ApplicationManager.java index ec729632..ad19226a 100644 --- a/src/helma/main/ApplicationManager.java +++ b/src/helma/main/ApplicationManager.java @@ -17,8 +17,9 @@ package helma.main; import helma.framework.core.*; +import helma.framework.repository.Repository; +import helma.framework.repository.FileRepository; import helma.util.StringUtils; -import helma.util.SystemProperties; import org.apache.xmlrpc.XmlRpcHandler; import org.mortbay.http.*; import org.mortbay.http.handler.*; @@ -26,6 +27,7 @@ import org.mortbay.jetty.servlet.*; import java.io.*; import java.rmi.*; import java.util.*; +import helma.util.ResourceProperties; /** * This class is responsible for starting and stopping Helma applications. @@ -35,7 +37,7 @@ public class ApplicationManager implements XmlRpcHandler { private Hashtable applications; private Hashtable xmlrpcHandlers; private int rmiPort; - private SystemProperties props; + private ResourceProperties props; private Server server; private long lastModified; @@ -46,7 +48,7 @@ public class ApplicationManager implements XmlRpcHandler { * @param server the server instance * @param port The RMI port we're binding to */ - public ApplicationManager(SystemProperties props, + public ApplicationManager(ResourceProperties props, Server server, int port) { this.props = props; this.server = server; @@ -282,6 +284,7 @@ public class ApplicationManager implements XmlRpcHandler { String uploadLimit; String debug; boolean encode; + Repository[] repositories; /** * Creates an AppDescriptor from the properties. @@ -313,6 +316,60 @@ public class ApplicationManager implements XmlRpcHandler { appDir = (appDirName == null) ? null : new File(appDirName); String dbDirName = props.getProperty(name + ".dbdir"); dbDir = (dbDirName == null) ? null : new File(dbDirName); + + // read and configure app repositories + ArrayList repositoryList = new ArrayList(); + for (int i = 0; true; i++) { + Class[] parameters = { String.class }; + + String[] repositoryArgs = { props.getProperty(name + ".repository." + i) }; + + if (repositoryArgs[0] != null) { + // lookup repository implementation + String repositoryImpl = props.getProperty(name + ".repository." + i + + ".implementation"); + if (repositoryImpl == null) { + // implementation not set manually, have to guess it + if (repositoryArgs[0].endsWith(".zip")) { + repositoryImpl = "helma.framework.repository.ZipRepository"; + } else { + repositoryImpl = "helma.framework.repository.FileRepository"; + } + } + + Repository newRepository = null; + try { + newRepository = (Repository) Class.forName(repositoryImpl) + .getConstructor(parameters).newInstance(repositoryArgs); + repositoryList.add(newRepository); + } catch (Exception ex) { + System.out.println("Adding repository " + repositoryArgs + " failed. " + + "Will not use that repository. Check your initArgs!"); + } + } else { + // no more repositories to add + break; + } + } + + if (repositoryList.size() > 0) { + repositories = new Repository[repositoryList.size()]; + repositoryList.toArray(repositories); + } else { + repositories = new Repository[1]; + if (appDir != null) { + repositories[0] = new FileRepository(appDir); + } else { + repositories[0] = new FileRepository(new File(server.getAppsHome(), appName)); + } + try { + if (!repositories[0].exists()) { + repositories[0].create(); + } + } catch (Exception swallow) { + // couldn't create repository + } + } } @@ -321,7 +378,7 @@ public class ApplicationManager implements XmlRpcHandler { try { // create the application instance - app = new Application(appName, server, appDir, dbDir); + app = new Application(appName, server, repositories, dbDir); // register ourselves descriptors.put(appName, this); diff --git a/src/helma/main/Server.java b/src/helma/main/Server.java index 2274bf38..2d1aa399 100644 --- a/src/helma/main/Server.java +++ b/src/helma/main/Server.java @@ -18,6 +18,7 @@ package helma.main; import helma.extensions.HelmaExtension; import helma.framework.*; +import helma.framework.repository.FileResource; import helma.framework.core.*; import helma.objectmodel.db.DbSource; import helma.util.*; @@ -31,10 +32,10 @@ import org.mortbay.util.LogSink; import org.mortbay.util.MultiException; import org.mortbay.util.Frame; import java.io.*; -import java.net.*; import java.rmi.registry.*; import java.rmi.server.*; import java.util.*; +import helma.util.ResourceProperties; /** * Helma server main class. @@ -50,9 +51,9 @@ public class Server implements IPathElement, Runnable { protected File hopHome; // server-wide properties - SystemProperties appsProps; - SystemProperties dbProps; - SystemProperties sysProps; + ResourceProperties appsProps; + ResourceProperties dbProps; + ResourceProperties sysProps; // our logger private Log logger; @@ -105,7 +106,8 @@ public class Server implements IPathElement, Runnable { hopHome = config.homeDir; // create system properties - sysProps = new SystemProperties(config.propFile.getAbsolutePath()); + sysProps = new ResourceProperties(); + sysProps.addResource(new FileResource(config.propFile)); } @@ -184,7 +186,8 @@ public class Server implements IPathElement, Runnable { guessConfig(config); // create system properties - SystemProperties sysProps = new SystemProperties(config.propFile.getAbsolutePath()); + ResourceProperties sysProps = new ResourceProperties(); + sysProps.addResource(new FileResource(config.propFile)); // check if there's a property setting for those ports not specified via command line if ((config.websrvPort == null) && (sysProps.getProperty("webPort") != null)) { @@ -283,7 +286,8 @@ public class Server implements IPathElement, Runnable { } // create system properties - SystemProperties sysProps = new SystemProperties(config.propFile.getAbsolutePath()); + ResourceProperties sysProps = new ResourceProperties(); + sysProps.addResource(new FileResource(config.propFile)); // try to get hopHome from property file if (config.homeDir == null && sysProps.getProperty("hophome") != null) { @@ -442,18 +446,21 @@ public class Server implements IPathElement, Runnable { // read db.properties file in helma home directory - File helper = new File(hopHome, "db.properties"); - dbProps = new SystemProperties(helper.getAbsolutePath()); + dbProps = new ResourceProperties(); + dbProps.addResource(new FileResource(new File(hopHome, "db.properties"))); DbSource.setDefaultProps(dbProps); // read apps.properties file String appsPropfile = sysProps.getProperty("appsPropFile"); + File file; if ((appsPropfile != null) && !"".equals(appsPropfile.trim())) { - helper = new File(appsPropfile); + file = new File(appsPropfile); + appsProps = new ResourceProperties(); } else { - helper = new File(hopHome, "apps.properties"); + file = new File(hopHome, "apps.properties"); + appsProps = new ResourceProperties(); } - appsProps = new SystemProperties(helper.getAbsolutePath()); + appsProps.addResource(new FileResource(file)); paranoid = "true".equalsIgnoreCase(sysProps.getProperty("paranoid")); @@ -665,7 +672,7 @@ public class Server implements IPathElement, Runnable { return; } - // set the security manager. + // set the security manager. // the default implementation is helma.main.HelmaSecurityManager. try { String secManClass = sysProps.getProperty("securityManager"); @@ -756,7 +763,7 @@ public class Server implements IPathElement, Runnable { if (!dir.isAbsolute()) { dir = new File(hopHome, logDir); } - + logDir = dir.getAbsolutePath(); } System.setProperty("helma.logdir", logDir); @@ -804,7 +811,7 @@ public class Server implements IPathElement, Runnable { * * @return ... */ - public SystemProperties getProperties() { + public ResourceProperties getProperties() { return sysProps; } @@ -813,7 +820,7 @@ public class Server implements IPathElement, Runnable { * * @return ... */ - public SystemProperties getDbProperties() { + public ResourceProperties getDbProperties() { return dbProps; } diff --git a/src/helma/objectmodel/db/DbMapping.java b/src/helma/objectmodel/db/DbMapping.java index 20fc4053..f6e5b02d 100644 --- a/src/helma/objectmodel/db/DbMapping.java +++ b/src/helma/objectmodel/db/DbMapping.java @@ -18,8 +18,7 @@ package helma.objectmodel.db; import helma.framework.core.Application; import helma.framework.core.Prototype; -import helma.util.SystemProperties; -import helma.util.Updatable; +import helma.util.ResourceProperties; import java.sql.*; import java.util.*; @@ -29,7 +28,7 @@ import java.util.*; * relational database table. Basically it consists of a set of JavaScript property-to- * Database row bindings which are represented by instances of the Relation class. */ -public final class DbMapping implements Updatable { +public final class DbMapping { // DbMappings belong to an application protected Application app; @@ -37,7 +36,7 @@ public final class DbMapping implements Updatable { private String typename; // properties from where the mapping is read - private SystemProperties props; + private ResourceProperties props; // name of data dbSource to which this mapping writes private DbSource dbSource; @@ -131,7 +130,7 @@ public final class DbMapping implements Updatable { /** * Create a DbMapping from a type.properties property file */ - public DbMapping(Application app, String typename, SystemProperties props) { + public DbMapping(Application app, String typename, ResourceProperties props) { this.app = app; // create a unique instance of the string. This is useful so // we can compare types just by using == instead of equals. @@ -1349,7 +1348,7 @@ public final class DbMapping implements Updatable { * * @return ... */ - public SystemProperties getProperties() { + public ResourceProperties getProperties() { return props; } } diff --git a/src/helma/objectmodel/db/DbSource.java b/src/helma/objectmodel/db/DbSource.java index f1409f82..2f76bdbd 100644 --- a/src/helma/objectmodel/db/DbSource.java +++ b/src/helma/objectmodel/db/DbSource.java @@ -16,7 +16,7 @@ package helma.objectmodel.db; -import helma.util.SystemProperties; +import helma.util.ResourceProperties; import java.sql.Connection; import java.sql.DriverManager; @@ -28,10 +28,10 @@ import java.util.Properties; * This class describes a releational data source (URL, driver, user and password). */ public class DbSource { - private static SystemProperties defaultProps = null; + private static ResourceProperties defaultProps = null; private Properties conProps; private String name; - private SystemProperties props; + private ResourceProperties props; protected String url; private String driver; private boolean isOracle; @@ -45,7 +45,7 @@ public class DbSource { * * @throws ClassNotFoundException ... */ - public DbSource(String name, SystemProperties props) + public DbSource(String name, ResourceProperties props) throws ClassNotFoundException { this.name = name; this.props = props; @@ -158,10 +158,10 @@ public class DbSource { * * @param props ... */ - public static void setDefaultProps(SystemProperties props) { + public static void setDefaultProps(ResourceProperties props) { defaultProps = props; } - + /** * Is this an Oracle database? * diff --git a/src/helma/scripting/ActionFile.java b/src/helma/scripting/ActionFile.java deleted file mode 100644 index a04de2e3..00000000 --- a/src/helma/scripting/ActionFile.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Helma License Notice - * - * The contents of this file are subject to the Helma License - * Version 2.0 (the "License"). You may not use this file except in - * compliance with the License. A copy of the License is available at - * http://adele.helma.org/download/helma/license.txt - * - * Copyright 1998-2003 Helma Software. All Rights Reserved. - * - * $RCSfile$ - * $Author$ - * $Revision$ - * $Date$ - */ - -package helma.scripting; - -import helma.framework.core.*; -import helma.util.Updatable; -import java.io.*; - -/** - * An ActionFile is a file containing function code that is exposed as a URI - * of objects of this class/type. It is - * usually represented by a file with extension .hac (hop action file) - * that contains the raw body of the function. - */ -public class ActionFile implements Updatable { - String name; - String sourceName; - Prototype prototype; - File file; - String content; - long lastmod; - - /** - * Creates a new ActionFile object. - * - * @param file ... - * @param name ... - * @param sourceName ... - * @param proto ... - */ - public ActionFile(File file, String name, String sourceName, Prototype proto) { - this.prototype = proto; - this.name = name; - this.sourceName = sourceName; - this.file = file; - this.lastmod = file.lastModified(); - this.content = null; - } - - /** - * Creates a new ActionFile object. - * - * @param content ... - * @param name ... - * @param sourceName ... - * @param proto ... - */ - public ActionFile(String content, String name, String sourceName, Prototype proto) { - this.prototype = proto; - this.name = name; - this.sourceName = sourceName; - this.file = null; - this.content = content; - } - - /** - * Tell the type manager whether we need an update. this is the case when - * the file has been modified or deleted. - */ - public boolean needsUpdate() { - return lastmod != file.lastModified(); - } - - /** - * - */ - public void update() { - if (!file.exists()) { - // remove functions declared by this from all object prototypes - remove(); - } else { - lastmod = file.lastModified(); - } - } - - /** - * - */ - public void remove() { - prototype.removeActionFile(this); - } - - /** - * - * - * @return ... - */ - public File getFile() { - return file; - } - - /** - * - * - * @return ... - */ - public String getName() { - return name; - } - - /** - * - * - * @return ... - */ - public String getSourceName() { - return sourceName; - } - - /** - * - * - * @return ... - * - * @throws FileNotFoundException ... - */ - public Reader getReader() throws FileNotFoundException { - if (content != null) { - return new StringReader(content); - } else if (file.length() == 0) { - return new StringReader(";"); - } else { - return new FileReader(file); - } - } - - /** - * - * - * @return ... - */ - public String getContent() { - if (content != null) { - return content; - } else { - try { - FileReader reader = new FileReader(file); - char[] cbuf = new char[(int) file.length()]; - - reader.read(cbuf); - reader.close(); - - return new String(cbuf); - } catch (Exception filex) { - prototype.getApplication().logEvent("Error reading " + this + ": " + filex); - - return null; - } - } - } - - /** - * - * - * @return ... - */ - public String getFunctionName() { - return name + "_action"; - } - - /** - * - * - * @return ... - */ - public Prototype getPrototype() { - return prototype; - } - - /** - * - * - * @return ... - */ - public String toString() { - return "ActionFile[" + sourceName + "]"; - } - - /** - * Override to produce hash code depending on source name - * - * @return a hash code value for this object. - */ - public int hashCode() { - return sourceName.hashCode(); - } - - /** - * Override to equal other ActionFile with the same source name - * - * @param obj the object to compare to - * @return true if obj is a ActionFile with the same source name - */ - public boolean equals(Object obj) { - return (obj instanceof ActionFile) && - sourceName.equals(((ActionFile) obj).getSourceName()); - } - -} diff --git a/src/helma/scripting/FunctionFile.java b/src/helma/scripting/FunctionFile.java deleted file mode 100644 index 208d1d7f..00000000 --- a/src/helma/scripting/FunctionFile.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Helma License Notice - * - * The contents of this file are subject to the Helma License - * Version 2.0 (the "License"). You may not use this file except in - * compliance with the License. A copy of the License is available at - * http://adele.helma.org/download/helma/license.txt - * - * Copyright 1998-2003 Helma Software. All Rights Reserved. - * - * $RCSfile$ - * $Author$ - * $Revision$ - * $Date$ - */ - -package helma.scripting; - -import helma.framework.core.*; -import helma.util.Updatable; -import java.io.*; - -/** - * This represents a File containing script functions for a given class/prototype. - */ -public class FunctionFile implements Updatable { - Prototype prototype; - File file; - String sourceName; - String content; - long lastmod; - - /** - * Creates a new FunctionFile object. - * - * @param file ... - * @param sourceName ... - * @param proto ... - */ - public FunctionFile(File file, String sourceName, Prototype proto) { - this.prototype = proto; - this.sourceName = sourceName; - this.file = file; - update(); - } - - /** - * Create a function file without a file, passing the code directly. This is used for - * files contained in zipped applications. The whole update mechanism is bypassed - * by immediately parsing the code. - * - * @param body ... - * @param sourceName ... - * @param proto ... - */ - public FunctionFile(String body, String sourceName, Prototype proto) { - this.prototype = proto; - this.sourceName = sourceName; - this.file = null; - this.content = body; - } - - /** - * Tell the type manager whether we need an update. this is the case when - * the file has been modified or deleted. - */ - public boolean needsUpdate() { - return (file != null) && (lastmod != file.lastModified()); - } - - /** - * - */ - public void update() { - if (file != null) { - if (!file.exists()) { - remove(); - } else { - lastmod = file.lastModified(); - } - } - } - - /** - * - * - * @return ... - */ - public File getFile() { - return file; - } - - /** - * - * - * @return ... - */ - public String getContent() { - return content; - } - - /** - * - * - * @return ... - */ - public String getSourceName() { - return sourceName; - } - - /** - * - */ - public void remove() { - prototype.removeFunctionFile(this); - } - - /** - * - * - * @return ... - */ - public String toString() { - return sourceName; - } - - /** - * Override to produce hash code depending on source name - * - * @return a hash code value for this object. - */ - public int hashCode() { - return sourceName.hashCode(); - } - - /** - * Override to equal other FunctionFiles with the same source name - * - * @param obj the object to compare to - * @return true if obj is a FunctionFile with the same source name - */ - public boolean equals(Object obj) { - return (obj instanceof FunctionFile) && - sourceName.equals(((FunctionFile) obj).getSourceName()); - } -} diff --git a/src/helma/scripting/Template.java b/src/helma/scripting/Template.java deleted file mode 100644 index c7b16d87..00000000 --- a/src/helma/scripting/Template.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Helma License Notice - * - * The contents of this file are subject to the Helma License - * Version 2.0 (the "License"). You may not use this file except in - * compliance with the License. A copy of the License is available at - * http://adele.helma.org/download/helma/license.txt - * - * Copyright 1998-2003 Helma Software. All Rights Reserved. - * - * $RCSfile$ - * $Author$ - * $Revision$ - * $Date$ - */ - -package helma.scripting; - -import helma.framework.core.*; -import java.io.*; -import java.util.StringTokenizer; -import java.util.Vector; - -/** - * This represents a Helma template, i.e. a file with the extension .hsp - * (Helma server page) that contains both parts that are to be evaluated - * as EcmaScript and parts that are to be delivered to the client as-is. - * Internally, templates are regular functions. - * Helma templates are callable via URL, but this is just a leftover from the - * days when there were no .hac (action) files. The recommended way - * now is to have a .hac file with all the logic which in turn calls one or more - * template files to do the formatting. - */ -public class Template extends ActionFile { - /** - * Creates a new Template object. - * - * @param file ... - * @param name ... - * @param sourceName - * @param proto ... - */ - public Template(File file, String name, String sourceName, Prototype proto) { - super(file, name, sourceName, proto); - } - - /** - * Creates a new Template object. - * - * @param content ... - * @param name ... - * @param sourceName ... - * @param proto ... - */ - public Template(String content, String name, String sourceName, Prototype proto) { - super(content, name, sourceName, proto); - } - - /** - * - * - * @return ... - */ - public String getFunctionName() { - return name; - } - - /** - * - * - * @return ... - */ - public Reader getReader() { - return new StringReader(getContent()); - } - - /** - * - * - * @return ... - */ - public String getContent() { - Vector partBuffer = new Vector(); - String cstring = super.getContent(); - char[] cnt = cstring.toCharArray(); - int l = cnt.length; - - if (l == 0) { - return ""; - } - - // if last charackter is whitespace, swallow it. this is necessary for some inner templates to look ok. - if (Character.isWhitespace(cnt[l - 1])) { - l -= 1; - } - - int lastIdx = 0; - - for (int i = 0; i < (l - 1); i++) { - if ((cnt[i] == '<') && (cnt[i + 1] == '%')) { - int j = i + 2; - - while ((j < (l - 1)) && ((cnt[j] != '%') || (cnt[j + 1] != '>'))) { - j++; - } - - if (j > (i + 2)) { - if ((i - lastIdx) > 0) { - partBuffer.addElement(new Part(this, - new String(cnt, lastIdx, - i - lastIdx), true)); - } - - String script = new String(cnt, i + 2, (j - i) - 2); - - partBuffer.addElement(new Part(this, script, false)); - lastIdx = j + 2; - } - - i = j + 1; - } - } - - if (lastIdx < l) { - partBuffer.addElement(new Part(this, new String(cnt, lastIdx, l - lastIdx), - true)); - } - - StringBuffer templateBody = new StringBuffer(); - int nparts = partBuffer.size(); - - for (int k = 0; k < nparts; k++) { - Part nextPart = (Part) partBuffer.elementAt(k); - - if (nextPart.isStatic || nextPart.content.trim().startsWith("=")) { - // check for <%= ... %> statements - if (!nextPart.isStatic) { - nextPart.content = nextPart.content.trim().substring(1).trim(); - - // cut trailing ";" - while (nextPart.content.endsWith(";")) - nextPart.content = nextPart.content.substring(0, - nextPart.content.length() - - 1); - } - - StringTokenizer st = new StringTokenizer(nextPart.content, "\r\n", true); - String nextLine = st.hasMoreTokens() ? st.nextToken() : null; - - // count newLines we "swallow", see explanation below - int newLineCount = 0; - - templateBody.append("res.write ("); - - if (nextPart.isStatic) { - templateBody.append("\""); - } - - while (nextLine != null) { - if ("\n".equals(nextLine)) { - // append a CRLF - newLineCount++; - templateBody.append("\\r\\n"); - } else if (!"\r".equals(nextLine)) { - try { - StringReader lineReader = new StringReader(nextLine); - int c = lineReader.read(); - - while (c > -1) { - if (nextPart.isStatic && - (((char) c == '"') || ((char) c == '\\'))) { - templateBody.append('\\'); - } - - templateBody.append((char) c); - c = lineReader.read(); - } - } catch (IOException srx) { - } - } - - nextLine = st.hasMoreTokens() ? st.nextToken() : null; - } - - if (nextPart.isStatic) { - templateBody.append("\""); - } - - templateBody.append("); "); - - // append the number of lines we have "swallowed" into - // one write statement, so error messages will *approximately* - // give correct line numbers. - for (int i = 0; i < newLineCount; i++) { - templateBody.append("\r\n"); - } - } else { - templateBody.append(nextPart.content); - - if (!nextPart.content.trim().endsWith(";")) { - templateBody.append(";"); - } - } - } - - // templateBody.append ("\r\nreturn null;\r\n"); - return templateBody.toString(); - } - - /** - * - */ - public void remove() { - prototype.removeTemplate(this); - } - - class Part { - String content; - Template parent; - boolean isPart; - boolean isStatic; - - public Part(Template parent, String content, boolean isStatic) { - isPart = false; - this.parent = parent; - this.content = content; - this.isStatic = isStatic; - } - - public String getName() { - return isStatic ? null : content; - } - - public String toString() { - return "Template.Part [" + content + "," + isStatic + "]"; - } - } - - /** - * Override to produce hash code depending on source name - * - * @return a hash code value for this object. - */ - public int hashCode() { - return sourceName.hashCode(); - } - - /** - * Override to equal other Template with the same source name - * - * @param obj the object to compare to - * @return true if obj is a Template with the same source name - */ - public boolean equals(Object obj) { - return (obj instanceof Template) && - sourceName.equals(((Template) obj).getSourceName()); - } - -} diff --git a/src/helma/scripting/rhino/GlobalObject.java b/src/helma/scripting/rhino/GlobalObject.java index c83b5a92..76c9aab9 100644 --- a/src/helma/scripting/rhino/GlobalObject.java +++ b/src/helma/scripting/rhino/GlobalObject.java @@ -105,7 +105,7 @@ public class GlobalObject extends ImporterTopLevel implements PropertyRecorder { * @return ... */ public boolean renderSkin(Object skinobj, Object paramobj) - throws UnsupportedEncodingException { + throws UnsupportedEncodingException, IOException { Context cx = Context.getCurrentContext(); RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval"); RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine"); @@ -139,7 +139,7 @@ public class GlobalObject extends ImporterTopLevel implements PropertyRecorder { * @return ... */ public String renderSkinAsString(Object skinobj, Object paramobj) - throws UnsupportedEncodingException { + throws UnsupportedEncodingException, IOException { Context cx = Context.getCurrentContext(); RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval"); RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine"); diff --git a/src/helma/scripting/rhino/HopObject.java b/src/helma/scripting/rhino/HopObject.java index 3d97bef1..c49e0d5f 100644 --- a/src/helma/scripting/rhino/HopObject.java +++ b/src/helma/scripting/rhino/HopObject.java @@ -27,6 +27,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Constructor; import java.util.*; import java.io.UnsupportedEncodingException; +import java.io.IOException; /** * @@ -250,7 +251,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * @return ... */ public boolean jsFunction_renderSkin(Object skinobj, Object paramobj) - throws UnsupportedEncodingException { + throws UnsupportedEncodingException, IOException { Context cx = Context.getCurrentContext(); RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval"); RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine"); @@ -284,7 +285,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * @return ... */ public String jsFunction_renderSkinAsString(Object skinobj, Object paramobj) - throws UnsupportedEncodingException { + throws UnsupportedEncodingException, IOException { Context cx = Context.getCurrentContext(); RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval"); RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine"); @@ -319,7 +320,8 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * * @return ... */ - public Object jsFunction_href(Object action) throws UnsupportedEncodingException { + public Object jsFunction_href(Object action) throws UnsupportedEncodingException, + IOException { if (node == null) { return null; } @@ -790,9 +792,6 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * @return ... */ public Object get(String name, Scriptable start) { - // System.err.println("GET from "+this+": "+name+" ->"+super.get(name, start)); - Object retval = null; - if (node == null) { return super.get(name, start); } else { diff --git a/src/helma/scripting/rhino/JavaObject.java b/src/helma/scripting/rhino/JavaObject.java index ce934da1..738cd4df 100644 --- a/src/helma/scripting/rhino/JavaObject.java +++ b/src/helma/scripting/rhino/JavaObject.java @@ -22,6 +22,7 @@ import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.io.UnsupportedEncodingException; +import java.io.IOException; /** * @@ -70,7 +71,7 @@ public class JavaObject extends NativeJavaObject { * @return ... */ public boolean renderSkin(Object skinobj, Object paramobj) - throws UnsupportedEncodingException { + throws UnsupportedEncodingException, IOException { Context cx = Context.getCurrentContext(); RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval"); RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine"); @@ -104,7 +105,7 @@ public class JavaObject extends NativeJavaObject { * @return ... */ public String renderSkinAsString(Object skinobj, Object paramobj) - throws UnsupportedEncodingException { + throws UnsupportedEncodingException, IOException { Context cx = Context.getCurrentContext(); RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval"); RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine"); @@ -139,7 +140,8 @@ public class JavaObject extends NativeJavaObject { * * @return ... */ - public Object href(Object action) throws UnsupportedEncodingException { + public Object href(Object action) throws UnsupportedEncodingException, + IOException { if (javaObject == null) { return null; } diff --git a/src/helma/scripting/rhino/RhinoActionAdapter.java b/src/helma/scripting/rhino/RhinoActionAdapter.java index 442ee72b..061d1844 100644 --- a/src/helma/scripting/rhino/RhinoActionAdapter.java +++ b/src/helma/scripting/rhino/RhinoActionAdapter.java @@ -16,7 +16,9 @@ package helma.scripting.rhino; -import helma.scripting.*; +import helma.framework.repository.Resource; + +import java.io.IOException; /** * An class that updates fesi interpreters with actionfiles and templates. @@ -31,9 +33,9 @@ public class RhinoActionAdapter { * * @param action ... */ - public RhinoActionAdapter(ActionFile action) { + public RhinoActionAdapter(Resource action) throws IOException { String content = action.getContent(); - String functionName = action.getFunctionName().replace('.', '_'); + String functionName = action.getShortName().replace('.', '_'); sourceName = action.toString(); function = composeFunction(functionName, @@ -41,7 +43,7 @@ public class RhinoActionAdapter { content); // check if this is a template and we need to generate an "_as_string" variant - if (action instanceof Template) { + if (action.getName().endsWith(".hsp")) { functionAsString = composeFunction(functionName + "_as_string", "arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10", "res.pushStringBuffer(); " + content + diff --git a/src/helma/scripting/rhino/RhinoCore.java b/src/helma/scripting/rhino/RhinoCore.java index dfaa5a06..20e3eaae 100644 --- a/src/helma/scripting/rhino/RhinoCore.java +++ b/src/helma/scripting/rhino/RhinoCore.java @@ -18,6 +18,7 @@ package helma.scripting.rhino; import helma.scripting.rhino.extensions.*; import helma.framework.core.*; +import helma.framework.repository.Resource; import helma.objectmodel.*; import helma.objectmodel.db.DbMapping; import helma.objectmodel.db.DbKey; @@ -215,18 +216,9 @@ public final class RhinoCore { } // loop through the prototype's code elements and evaluate them - // first the zipped ones ... - for (Iterator it = prototype.getZippedCode().values().iterator(); it.hasNext();) { - Object code = it.next(); - - evaluate(type, code); - } - - // then the unzipped ones (this is to make sure unzipped code overwrites zipped code) - for (Iterator it = prototype.getCode().values().iterator(); it.hasNext();) { - Object code = it.next(); - - evaluate(type, code); + Iterator code = prototype.getCodeResources(); + while (code.hasNext()) { + evaluate(type, (Resource) code.next()); } type.commitCompilation(); @@ -310,7 +302,9 @@ public final class RhinoCore { TypeInfo type = (TypeInfo) prototypes.get("global"); - updatePrototype(type, checked); + if (type != null) { + updatePrototype(type, checked); + } for (Iterator i = protos.iterator(); i.hasNext();) { Prototype proto = (Prototype) i.next(); @@ -624,7 +618,7 @@ public final class RhinoCore { } protected String postProcessHref(Object obj, String protoName, String basicHref) - throws UnsupportedEncodingException { + throws UnsupportedEncodingException, IOException { // check if the app.properties specify a href-function to post-process the // basic href. String hrefFunction = app.getProperty("hrefFunction", null); @@ -750,46 +744,10 @@ public final class RhinoCore { // private evaluation/compilation methods //////////////////////////////////////////////// - private synchronized void evaluate(TypeInfo type, Object code) { - if (code instanceof FunctionFile) { - FunctionFile funcfile = (FunctionFile) code; - File file = funcfile.getFile(); - - if (file != null) { - try { - FileReader fr = new FileReader(file); - - updateEvaluator(type, fr, funcfile.getSourceName(), 1); - } catch (IOException iox) { - app.logEvent("Error updating function file: " + iox); - } - } else { - StringReader reader = new StringReader(funcfile.getContent()); - - updateEvaluator(type, reader, funcfile.getSourceName(), 1); - } - } else if (code instanceof ActionFile) { - ActionFile action = (ActionFile) code; - RhinoActionAdapter fa = new RhinoActionAdapter(action); - - try { - updateEvaluator(type, new StringReader(fa.function), - action.getSourceName(), 0); - if (fa.functionAsString != null) { - // templates have an _as_string variant that needs to be compiled - updateEvaluator(type, new StringReader(fa.functionAsString), - action.getSourceName(), 0); - } - } catch (Exception esx) { - app.logEvent("Error parsing " + action + ": " + esx); - } - } - } - - private synchronized void updateEvaluator(TypeInfo type, Reader reader, - String sourceName, int firstline) { - // System.err.println("UPDATE EVALUATOR: "+prototype+" - "+sourceName); + private synchronized void evaluate (TypeInfo type, Resource code) { Scriptable threadScope = global.unregisterScope(); + String sourceName = code.getName(); + Reader reader = null; try { // get the current context @@ -798,8 +756,14 @@ public final class RhinoCore { Scriptable op = type.objProto; // do the update, evaluating the file - // Script script = cx.compileReader(reader, sourceName, firstline, null); - cx.evaluateReader(op, reader, sourceName, firstline, null); + if (sourceName.endsWith(".js")) { + reader = new InputStreamReader(code.getInputStream()); + cx.evaluateReader(op, reader, sourceName, 1, null); + } else if (sourceName.endsWith(".hac")) { + RhinoActionAdapter raa = new RhinoActionAdapter(code); + reader = new StringReader(raa.function); + cx.evaluateReader(op, reader, sourceName, 0, null); + } } catch (Exception e) { app.logEvent("Error parsing file " + sourceName + ": " + e); @@ -892,7 +856,7 @@ public final class RhinoCore { /** * Compilation has been completed successfully - switch over to code - * from temporary prototype, removing properties that haven't been + * from temporary prototype, removing properties that haven't been * renewed. */ public void commitCompilation() { @@ -935,7 +899,7 @@ public final class RhinoCore { } // mark this type as updated - lastUpdate = frameworkProto.getLastUpdate(); + lastUpdate = frameworkProto.lastCodeUpdate(); // If this prototype defines a postCompile() function, call it Context cx = Context.getCurrentContext(); @@ -953,7 +917,7 @@ public final class RhinoCore { } public boolean needsUpdate() { - return frameworkProto.getLastUpdate() > lastUpdate; + return frameworkProto.lastCodeUpdate() > lastUpdate; } public void setParentType(TypeInfo type) { diff --git a/src/helma/scripting/rhino/RhinoEngine.java b/src/helma/scripting/rhino/RhinoEngine.java index 069824a6..71a46864 100644 --- a/src/helma/scripting/rhino/RhinoEngine.java +++ b/src/helma/scripting/rhino/RhinoEngine.java @@ -30,6 +30,8 @@ import helma.scripting.rhino.debug.Tracer; import org.mozilla.javascript.*; import java.util.*; +import java.io.File; +import java.io.IOException; import java.lang.ref.WeakReference; /** @@ -432,7 +434,7 @@ public class RhinoEngine implements ScriptingEngine { */ public IPathElement getIntrospector() { if (doc == null) { - doc = new DocApplication(app.getName(), app.getAppDir().toString()); + doc = new DocApplication(app.getName(), new File(Server.getServer().getAppsHome(), app.getName())); doc.readApplication(); } return doc; @@ -482,7 +484,7 @@ public class RhinoEngine implements ScriptingEngine { * skinpath set in the current response object and does per-response skin * caching. */ - public Skin getSkin(String protoName, String skinName) { + public Skin getSkin(String protoName, String skinName) throws IOException { SkinKey key = new SkinKey(protoName, skinName); Skin skin = reval.res.getCachedSkin(key); diff --git a/src/helma/servlet/StandaloneServletClient.java b/src/helma/servlet/StandaloneServletClient.java index 0540719c..a8aca2ad 100644 --- a/src/helma/servlet/StandaloneServletClient.java +++ b/src/helma/servlet/StandaloneServletClient.java @@ -17,7 +17,10 @@ package helma.servlet; import helma.framework.*; +import helma.framework.repository.Repository; import helma.framework.core.Application; +import helma.framework.repository.Repository; +import helma.framework.repository.FileRepository; import java.io.*; import javax.servlet.*; @@ -92,10 +95,11 @@ public final class StandaloneServletClient extends AbstractServletClient { } try { - File appHome = new File(appDir); + Repository[] repositories = new Repository[1]; + repositories[0] = new FileRepository(new File(appDir)); File dbHome = new File(dbDir); - app = new Application(appName, appHome, dbHome); + app = new Application(appName, repositories, dbHome); app.init(); app.start(); } catch (Exception x) { diff --git a/src/helma/util/CryptResource.java b/src/helma/util/CryptResource.java new file mode 100644 index 00000000..617b6662 --- /dev/null +++ b/src/helma/util/CryptResource.java @@ -0,0 +1,124 @@ +/* + * Helma License Notice + * + * The contents of this file are subject to the Helma License + * Version 2.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://adele.helma.org/download/helma/license.txt + * + * Copyright 1998-2003 Helma Software. All Rights Reserved. + * + * $RCSfile$ + * $Author$ + * $Revision$ + * $Date$ + */ + +package helma.util; + +import java.io.BufferedReader; +import java.io.StringReader; +import java.util.Properties; +import java.util.StringTokenizer; +import helma.framework.repository.Resource; +import helma.framework.repository.Resource; + +/** + * This file authenticates against a passwd source + */ +public class CryptResource { + + private Properties users; + private CryptResource parentResource; + private Resource resource; + private long lastRead = 0; + + /** + * Creates a new CryptSource object. + * + * @param resource ... + * @param parentResource ... + */ + public CryptResource(Resource resource, CryptResource parentResource) { + this.resource = resource; + this.parentResource = parentResource; + users = new Properties(); + } + + /** + * + * + * @param username ... + * @param pw ... + * + * @return ... + */ + public boolean authenticate(String username, String pw) { + if (resource.exists() && (resource.lastModified() > lastRead)) { + readFile(); + } else if (!resource.exists() && (users.size() > 0)) { + users.clear(); + } + + String realpw = users.getProperty(username); + + if (realpw != null) { + try { + // check if password matches + // first we try with unix crypt algorithm + String cryptpw = Crypt.crypt(realpw, pw); + + if (realpw.equals(cryptpw)) { + return true; + } + + // then try MD5 + if (realpw.equals(MD5Encoder.encode(pw))) { + return true; + } + } catch (Exception x) { + return false; + } + } else { + if (parentResource != null) { + return parentResource.authenticate(username, pw); + } + } + + return false; + } + + private synchronized void readFile() { + BufferedReader reader = null; + + users = new Properties(); + + try { + reader = new BufferedReader(new StringReader(resource.getContent())); + + String line = reader.readLine(); + + while (line != null) { + StringTokenizer st = new StringTokenizer(line, ":"); + + if (st.countTokens() > 1) { + users.put(st.nextToken(), st.nextToken()); + } + + line = reader.readLine(); + } + } catch (Exception ignore) { + } finally { + if (reader != null) { + try { + reader.close(); + } catch (Exception x) { + } + } + + lastRead = System.currentTimeMillis(); + } + } + +} + diff --git a/src/helma/util/ResourceProperties.java b/src/helma/util/ResourceProperties.java new file mode 100644 index 00000000..ab7d8624 --- /dev/null +++ b/src/helma/util/ResourceProperties.java @@ -0,0 +1,411 @@ +/* + * Helma License Notice + * + * The contents of this file are subject to the Helma License + * Version 2.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://adele.helma.org/download/helma/license.txt + * + * Copyright 1998-2003 Helma Software. All Rights Reserved. + * + * $RCSfile$ + * $Author$ + * $Revision$ + * $Date$ + */ + +package helma.util; + +import java.io.IOException; +import java.util.*; +import helma.framework.core.*; +import helma.framework.repository.Resource; +import helma.framework.repository.Repository; + +/** + * A property dictionary that is updated from property resources + */ +public final class ResourceProperties extends Properties { + + // Delay between checks + private final long cacheTime = 1500L; + + // Default properties + public ResourceProperties defaultProperties; + + // Defines wether keys are case-sensitive or not + private boolean ignoreCase = true; + + // Cached checksum of last check + private long lastChecksum = -1; + + // Time of last check + private long lastCheck = 0; + + // Time porperties were last modified + private long lastModified = 0; + + // Application where to fetch additional resources + private Application app; + + // Name of possible resources to fetch from the applications's repositories + private String resourceName; + + // Sorted map of resources + private TreeSet resources; + + /** + * Constructs an empty ResourceProperties + * Resources must be added manually afterwards + */ + public ResourceProperties() { + resources = new TreeSet(); + } + + /** + * Constructs a ResourceProperties retrieving resources from the given + * application using the given name to fetch resources + * @param app application to fetch resources from + * @param resourceName name to use when fetching resources from the application + */ + public ResourceProperties(Application app, String resourceName) { + this.app = app; + this.resourceName = resourceName; + resources = new TreeSet(app.getResourceComparator()); + } + + /** + * Constructs a ResourceProperties retrieving resources from the given + * application using the given name to fetch resources and falling back + * to the given default properties + * @param app application to fetch resources from + * @param sourceName name to use when fetching resources from the application + * @param defaultProperties default properties + */ + public ResourceProperties(Application app, String sourceName, ResourceProperties defaultProperties) { + this(app, sourceName); + this.defaultProperties = defaultProperties; + forceUpdate(); + } + + /** + * Updates the properties regardless of an actual need + */ + private void forceUpdate() { + lastChecksum = -1; + update(); + } + + /** + * Sets the default properties and updates all properties + * @param defaultProperties default properties + */ + public void setDefaultProperties(ResourceProperties defaultProperties) { + this.defaultProperties = defaultProperties; + update(); + } + + /** + * Adds a resource to the list of resources and updates all properties if + * needed + * @param resource resource to add + */ + public void addResource(Resource resource) { + if (resource != null) { + resources.add(resource); + forceUpdate(); + } + } + + /** + * Removes a resource from the list of resources and updates all properties + * if needed + * @param resource resource to remove + */ + public void removeResource(Resource resource) { + if (resources.contains(resource)) { + resources.remove(resource); + forceUpdate(); + } + + return; + } + + /** + * Checks wether the properties need to be updated + * @return true if the properties need tu be updated + */ + public boolean needsUpdate() { + lastCheck = System.currentTimeMillis(); + if (getChecksum() != lastChecksum) { + return true; + } else { + return false; + } + } + + /** + * Updates all properties if there is a need to update + */ + public void update() { + if (needsUpdate() || (defaultProperties != null && defaultProperties.needsUpdate())) { + clear(); + + // first of all, properties are load from default properties + if (defaultProperties != null) { + defaultProperties.update(); + this.putAll(defaultProperties); + } + + /* next we try to load properties from the application's + repositories, if we blong to any application */ + if (app != null) { + Iterator iterator = app.getRepositories(); + while (iterator.hasNext()) { + try { + Repository repository = (Repository) iterator.next(); + Resource resource = repository.getResource(resourceName); + if (resource != null) { + load(resource.getInputStream()); + } + } catch (IOException iox) { + iox.printStackTrace(); + } + } + } + + // at last we try to load properties from the resource list + if (resources != null) { + Iterator iterator = resources.iterator(); + while (iterator.hasNext()) { + try { + load(((Resource) iterator.next()).getInputStream()); + } catch (IOException ignore) {} + } + } + + lastChecksum = getChecksum(); + lastCheck = lastModified = System.currentTimeMillis(); + } + + return; + } + + /** + * Checks wether the given object is in the value list + * @param value value to look for + * @return true if the value is found in the value list + */ + public boolean contains(Object value) { + if ((System.currentTimeMillis() - lastCheck) > cacheTime) { + update(); + } + + return super.contains(value.toString()); + } + + /** + * Checks wether the given object is in the key list + * @param key key to look for + * @return true if the key is found in the key list + */ + public boolean containsKey(Object key) { + if ((System.currentTimeMillis() - lastCheck) > cacheTime) { + update(); + } + + return super.containsKey(key.toString()); + } + + /** + * Returns an enumeration of all values + * @return values enumeration + */ + public Enumeration elements() { + if ((System.currentTimeMillis() - lastCheck) > cacheTime) { + update(); + } + + return super.elements(); + } + + /** + * Returns a value in this list fetched by the given key + * @param key key to use for fetching the value + * @return value belonging to the given key + */ + public Object get(Object key) { + if ((System.currentTimeMillis() - lastCheck) > cacheTime) { + update(); + } + + return (String) super.get(ignoreCase == true ? key.toString().toLowerCase() : key.toString()); + } + + /** + * Returns the date the resources were last modified + * @return last modified date + */ + public long lastModified() { + return lastModified; + } + + /** + * Returns a checksum for all resources + * @return checksum + */ + public long getChecksum() { + long checksum = 0; + + if (app != null) { + Iterator iterator = app.getRepositories(); + while (iterator.hasNext()) { + try { + Repository repository = (Repository) iterator.next(); + Resource resource = repository.getResource(resourceName); + checksum += resource != null ? + resource.lastModified() : repository.lastModified(); + } catch (IOException iox) { + iox.printStackTrace(); + } + } + } + + if (resources != null) { + Iterator iterator = resources.iterator(); + while (iterator.hasNext()) { + checksum += ((Resource) iterator.next()).lastModified(); + } + } + + return checksum; + } + + /** + * Returns a value in the list fetched by the given key or a default value + * if no corresponding key is found + * @param key key to use for fetching the value + * @param defaultValue default value to return if key is not found + * @return spiecific value or default value if not found + */ + public String getProperty(String key, String defaultValue) { + if ((System.currentTimeMillis() - lastCheck) > cacheTime) { + update(); + } + + return super.getProperty(ignoreCase == true ? key.toLowerCase() : key, defaultValue); + } + + /** + * Returns a value in this list fetched by the given key + * @param key key to use for fetching the value + * @return value belonging to the given key + */ + public String getProperty(String key) { + if ((System.currentTimeMillis() - lastCheck) > cacheTime) { + update(); + } + + return super.getProperty(ignoreCase == true ? key.toLowerCase() : key); + } + + /** + * Checks wether the properties list is empty + * @return true if the properties list is empty + */ + public boolean isEmpty() { + if ((System.currentTimeMillis() - lastCheck) > cacheTime) { + update(); + } + + return super.isEmpty(); + } + + /** + * Checks wether case-sensitivity is ignored for keys + * @return true if case-sensitivity is ignored for keys + */ + public boolean isIgnoreCase() { + return ignoreCase; + } + + /** + * Returns an enumeration of all keys + * @return keys enumeration + */ + public Enumeration keys() { + if ((System.currentTimeMillis() - lastCheck) > cacheTime) { + update(); + } + + return super.keys(); + } + + /** + * Returns a set of all keys + * @return keys set + */ + public Set keySet() { + if ((System.currentTimeMillis() - lastCheck) > cacheTime) { + update(); + } + + return super.keySet(); + } + + /** + * Puts a new key-value pair into the properties list + * @param key key + * @param value value + * @return the old value, if an old value got replaced + */ + public Object put(Object key, Object value) { + if (value != null) { + value = value.toString().trim(); + } + + return super.put(ignoreCase == true ? key.toString().toLowerCase() : key.toString(), value); + } + + /** + * Removes a key-value pair from the properties list + * @param key key + * @return the old value + */ + public Object remove(Object key) { + return super.remove(ignoreCase == true ? key.toString().toLowerCase() : key.toString()); + } + + /** + * Changes how keys are handled + * @param ignore true if to ignore case-sensitivity for keys + */ + public void setIgnoreCase(boolean ignore) { + if (!super.isEmpty()) { + throw new RuntimeException("setIgnoreCase() can only be called on empty Properties"); + } + ignoreCase = ignore; + return; + } + + /** + * Returns the number of peroperties in the list + * @return number of properties + */ + public int size() { + if ((System.currentTimeMillis() - lastCheck) > cacheTime) { + update(); + } + + return super.size(); + } + + /** + * Returns a string-representation of the class + * @return string + */ + public String toString() { + return super.toString(); + } + +} diff --git a/src/helma/util/Updatable.java b/src/helma/util/Updatable.java deleted file mode 100644 index 5a81f5d5..00000000 --- a/src/helma/util/Updatable.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Helma License Notice - * - * The contents of this file are subject to the Helma License - * Version 2.0 (the "License"). You may not use this file except in - * compliance with the License. A copy of the License is available at - * http://adele.helma.org/download/helma/license.txt - * - * Copyright 1998-2003 Helma Software. All Rights Reserved. - * - * $RCSfile$ - * $Author$ - * $Revision$ - * $Date$ - */ - -package helma.util; - - -/** - * An interface of classes that can update themselves and know when to do so. - */ -public interface Updatable { - /** - * - * - * @return ... - */ - public boolean needsUpdate(); - - /** - * - */ - public void update(); - - /** - * - */ - public void remove(); -}