From d1f972591faabe7ba0d5b2b7d014c4c8e08dc8bd Mon Sep 17 00:00:00 2001 From: hns Date: Mon, 20 Oct 2008 12:48:53 +0000 Subject: [PATCH] Merge helma 1 trunk from revision 8828 to 9332 minus the commit 9325 for case sensitive HopObject properties which is going to Helma 1.7 exclusively. svn merge -r 8828:HEAD https://dev.helma.org/svn/helma/helma/trunk/ svn merge -r 9325:9324 https://dev.helma.org/svn/helma/helma/trunk/ --- README.txt | 2 +- build/build.xml | 541 ------------------ license.txt | 2 +- src/helma/framework/RequestTrans.java | 13 +- src/helma/framework/ResponseTrans.java | 18 +- src/helma/framework/core/Application.java | 40 +- src/helma/framework/core/ApplicationBean.java | 41 +- .../framework/core/RequestEvaluator.java | 37 +- src/helma/framework/core/Session.java | 2 + src/helma/framework/core/Skin.java | 38 +- src/helma/framework/core/TypeManager.java | 30 +- src/helma/main/ApplicationManager.java | 17 +- src/helma/main/Server.java | 17 +- src/helma/main/ServerConfig.java | 4 +- src/helma/objectmodel/TransientNode.java | 3 +- src/helma/objectmodel/db/DbMapping.java | 29 +- src/helma/objectmodel/db/Node.java | 128 +++-- .../objectmodel/db/OrderedSubnodeList.java | 2 +- src/helma/objectmodel/db/SubnodeList.java | 2 +- src/helma/objectmodel/db/Transactor.java | 62 +- src/helma/objectmodel/dom/XmlWriter.java | 4 +- src/helma/scripting/rhino/HopObject.java | 38 +- src/helma/scripting/rhino/HopObjectCtor.java | 9 +- .../scripting/rhino/ListViewWrapper.java | 337 ----------- src/helma/scripting/rhino/RhinoCore.java | 20 +- src/helma/scripting/rhino/RhinoEngine.java | 23 +- .../scripting/rhino/SerializationProxy.java | 14 +- .../scripting/rhino/extensions/XmlObject.java | 2 +- src/helma/servlet/AbstractServletClient.java | 53 +- .../servlet/StandaloneServletClient.java | 26 +- src/helma/util/HtmlEncoder.java | 11 +- src/helma/util/Logger.java | 10 +- src/helma/util/ResourceProperties.java | 57 +- 33 files changed, 458 insertions(+), 1174 deletions(-) delete mode 100644 build/build.xml delete mode 100644 src/helma/scripting/rhino/ListViewWrapper.java diff --git a/README.txt b/README.txt index d5e1b144..9ec81dcc 100644 --- a/README.txt +++ b/README.txt @@ -1,4 +1,4 @@ -This is the README file for version 1.6.2 of the Helma Javascript +This is the README file for version 1.6.3 of the Helma Javascript Web Application Framework. ============== diff --git a/build/build.xml b/build/build.xml deleted file mode 100644 index c6c2f1c5..00000000 --- a/build/build.xml +++ /dev/null @@ -1,541 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/license.txt b/license.txt index 441e6676..d8e184f9 100644 --- a/license.txt +++ b/license.txt @@ -1,4 +1,4 @@ - Copyright (c) 1999-2006 Helma Project. All rights reserved. + Copyright (c) 1999-2008 Helma Project. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/src/helma/framework/RequestTrans.java b/src/helma/framework/RequestTrans.java index 4b78ca2e..ae13bece 100644 --- a/src/helma/framework/RequestTrans.java +++ b/src/helma/framework/RequestTrans.java @@ -164,7 +164,18 @@ public class RequestTrans implements Serializable { * @return true if this might be an XML-RPC request. */ public synchronized boolean checkXmlRpc() { - return "POST".equals(method) && "text/xml".equals(request.getContentType()); + if ("POST".equalsIgnoreCase(method)) { + String contentType = request.getContentType(); + if (contentType == null) { + return false; + } + int semi = contentType.indexOf(";"); + if (semi > -1) { + contentType = contentType.substring(0, semi); + } + return "text/xml".equalsIgnoreCase(contentType.trim()); + } + return false; } /** diff --git a/src/helma/framework/ResponseTrans.java b/src/helma/framework/ResponseTrans.java index 8e8a322a..8baeb479 100644 --- a/src/helma/framework/ResponseTrans.java +++ b/src/helma/framework/ResponseTrans.java @@ -493,11 +493,11 @@ public final class ResponseTrans extends Writer implements Serializable { /** * Allow to directly set the byte array for the response. Calling this more than once will - * overwrite the previous output. We take a generic object as parameter to be able to - * generate a better error message, but it must be byte[]. + * overwrite the previous output. + * @param bytes an arbitrary byte array */ - public void writeBinary(byte[] what) { - response = what; + public void writeBinary(byte[] bytes) { + response = bytes; } /** @@ -649,6 +649,11 @@ public final class ResponseTrans extends Writer implements Serializable { // there's no point in closing the response buffer HttpServletResponse res = reqtrans.getServletResponse(); if (res != null && res.isCommitted()) { + // response was committed using HttpServletResponse directly. We need + // set response to null and notify waiters in order to let attached + // requests know they can't reuse this response. + response = null; + notifyAll(); return; } @@ -664,7 +669,8 @@ public final class ResponseTrans extends Writer implements Serializable { boolean encodingError = false; - // only close if the response hasn't been closed yet + // only close if the response hasn't been closed yet, and if no + // response was generated using writeBinary(). if (response == null) { // if debug buffer exists, append it to main buffer if (contentType != null && @@ -747,7 +753,7 @@ public final class ResponseTrans extends Writer implements Serializable { * @return the response body */ public byte[] getContent() { - return (response == null) ? new byte[0] : response; + return response; } /** diff --git a/src/helma/framework/core/Application.java b/src/helma/framework/core/Application.java index b8aeb882..d02b3da4 100644 --- a/src/helma/framework/core/Application.java +++ b/src/helma/framework/core/Application.java @@ -342,7 +342,7 @@ public final class Application implements Runnable { String ignoreDirs; Initializer(String dirs) { - super("INIT-" + name); + super(name + "-init"); ignoreDirs = dirs; } @@ -489,7 +489,7 @@ public final class Application implements Runnable { releaseEvaluator(eval); } - worker = new Thread(this, "Worker-" + name); + worker = new Thread(this, name + "-worker"); worker.setPriority(Thread.NORM_PRIORITY + 1); worker.start(); } @@ -720,6 +720,14 @@ public final class Application implements Runnable { if (ev != null) { res = ev.attachHttpRequest(req); + if (res != null) { + // we can only use the existing response object if the response + // wasn't written to the HttpServletResponse directly. + res.waitForClose(); + if (res.getContent() == null) { + res = null; + } + } } if (res == null) { @@ -752,8 +760,6 @@ public final class Application implements Runnable { } catch (UnsupportedEncodingException uee) { logError("Unsupported response encoding", uee); } - } else { - res.waitForClose(); } } @@ -1462,7 +1468,7 @@ public final class Application implements Runnable { /** * get the app's event log. */ - Log getEventLog() { + public Log getEventLog() { if (eventLog == null) { eventLog = getLogger(eventLogName); // set log level for event log in case it is a helma.util.Logger @@ -1479,7 +1485,7 @@ public final class Application implements Runnable { /** * get the app's access log. */ - Log getAccessLog() { + public Log getAccessLog() { if (accessLog == null) { accessLog = getLogger(accessLogName); } @@ -1735,28 +1741,6 @@ public final class Application implements Runnable { return Collections.unmodifiableList(repositories); } - /** - * Set the code resource currently being evaluated/compiled. This is used - * to set the proper parent repository when a new repository is added - * via app.addRepository(). - * - * @param resource the resource being currently evaluated/compiled - */ - public void setCurrentCodeResource(Resource resource) { - currentCodeResource = resource; - } - - /** - * Set the code resource currently being evaluated/compiled. This is used - * to set the proper parent repository when a new repository is added - * via app.addRepository(). - - * @return the resource being currently evaluated/compiled - */ - public Resource getCurrentCodeResource() { - return currentCodeResource; - } - /** * Return the directory of the Helma server */ diff --git a/src/helma/framework/core/ApplicationBean.java b/src/helma/framework/core/ApplicationBean.java index 8bd0b377..d23c2c4c 100644 --- a/src/helma/framework/core/ApplicationBean.java +++ b/src/helma/framework/core/ApplicationBean.java @@ -38,7 +38,7 @@ import org.apache.commons.logging.LogFactory; * application specific functionality. */ public class ApplicationBean implements Serializable { - Application app; + transient Application app; WrappedMap properties = null; /** @@ -137,33 +137,27 @@ public class ApplicationBean implements Serializable { * * @param obj the repository, relative or absolute path to the library. */ - public void addRepository(Object obj) { - Resource current = app.getCurrentCodeResource(); - Repository parent = current == null ? - null : current.getRepository().getRootRepository(); + public synchronized void addRepository(Object obj) { Repository rep; if (obj instanceof String) { String path = (String) obj; - File file = new File(path).getAbsoluteFile(); + File file = findResource(null, path); if (!file.exists()) { - file = new File(path + ".zip").getAbsoluteFile(); + file = findResource(app.hopHome, path); } if (!file.exists()) { - file = new File(path + ".js").getAbsoluteFile(); - } - if (!file.exists()) { - throw new RuntimeException("Repository path does not exist: " + obj); + throw new RuntimeException("Repository path does not exist: " + file); } if (file.isDirectory()) { - rep = new FileRepository(file, parent); + rep = new FileRepository(file); } else if (file.isFile()) { if (file.getName().endsWith(".zip")) { - rep = new ZipRepository(file, parent); + rep = new ZipRepository(file); } else { - rep = new SingleFileRepository(file, parent); + rep = new SingleFileRepository(file); } } else { - throw new RuntimeException("Unrecognized file type in addRepository: " + obj); + throw new RuntimeException("Unsupported file type in addRepository: " + file); } } else if (obj instanceof Repository) { rep = (Repository) obj; @@ -178,6 +172,23 @@ public class ApplicationBean implements Serializable { } } + /** + * Helper method to resolve a repository path. + * @param parent the parent file + * @param path the repository path + * @return our best guess of what the file may be + */ + private File findResource(File parent, String path) { + File file = new File(parent, path).getAbsoluteFile(); + if (!file.exists()) { + file = new File(parent, path + ".zip").getAbsoluteFile(); + } + if (!file.exists()) { + file = new File(parent, path + ".js").getAbsoluteFile(); + } + return file; + } + /** * Get the app's classloader * @return the app's classloader diff --git a/src/helma/framework/core/RequestEvaluator.java b/src/helma/framework/core/RequestEvaluator.java index b2aee3ef..e14df4f3 100644 --- a/src/helma/framework/core/RequestEvaluator.java +++ b/src/helma/framework/core/RequestEvaluator.java @@ -25,6 +25,7 @@ import java.util.*; import org.apache.xmlrpc.XmlRpcRequestProcessor; import org.apache.xmlrpc.XmlRpcServerRequest; +import org.apache.commons.logging.Log; /** * This class does the work for incoming requests. It holds a transactor thread @@ -79,6 +80,10 @@ public final class RequestEvaluator implements Runnable { // the exception thrown by the evaluator, if any. private volatile Exception exception; + // For numbering threads. + private int threadId; + + /** * Create a new RequestEvaluator for this application. * @param app the application @@ -155,6 +160,12 @@ public final class RequestEvaluator implements Runnable { // request path object RequestPath requestPath = new RequestPath(app); + String txname = req.getMethod().toLowerCase() + ":" + req.getPath(); + Log eventLog = app.getEventLog(); + if (eventLog.isDebugEnabled()) { + eventLog.debug(txname + " starting"); + } + int tries = 0; boolean done = false; Throwable error = null; @@ -198,14 +209,14 @@ public final class RequestEvaluator implements Runnable { throw new IllegalStateException("No function name in non-internal request "); } - // Transaction name is used for logging etc. - StringBuffer txname = new StringBuffer(app.getName()); - txname.append(":").append(req.getMethod().toLowerCase()).append(":"); - txname.append((error == null) ? req.getPath() : "error"); + // Update transaction name in case we're processing an error + if (error != null) { + txname = "error:" + txname; + } // begin transaction transactor = Transactor.getInstance(app.nmgr); - transactor.begin(txname.toString()); + transactor.begin(txname); Object root = app.getDataRoot(); initGlobals(root, requestPath); @@ -398,6 +409,7 @@ public final class RequestEvaluator implements Runnable { ScriptingEngine.ARGS_WRAP_XMLRPC, false); res.writeXmlRpcResponse(result); + app.xmlrpcCount += 1; } else { scriptingEngine.invoke(currentElement, actionProcessor, @@ -478,7 +490,7 @@ public final class RequestEvaluator implements Runnable { return; } abortTransaction(); - app.logError(txname + ": " + error, x); + app.logError(txname + " " + error, x); // If the transactor thread has been killed by the invoker thread we don't have to // bother for the error message, just quit. @@ -514,7 +526,7 @@ public final class RequestEvaluator implements Runnable { return; } abortTransaction(); - app.logError(txname + ": " + error, x); + app.logError(txname + " " + error, x); // If the transactor thread has been killed by the invoker thread we don't have to // bother for the error message, just quit. @@ -598,9 +610,7 @@ public final class RequestEvaluator implements Runnable { done = false; error = x; - Transactor tx = Transactor.getInstance(); - String txname = tx == null ? "no-txn" : tx.getTransactionName(); - app.logError(txname + ": " + error, x); + app.logError(txname + " " + error, x); if (req.isXmlRpc()) { // if it's an XML-RPC exception immediately generate error response @@ -619,8 +629,9 @@ public final class RequestEvaluator implements Runnable { } finally { app.setCurrentRequestEvaluator(null); // exit execution context - if (scriptingEngine != null) + if (scriptingEngine != null) { scriptingEngine.exitContext(); + } } } @@ -667,7 +678,7 @@ public final class RequestEvaluator implements Runnable { if ((thread == null) || !thread.isAlive()) { // app.logEvent ("Starting Thread"); - thread = new Thread(app.threadgroup, this); + thread = new Thread(app.threadgroup, this, app.getName() + "-" + (++threadId)); thread.setContextClassLoader(app.getClassLoader()); thread.start(); } else { @@ -783,7 +794,7 @@ public final class RequestEvaluator implements Runnable { // Get a reference to the res object at the time we enter ResponseTrans localRes = res; - if ((localRes == null) || !req.equals(this.req)) { + if (localRes == null || !req.equals(this.req)) { return null; } diff --git a/src/helma/framework/core/Session.java b/src/helma/framework/core/Session.java index 8addc5a9..8bb84de4 100644 --- a/src/helma/framework/core/Session.java +++ b/src/helma/framework/core/Session.java @@ -32,6 +32,8 @@ import java.util.*; */ public class Session implements Serializable { + static final long serialVersionUID = -6149094040363012913L; + transient protected Application app; protected String sessionId; diff --git a/src/helma/framework/core/Skin.java b/src/helma/framework/core/Skin.java index 61b3712e..8bef294f 100644 --- a/src/helma/framework/core/Skin.java +++ b/src/helma/framework/core/Skin.java @@ -437,13 +437,8 @@ public final class Skin { if (state == PARSE_MACRONAME && "//".equals(b.toString())) { isCommentMacro = true; - // search macro end tag - while (i < length - 1 && - (source[i] != '%' || source[i + 1] != '>')) { - i++; - } - state = PARSE_DONE; - break loop; + // just continue parsing the macro as this is the only way + // to correctly catch embedded macros - see bug 588 } break; @@ -639,7 +634,7 @@ public final class Skin { } if ((sandbox != null) && !sandbox.contains(name)) { - throw new RuntimeException("Macro " + name + " not allowed in sandbox"); + throw new MacroException("Macro not allowed in sandbox: " + name); } Object handler = null; @@ -705,7 +700,7 @@ public final class Skin { buffer.setLength(bufLength); } } else if (standardParams.verboseFailmode(handler, engine)) { - throw new UnhandledMacroException(name); + throw new MacroException("Unhandled macro: " + name); } } else { value = engine.getProperty(handler, propName); @@ -713,7 +708,7 @@ public final class Skin { return filter(value, cx); } } else if (standardParams.verboseFailmode(handler, engine)) { - throw new UnhandledMacroException(name); + throw new MacroException("Unhandled macro: " + name); } return filter(null, cx); } @@ -786,8 +781,8 @@ public final class Skin { throw concur; } catch (TimeoutException timeout) { throw timeout; - } catch (UnhandledMacroException unhandled) { - String msg = "Unhandled Macro: " + unhandled.getMessage(); + } catch (MacroException mx) { + String msg = mx.getMessage(); cx.reval.getResponse().write(" [" + msg + "] "); app.logError(msg); } catch (Exception x) { @@ -816,9 +811,9 @@ public final class Skin { throws Exception { if (name == null) { - throw new RuntimeException("Empty macro filter"); + throw new MacroException("Empty macro filter"); } else if (sandbox != null && !sandbox.contains(name)) { - throw new RuntimeException("Macro " + name + " not allowed in sandbox"); + throw new MacroException("Macro not allowed in sandbox: " + name); } Object handlerObject = null; @@ -840,7 +835,7 @@ public final class Skin { return filter(retval, cx); } else { - throw new RuntimeException("Undefined Filter " + name); + throw new MacroException("Undefined macro filter: " + name); } } @@ -1103,9 +1098,9 @@ public final class Skin { // limiting to 50 passes to avoid infinite loops int maxloop = 50; while (obj != null && maxloop-- > 0) { - Prototype proto = app.getPrototype(obj); + String protoName = app.getPrototypeName(obj); - if ((proto != null) && proto.isInstanceOf(handlerName)) { + if (handlerName.equalsIgnoreCase(protoName)) { if (handlerCache != null) handlerCache.put(handlerName, obj); return obj; @@ -1126,12 +1121,13 @@ public final class Skin { } /** - * Exception type for unhandled macros + * Exception type for unhandled, forbidden or failed macros */ - class UnhandledMacroException extends Exception { - UnhandledMacroException(String name) { - super(name); + class MacroException extends Exception { + MacroException(String message) { + super(message); } } + } diff --git a/src/helma/framework/core/TypeManager.java b/src/helma/framework/core/TypeManager.java index ad7809f3..818a250b 100644 --- a/src/helma/framework/core/TypeManager.java +++ b/src/helma/framework/core/TypeManager.java @@ -101,7 +101,7 @@ public final class TypeManager { * Run through application's prototype directories and create prototypes, but don't * compile or evaluate any scripts. */ - public void createPrototypes() throws IOException { + public synchronized void createPrototypes() throws IOException { // create standard prototypes. for (int i = 0; i < standardTypes.length; i++) { createPrototype(standardTypes[i], null); @@ -126,7 +126,7 @@ public final class TypeManager { lastCheck = System.currentTimeMillis(); } - protected void checkRepository(Repository repository, boolean update) throws IOException { + protected synchronized void checkRepository(Repository repository, boolean update) throws IOException { Repository[] list = repository.getRepositories(); for (int i = 0; i < list.length; i++) { @@ -183,7 +183,7 @@ public final class TypeManager { * Run through application's prototype sources and check if * there are any prototypes to be created. */ - private void checkRepositories() throws IOException { + private synchronized void checkRepositories() throws IOException { List list = app.getRepositories(); // walk through repositories and check if any of them have changed. @@ -197,12 +197,21 @@ public final class TypeManager { } } + boolean debug = "true".equalsIgnoreCase(app.getProperty("helma.debugTypeManager")); + if (debug) { + System.err.println("Starting CHECK loop in " + Thread.currentThread()); + } + // loop through prototypes and check if type.properties needs updates // it's important that we do this _after_ potentially new prototypes // have been created in the previous loop. for (Iterator i = prototypes.values().iterator(); i.hasNext();) { Prototype proto = (Prototype) i.next(); + if (debug) { + System.err.println("CHECK: " + proto.getName() + " in " + Thread.currentThread()); + } + // update prototype's type mapping DbMapping dbmap = proto.getDbMapping(); @@ -216,6 +225,9 @@ public final class TypeManager { dbmap.update(); } } + if (debug) { + System.err.println("Finished CHECK in " + Thread.currentThread()); + } } private boolean isValidTypeName(String str) { @@ -263,14 +275,14 @@ public final class TypeManager { * * @return a collection containing the prototypes */ - public Collection getPrototypes() { + public synchronized Collection getPrototypes() { return Collections.unmodifiableCollection(prototypes.values()); } /** * Get a prototype defined for this application */ - public Prototype getPrototype(String typename) { + public synchronized Prototype getPrototype(String typename) { if (typename == null) { return null; } @@ -284,12 +296,14 @@ public final class TypeManager { * @param repository the first prototype source * @return the newly created prototype */ - public Prototype createPrototype(String typename, Repository repository) { + public synchronized Prototype createPrototype(String typename, Repository repository) { + if ("true".equalsIgnoreCase(app.getProperty("helma.debugTypeManager"))) { + System.err.println("CREATE: " + typename + " from " + repository + " in " + Thread.currentThread()); + // Thread.dumpStack(); + } Prototype proto = new Prototype(typename, repository, app); - // put the prototype into our map prototypes.put(proto.getLowerCaseName(), proto); - return proto; } diff --git a/src/helma/main/ApplicationManager.java b/src/helma/main/ApplicationManager.java index a690eb1a..a1716867 100644 --- a/src/helma/main/ApplicationManager.java +++ b/src/helma/main/ApplicationManager.java @@ -281,6 +281,14 @@ public class ApplicationManager implements XmlRpcHandler { return server.getLogger(); } + private String findResource(String path) { + File file = new File(path); + if (!file.isAbsolute() && !file.exists()) { + file = new File(server.getHopHome(), path); + } + return file.getAbsolutePath(); + } + /** * Inner class that describes an application and its start settings. */ @@ -350,7 +358,7 @@ public class ApplicationManager implements XmlRpcHandler { ignoreDirs = conf.getProperty("ignore"); // read and configure app repositories - ArrayList repositoryList = new ArrayList(); + ArrayList repositoryList = new ArrayList(); Class[] parameters = { String.class }; for (int i = 0; true; i++) { String repositoryArgs = conf.getProperty("repository." + i); @@ -362,10 +370,13 @@ public class ApplicationManager implements XmlRpcHandler { if (repositoryImpl == null) { // implementation not set manually, have to guess it if (repositoryArgs.endsWith(".zip")) { + repositoryArgs = findResource(repositoryArgs); repositoryImpl = "helma.framework.repository.ZipRepository"; } else if (repositoryArgs.endsWith(".js")) { + repositoryArgs = findResource(repositoryArgs); repositoryImpl = "helma.framework.repository.SingleFileRepository"; } else { + repositoryArgs = findResource(repositoryArgs); repositoryImpl = "helma.framework.repository.FileRepository"; } } @@ -373,7 +384,7 @@ public class ApplicationManager implements XmlRpcHandler { try { Repository newRepository = (Repository) Class.forName(repositoryImpl) .getConstructor(parameters) - .newInstance(repositoryArgs); + .newInstance(new Object[] {repositoryArgs}); repositoryList.add(newRepository); } catch (Exception ex) { getLogger().error("Adding repository " + repositoryArgs + " failed. " + @@ -397,7 +408,7 @@ public class ApplicationManager implements XmlRpcHandler { new File(server.getAppsHome(), appName))); } repositories = new Repository[repositoryList.size()]; - repositories = repositoryList.toArray(repositories); + repositories = (Repository[]) repositoryList.toArray(repositories); } diff --git a/src/helma/main/Server.java b/src/helma/main/Server.java index 91bcfb96..2f7985b8 100644 --- a/src/helma/main/Server.java +++ b/src/helma/main/Server.java @@ -32,10 +32,6 @@ import java.io.*; import java.rmi.registry.*; import java.rmi.server.*; import java.util.*; -import java.net.Socket; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.net.InetSocketAddress; import helma.util.ResourceProperties; @@ -44,7 +40,7 @@ import helma.util.ResourceProperties; */ public class Server implements Runnable { // version string - public static final String version = "1.6.2 (__builddate__)"; + public static final String version = "1.6.3 (__builddate__)"; // static server instance private static Server server; @@ -109,7 +105,9 @@ public class Server implements Runnable { // create system properties sysProps = new ResourceProperties(); - sysProps.addResource(new FileResource(config.getPropFile())); + if (config.hasPropFile()) { + sysProps.addResource(new FileResource(config.getPropFile())); + } } @@ -304,13 +302,6 @@ public class Server implements Runnable { if (!config.hasHomeDir()) { throw new Exception ("couldn't determine helma directory"); } - - // try to transform hopHome directory to its canonical representation - try { - config.setHomeDir(config.getHomeDir().getCanonicalFile()); - } catch (IOException iox) { - config.setHomeDir(config.getHomeDir().getAbsoluteFile()); - } } diff --git a/src/helma/main/ServerConfig.java b/src/helma/main/ServerConfig.java index 6d0ebf7e..9217f868 100644 --- a/src/helma/main/ServerConfig.java +++ b/src/helma/main/ServerConfig.java @@ -93,7 +93,7 @@ public class ServerConfig { } public void setPropFile(File propFile) { - this.propFile = propFile; + this.propFile = propFile == null ? null : propFile.getAbsoluteFile(); } public File getHomeDir() { @@ -101,6 +101,6 @@ public class ServerConfig { } public void setHomeDir(File homeDir) { - this.homeDir = homeDir; + this.homeDir = homeDir == null ? null : homeDir.getAbsoluteFile(); } } diff --git a/src/helma/objectmodel/TransientNode.java b/src/helma/objectmodel/TransientNode.java index f50c250b..a4f5c004 100644 --- a/src/helma/objectmodel/TransientNode.java +++ b/src/helma/objectmodel/TransientNode.java @@ -19,6 +19,7 @@ package helma.objectmodel; import helma.framework.IPathElement; import helma.objectmodel.db.DbMapping; import helma.objectmodel.db.Relation; +import helma.objectmodel.db.Node; import helma.util.*; import java.io.*; import java.util.Date; @@ -588,7 +589,7 @@ public class TransientNode implements INode, Serializable { } private Property makeVirtualNode(String propname, Relation rel) { - INode node = new helma.objectmodel.db.Node(rel.getPropName(), rel.getPrototype(), + INode node = new Node(rel.getPropName(), rel.getPrototype(), dbmap.getWrappedNodeManager()); // node.setState (TRANSIENT); diff --git a/src/helma/objectmodel/db/DbMapping.java b/src/helma/objectmodel/db/DbMapping.java index 4aa9ff58..d469752c 100644 --- a/src/helma/objectmodel/db/DbMapping.java +++ b/src/helma/objectmodel/db/DbMapping.java @@ -124,7 +124,10 @@ public final class DbMapping { HashSet dependentMappings = new HashSet(); // does this DbMapping describe a virtual node (collection, mountpoint, groupnode)? - private boolean virtual = false; + private boolean isVirtual = false; + + // does this Dbmapping describe a group node? + private boolean isGroup = false; /** * Create an internal DbMapping used for "virtual" mappings aka collections, mountpoints etc. @@ -132,7 +135,7 @@ public final class DbMapping { public DbMapping(Application app, String parentTypeName) { this(app, parentTypeName, null); // DbMappings created with this constructor always define virtual nodes - virtual = true; + isVirtual = true; if (parentTypeName != null) { parentMapping = app.getDbMapping(parentTypeName); if (parentMapping == null) { @@ -311,9 +314,6 @@ public final class DbMapping { } rel.update(dbField, props); - - // store relation with lower case property name - // (ResourceProperties now preserve key capitalization!) p2d.put(propName.toLowerCase(), rel); if ((rel.columnName != null) && rel.isPrimitiveOrReference()) { @@ -756,9 +756,9 @@ public final class DbMapping { * db-mapping with the right relations to create the group-by nodes */ public synchronized DbMapping getGroupbyMapping() { - if ((subRelation == null) && (parentMapping != null)) { + if ((subRelation == null) && (parentMapping != null)) { return parentMapping.getGroupbyMapping(); - } else if (subRelation.groupby == null) { + } else if (subRelation == null || subRelation.groupby == null) { return null; } else if (groupbyMapping == null) { initGroupbyMapping(); @@ -774,6 +774,7 @@ public final class DbMapping { // if a prototype is defined for groupby nodes, use that // if mapping doesn' exist or isn't defined, create a new (anonymous internal) one groupbyMapping = new DbMapping(app, subRelation.groupbyPrototype); + groupbyMapping.isGroup = true; // set subnode and property relations groupbyMapping.subRelation = subRelation.getGroupbySubnodeRelation(); @@ -1547,7 +1548,7 @@ public final class DbMapping { * a utility method to escape single quotes used for inserting * string-values into relational databases. * Searches for "'" characters and escapes them by duplicating them (= "''") - * @param str the string to escape + * @param value the string to escape * @return the escaped string */ static String escapeString(Object value) { @@ -1574,7 +1575,7 @@ public final class DbMapping { /** * Utility method to check whether the argument is a number literal. - * @param str a string representing a number literal + * @param value a string representing a number literal * @return the argument, if it conforms to the number literal syntax * @throws IllegalArgumentException if the argument does not represent a number */ @@ -1596,6 +1597,14 @@ public final class DbMapping { * @return true if this instance describes a virtual node. */ public boolean isVirtual() { - return virtual; + return isVirtual; + } + + /** + * Find if this DbMapping describes a group node. + * @return true if this instance describes a group node. + */ + public boolean isGroup() { + return isGroup; } } \ No newline at end of file diff --git a/src/helma/objectmodel/db/Node.java b/src/helma/objectmodel/db/Node.java index 12006247..b08f784c 100644 --- a/src/helma/objectmodel/db/Node.java +++ b/src/helma/objectmodel/db/Node.java @@ -869,26 +869,10 @@ public final class Node implements INode, Serializable { loadNodes(); // check if this node has a group-by subnode-relation - if (dbmap != null) { - Relation srel = dbmap.getSubnodeRelation(); - - if ((srel != null) && (srel.groupby != null)) { - Relation groupbyRel = srel.otherType.columnNameToRelation(srel.groupby); - String groupbyProp = (groupbyRel != null) ? groupbyRel.propName - : srel.groupby; - String groupbyValue = node.getString(groupbyProp); - INode groupbyNode = (INode) getChildElement(groupbyValue); - - // if group-by node doesn't exist, we'll create it - if (groupbyNode == null) { - groupbyNode = getGroupbySubnode(groupbyValue, true); - } else { - groupbyNode.setDbMapping(dbmap.getGroupbyMapping()); - } - - groupbyNode.addNode(node); - return node; - } + INode groupbyNode = getGroupbySubnode(node, true); + if (groupbyNode != null) { + groupbyNode.addNode(node); + return node; } NodeHandle nhandle = node.getHandle(); @@ -1198,6 +1182,38 @@ public final class Node implements INode, Serializable { return retval; } + protected Node getGroupbySubnode(Node node, boolean create) { + if (node.dbmap != null && node.dbmap.isGroup()) { + return null; + } + + if (dbmap != null) { + Relation srel = dbmap.getSubnodeRelation(); + + if ((srel != null) && (srel.groupby != null)) { + Relation groupbyRel = srel.otherType.columnNameToRelation(srel.groupby); + String groupbyProp = (groupbyRel != null) ? groupbyRel.propName + : srel.groupby; + String groupbyValue = node.getString(groupbyProp); + Node groupbyNode = (Node) getChildElement(groupbyValue); + + // if group-by node doesn't exist, we'll create it + if (groupbyNode == null) { + groupbyNode = getGroupbySubnode(groupbyValue, create); + // mark subnodes as changed as we have a new group node + if (create && groupbyNode != null) { + Transactor.getInstance().visitParentNode(this); + } + } else { + groupbyNode.setDbMapping(dbmap.getGroupbyMapping()); + } + + return groupbyNode; + } + } + return null; + } + /** * * @@ -1211,10 +1227,7 @@ public final class Node implements INode, Serializable { throw new IllegalArgumentException("Can't create group by null"); } - if (state == TRANSIENT) { - throw new RuntimeException("Can't add grouped child on transient node. "+ - "Make parent persistent before adding grouped nodes."); - } + boolean persistent = state != TRANSIENT; loadNodes(); @@ -1228,34 +1241,44 @@ public final class Node implements INode, Serializable { boolean relational = groupbyMapping.getSubnodeMapping().isRelational(); if (relational || create) { - Node node = relational ? new Node(this, sid, nmgr, null) - : new Node(sid, null, nmgr); + Node node; + if (relational && persistent) { + node = new Node(this, sid, nmgr, null); + } else { + node = new Node(sid, null, nmgr); + node.setParent(this); + } // set "groupname" property to value of groupby field node.setString("groupname", sid); - + // Set the dbmapping on the group node node.setDbMapping(groupbyMapping); + node.setPrototype(groupbyMapping.getTypeName()); - if (!relational) { - // if we're not transient, make new node persistable - if (state != TRANSIENT) { - node.makePersistable(); - node.checkWriteLock(); - } - subnodes.add(node.getHandle()); + // if we're relational and persistent, make new node persistable + if (!relational && persistent) { + node.makePersistable(); + node.checkWriteLock(); + } + + // if we created a new node, check if we need to add it to subnodes + if (create) { + NodeHandle handle = node.getHandle(); + if (!subnodes.contains(handle)) + subnodes.add(handle); } - // Set the dbmapping on the group node - node.setPrototype(groupbyMapping.getTypeName()); // If we created the group node, we register it with the // nodemanager. Otherwise, we just evict whatever was there before - if (create) { - // register group node with transactor - Transactor tx = Transactor.getInstanceOrFail(); - tx.visitCleanNode(node); - nmgr.registerNode(node); - } else { - nmgr.evictKey(node.getKey()); + if (persistent) { + if (create) { + // register group node with transactor + Transactor tx = Transactor.getInstanceOrFail(); + tx.visitCleanNode(node); + nmgr.registerNode(node); + } else { + nmgr.evictKey(node.getKey()); + } } return node; @@ -1299,6 +1322,13 @@ public final class Node implements INode, Serializable { * {@link #removeNode(INode)}. */ protected void releaseNode(Node node) { + + Node groupNode = getGroupbySubnode(node, false); + if (groupNode != null) { + groupNode.releaseNode(node); + return; + } + INode parent = node.getParent(); checkWriteLock(); @@ -1314,7 +1344,9 @@ public final class Node implements INode, Serializable { synchronized (subnodes) { removed = subnodes.remove(node.getHandle()); } - if (removed) { + if (dbmap != null && dbmap.isGroup() && subnodes.size() == 0) { + remove(); + } else if (removed) { registerSubnodeChange(); } } @@ -1341,6 +1373,12 @@ public final class Node implements INode, Serializable { nmgr.evictKey(new SyntheticKey(getKey(), prop)); } } + } else if (prel.groupby != null) { + String prop = node.getString("groupname"); + if (prop != null && state != TRANSIENT) { + nmgr.evictKey(new SyntheticKey(getKey(), prop)); + } + } // TODO: We should unset constraints to actually remove subnodes here, // but omit it by convention and to keep backwards compatible. @@ -2443,7 +2481,7 @@ public final class Node implements INode, Serializable { lastmodified = System.currentTimeMillis(); - if (state == CLEAN) { + if (state == CLEAN && isPersistableProperty(propname)) { markAs(MODIFIED); } } else if (dbmap != null) { diff --git a/src/helma/objectmodel/db/OrderedSubnodeList.java b/src/helma/objectmodel/db/OrderedSubnodeList.java index 2876c93f..cb9a5d71 100644 --- a/src/helma/objectmodel/db/OrderedSubnodeList.java +++ b/src/helma/objectmodel/db/OrderedSubnodeList.java @@ -381,7 +381,7 @@ public class OrderedSubnodeList extends SubnodeList { return 0; } - public List getOrderedView (String order) { + public SubnodeList getOrderedView (String order) { if (origin != null) { return origin.getOrderedView(order); } else { diff --git a/src/helma/objectmodel/db/SubnodeList.java b/src/helma/objectmodel/db/SubnodeList.java index 7218386d..1de67312 100644 --- a/src/helma/objectmodel/db/SubnodeList.java +++ b/src/helma/objectmodel/db/SubnodeList.java @@ -104,7 +104,7 @@ public class SubnodeList extends ArrayList { } } - public List getOrderedView (String order) { + public SubnodeList getOrderedView (String order) { String key = order.trim().toLowerCase(); // long start = System.currentTimeMillis(); if (views == null) { diff --git a/src/helma/objectmodel/db/Transactor.java b/src/helma/objectmodel/db/Transactor.java index 0843b21a..a8d457e6 100644 --- a/src/helma/objectmodel/db/Transactor.java +++ b/src/helma/objectmodel/db/Transactor.java @@ -24,6 +24,8 @@ import java.sql.Statement; import java.sql.SQLException; import java.util.*; +import org.apache.commons.logging.Log; + /** * A subclass of thread that keeps track of changed nodes and triggers * changes in the database when a transaction is commited. @@ -34,13 +36,13 @@ public class Transactor { NodeManager nmgr; // List of nodes to be updated - private HashMap dirtyNodes; + private Map dirtyNodes; // List of visited clean nodes - private HashMap cleanNodes; + private Map cleanNodes; // List of nodes whose child index has been modified - private HashSet parentNodes; + private Set parentNodes; // Is a transaction in progress? private volatile boolean active; @@ -50,10 +52,10 @@ public class Transactor { protected ITransaction txn; // Transactions for SQL data sources - private HashMap sqlConnections; + private Map sqlConnections; // Set of SQL connections that already have been verified - private HashSet testedConnections; + private Map testedConnections; // when did the current transaction start? private long tstart; @@ -64,7 +66,7 @@ public class Transactor { // the thread we're associated with private Thread thread; - private static final ThreadLocal txtor = new ThreadLocal (); + private static final ThreadLocal txtor = new ThreadLocal(); /** * Creates a new Transactor object. @@ -75,12 +77,12 @@ public class Transactor { this.thread = Thread.currentThread(); this.nmgr = nmgr; - dirtyNodes = new HashMap(); + dirtyNodes = new LinkedHashMap(); cleanNodes = new HashMap(); parentNodes = new HashSet(); sqlConnections = new HashMap(); - testedConnections = new HashSet(); + testedConnections = new HashMap(); active = false; killed = false; } @@ -90,7 +92,7 @@ public class Transactor { * @return the transactor associated with the current thread */ public static Transactor getInstance() { - return txtor.get(); + return (Transactor) txtor.get(); } /** @@ -99,7 +101,7 @@ public class Transactor { * @throws IllegalStateException if no transactor is associated with the current thread */ public static Transactor getInstanceOrFail() throws IllegalStateException { - Transactor tx = txtor.get(); + Transactor tx = (Transactor) txtor.get(); if (tx == null) throw new IllegalStateException("Operation requires a Transactor, " + "but current thread does not have one."); @@ -112,7 +114,7 @@ public class Transactor { * @return the transactor associated with the current thread */ public static Transactor getInstance(NodeManager nmgr) { - Transactor t = txtor.get(); + Transactor t = (Transactor) txtor.get(); if (t == null) { t = new Transactor(nmgr); txtor.set(t); @@ -240,7 +242,7 @@ public class Transactor { public void registerConnection(DbSource src, Connection con) { sqlConnections.put(src, con); // we assume a freshly created connection is ok. - testedConnections.add(src); + testedConnections.put(src, new Long(System.currentTimeMillis())); } /** @@ -250,13 +252,15 @@ public class Transactor { */ public Connection getConnection(DbSource src) { Connection con = (Connection) sqlConnections.get(src); - if (con != null && !testedConnections.contains(src)) { + Long tested = (Long) testedConnections.get(src); + long now = System.currentTimeMillis(); + if (con != null && (tested == null || now - tested.longValue() > 10000)) { // Check if the connection is still alive by executing a simple statement. try { Statement stmt = con.createStatement(); stmt.execute("SELECT 1"); stmt.close(); - testedConnections.add(src); + testedConnections.put(src, new Long(now)); } catch (SQLException sx) { try { con.close(); @@ -326,6 +330,7 @@ public class Transactor { // the set to collect DbMappings to be marked as changed HashSet dirtyDbMappings = new HashSet(); + Log eventLog = nmgr.app.getEventLog(); for (int i = 0; i < dirty.length; i++) { Node node = (Node) dirty[i]; @@ -346,8 +351,10 @@ public class Transactor { } inserted++; - nmgr.app.logEvent("inserted: Node " + node.getPrototype() + "/" + - node.getID()); + if (eventLog.isDebugEnabled()) { + eventLog.debug("inserted node: " + node.getPrototype() + "/" + + node.getID()); + } } else if (nstate == Node.MODIFIED) { // only mark DbMapping as dirty if updateNode returns true if (nmgr.updateNode(nmgr.db, txn, node)) { @@ -363,8 +370,10 @@ public class Transactor { } updated++; - nmgr.app.logEvent("updated: Node " + node.getPrototype() + "/" + - node.getID()); + if (eventLog.isDebugEnabled()) { + eventLog.debug("updated node: " + node.getPrototype() + "/" + + node.getID()); + } } else if (nstate == Node.DELETED) { nmgr.deleteNode(nmgr.db, txn, node); dirtyDbMappings.add(node.getDbMapping()); @@ -377,6 +386,10 @@ public class Transactor { } deleted++; + if (eventLog.isDebugEnabled()) { + eventLog.debug("removed node: " + node.getPrototype() + "/" + + node.getID()); + } } node.clearWriteLock(); @@ -419,10 +432,15 @@ public class Transactor { txn = null; } - nmgr.app.logAccess(tname + " " + inserted + - " inserted, " + updated + - " updated, " + deleted + " deleted in " + - (now - tstart) + " millis"); + StringBuffer msg = new StringBuffer(tname).append(" done in ") + .append(now - tstart).append(" millis"); + if(inserted + updated + deleted > 0) { + msg.append(" [+") + .append(inserted).append(", ~") + .append(updated).append(", -") + .append(deleted).append("]"); + } + nmgr.app.logAccess(msg.toString()); // unset transaction name tname = null; diff --git a/src/helma/objectmodel/dom/XmlWriter.java b/src/helma/objectmodel/dom/XmlWriter.java index 15691356..e73b7954 100644 --- a/src/helma/objectmodel/dom/XmlWriter.java +++ b/src/helma/objectmodel/dom/XmlWriter.java @@ -268,7 +268,7 @@ public class XmlWriter extends OutputStreamWriter implements XmlConstants { throws IOException { Enumeration e = null; - if (dbmode && node instanceof helma.objectmodel.db.Node) { + if (dbmode && node instanceof Node) { // a newly constructed db.Node doesn't have a propMap, // but returns an enumeration of all it's db-mapped properties Hashtable props = ((Node) node).getPropMap(); @@ -392,7 +392,7 @@ public class XmlWriter extends OutputStreamWriter implements XmlConstants { * loop through the children-array and print them as */ private void writeChildren(INode node, int level) throws IOException { - if (dbmode && node instanceof helma.objectmodel.db.Node) { + if (dbmode && node instanceof Node) { Node dbNode = (Node) node; DbMapping smap = (dbNode.getDbMapping() == null) ? null : dbNode.getDbMapping() diff --git a/src/helma/scripting/rhino/HopObject.java b/src/helma/scripting/rhino/HopObject.java index 8300e937..9b6319c7 100644 --- a/src/helma/scripting/rhino/HopObject.java +++ b/src/helma/scripting/rhino/HopObject.java @@ -20,6 +20,7 @@ import helma.framework.core.*; import helma.framework.repository.Resource; import helma.objectmodel.*; import helma.objectmodel.db.*; +import helma.objectmodel.db.Node; import org.mozilla.javascript.*; import java.lang.reflect.Method; @@ -170,13 +171,13 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco */ private void checkNode() { if (node != null && node.getState() == INode.INVALID) { - if (node instanceof helma.objectmodel.db.Node) { - NodeHandle handle = ((helma.objectmodel.db.Node) node).getHandle(); + if (node instanceof Node) { + NodeHandle handle = ((Node) node).getHandle(); node = handle.getNode(core.app.getWrappedNodeManager()); if (node == null) { // we probably have a deleted node. Replace with empty transient node // to avoid throwing an exception. - node = new helma.objectmodel.TransientNode(); + node = new TransientNode(); // throw new RuntimeException("Tried to access invalid/removed node " + handle + "."); } } @@ -455,14 +456,14 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco } private void prefetchChildren(int start, int length) { - if (!(node instanceof helma.objectmodel.db.Node)) { + if (!(node instanceof Node)) { return; } checkNode(); try { - ((helma.objectmodel.db.Node) node).prefetchChildren(start, length); + ((Node) node).prefetchChildren(start, length); } catch (Exception x) { core.app.logError("Error in HopObject.prefetchChildren: " + x, x); } @@ -638,8 +639,8 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco checkNode(); - if (node instanceof helma.objectmodel.db.Node) { - ((helma.objectmodel.db.Node) node).persist(); + if (node instanceof Node) { + ((Node) node).persist(); return node.getID(); } return null; @@ -649,19 +650,19 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * Invalidate the node itself or a subnode */ public boolean jsFunction_invalidate(Object childId) { - if (childId != null && node instanceof helma.objectmodel.db.Node) { + if (childId != null && node instanceof Node) { if (childId == Undefined.instance) { if (node.getState() == INode.INVALID) { return true; } - ((helma.objectmodel.db.Node) node).invalidate(); + ((Node) node).invalidate(); } else { checkNode(); - ((helma.objectmodel.db.Node) node).invalidateNode(childId.toString()); + ((Node) node).invalidateNode(childId.toString()); } } @@ -675,7 +676,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * @return true if the the wrapped Node has a valid database id. */ public boolean jsFunction_isPersistent() { - if (!(node instanceof helma.objectmodel.db.Node)) { + if (!(node instanceof Node)) { return false; } checkNode(); @@ -690,7 +691,7 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * @return true if the the wrapped Node is not stored in a database. */ public boolean jsFunction_isTransient() { - if (!(node instanceof helma.objectmodel.db.Node)) { + if (!(node instanceof Node)) { return true; } checkNode(); @@ -1096,10 +1097,10 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * do have a higher id than the last record loaded by this collection */ public int jsFunction_update() { - if (!(node instanceof helma.objectmodel.db.Node)) + if (!(node instanceof Node)) throw new RuntimeException ("update only callabel on persistent HopObjects"); checkNode(); - helma.objectmodel.db.Node n = (helma.objectmodel.db.Node) node; + Node n = (Node) node; return n.updateSubnodes(); } @@ -1111,18 +1112,19 @@ public class HopObject extends ScriptableObject implements Wrapper, PropertyReco * @return ListViewWrapper holding the information of the ordered view */ public Object jsFunction_getOrderedView(String expr) { - if (!(node instanceof helma.objectmodel.db.Node)) { + if (!(node instanceof Node)) { throw new RuntimeException ( "getOrderedView only callable on persistent HopObjects"); } - helma.objectmodel.db.Node n = (helma.objectmodel.db.Node) node; + Node n = (Node) node; n.loadNodes(); SubnodeList subnodes = n.getSubnodeList(); if (subnodes == null) { throw new RuntimeException ( "getOrderedView only callable on already existing subnode-collections"); } - return new ListViewWrapper (subnodes.getOrderedView(expr), - core, core.app.getWrappedNodeManager(), this); + Node subnode = new Node("OrderedView", "HopObject", core.app.getWrappedNodeManager()); + subnode.setSubnodes(subnodes.getOrderedView(expr)); + return new HopObject("HopObject", core, subnode, core.getPrototype("HopObject")); } } diff --git a/src/helma/scripting/rhino/HopObjectCtor.java b/src/helma/scripting/rhino/HopObjectCtor.java index e8f71a3f..1f00ce61 100644 --- a/src/helma/scripting/rhino/HopObjectCtor.java +++ b/src/helma/scripting/rhino/HopObjectCtor.java @@ -15,14 +15,15 @@ */ package helma.scripting.rhino; -import org.mozilla.javascript.*; - import java.lang.reflect.Constructor; import java.lang.reflect.Method; import helma.objectmodel.INode; import helma.objectmodel.db.DbMapping; import helma.objectmodel.db.DbKey; +import helma.objectmodel.db.Node; + +import org.mozilla.javascript.*; public class HopObjectCtor extends FunctionObject { @@ -89,8 +90,8 @@ public class HopObjectCtor extends FunctionObject { throw new EvaluatorException(x.toString()); } } else { - INode node = new helma.objectmodel.db.Node(protoname, protoname, - core.app.getWrappedNodeManager()); + INode node = new Node(protoname, protoname, + core.app.getWrappedNodeManager()); Scriptable proto = core.getPrototype(protoname); HopObject hobj = new HopObject(protoname, core, node, proto); diff --git a/src/helma/scripting/rhino/ListViewWrapper.java b/src/helma/scripting/rhino/ListViewWrapper.java deleted file mode 100644 index 66e006de..00000000 --- a/src/helma/scripting/rhino/ListViewWrapper.java +++ /dev/null @@ -1,337 +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.rhino; - -import helma.objectmodel.INode; -import helma.objectmodel.db.Key; -import helma.objectmodel.db.NodeHandle; -import helma.objectmodel.db.OrderedSubnodeList; -import helma.objectmodel.db.WrappedNodeManager; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import org.mozilla.javascript.Context; -import org.mozilla.javascript.EvaluatorException; -import org.mozilla.javascript.FunctionObject; -import org.mozilla.javascript.Scriptable; -import org.mozilla.javascript.ScriptableObject; -import org.mozilla.javascript.Undefined; -import org.mozilla.javascript.Wrapper; -import org.mozilla.javascript.ScriptRuntime; - - -public class ListViewWrapper extends ScriptableObject implements Wrapper, Scriptable { - final List list; - final RhinoCore core; - final WrappedNodeManager wnm; - final HopObject hObj; - INode node; - - static ListViewWrapper listViewProto; - - /** - * Private constructor used to create the object prototype. - */ - private ListViewWrapper() { - list = null; - core = null; - wnm = null; - node = null; - hObj = null; - } - - /** - * Create a JS wrapper around a subnode list. - * @param list - * @param core - * @param wnm - * @param hObj - */ - ListViewWrapper (List list, RhinoCore core, WrappedNodeManager wnm, HopObject hObj) { - if (list == null) { - throw new IllegalArgumentException ("ListWrapper unable to wrap null list."); - } - this.core = core; - this.list = list; - this.wnm = wnm; - this.hObj = hObj; - this.node = hObj.node; - if (listViewProto == null) { - listViewProto = new ListViewWrapper(); - listViewProto.init(); - } - setPrototype(listViewProto); - } - - /** - * Init JS functions from methods. - */ - void init() { - int attributes = DONTENUM | PERMANENT; - - Method[] methods = getClass().getDeclaredMethods(); - for (int i=0; i= 0xfffffff) - throw new EvaluatorException("Exceeded instruction count, interrupting"); - } */ + protected void observeInstructionCount(Context cx, int instructionCount) { + RhinoEngine engine = RhinoEngine.getRhinoEngine(); + if (engine != null && engine.thread != Thread.currentThread()) { + throw new EvaluatorException("Request timed out"); + } + } } } diff --git a/src/helma/scripting/rhino/RhinoEngine.java b/src/helma/scripting/rhino/RhinoEngine.java index 7d90d990..d55a0f44 100644 --- a/src/helma/scripting/rhino/RhinoEngine.java +++ b/src/helma/scripting/rhino/RhinoEngine.java @@ -26,6 +26,7 @@ import helma.main.Server; import helma.objectmodel.*; import helma.objectmodel.db.DbMapping; import helma.objectmodel.db.Relation; +import helma.objectmodel.db.Node; import helma.scripting.*; import helma.scripting.rhino.debug.Tracer; import helma.util.StringUtils; @@ -86,7 +87,7 @@ public class RhinoEngine implements ScriptingEngine { this.reval = reval; initRhinoCore(app); - context = core.contextFactory.enter(); + context = core.contextFactory.enterContext(); try { extensionGlobals = new HashMap(); @@ -113,7 +114,7 @@ public class RhinoEngine implements ScriptingEngine { app.logError("Cannot initialize interpreter", e); throw new RuntimeException(e.getMessage(), e); } finally { - core.contextFactory.exit (); + Context.exit(); } } @@ -162,7 +163,7 @@ public class RhinoEngine implements ScriptingEngine { // (chicken and egg problem, kind of) thread = Thread.currentThread(); global = new GlobalObject(core, app, true); - context = core.contextFactory.enter(); + context = core.contextFactory.enterContext(); if (core.hasTracer) { context.setDebugger(new Tracer(getResponse()), null); @@ -214,7 +215,7 @@ public class RhinoEngine implements ScriptingEngine { public synchronized void exitContext() { // unregister the engine threadlocal engines.set(null); - core.contextFactory.exit(); + Context.exit(); thread = null; global = null; } @@ -345,7 +346,7 @@ public class RhinoEngine implements ScriptingEngine { * Let the evaluator know that the current evaluation has been * aborted. */ - public void abort() { + public void abort() { // current request has been aborted. Thread t = thread; // set thread to null @@ -528,7 +529,7 @@ public class RhinoEngine implements ScriptingEngine { * @throws java.io.IOException */ public void serialize(Object obj, OutputStream out) throws IOException { - core.contextFactory.enter(); + core.contextFactory.enterContext(); engines.set(this); try { // use a special ScriptableOutputStream that unwraps Wrappers @@ -536,8 +537,8 @@ public class RhinoEngine implements ScriptingEngine { protected Object replaceObject(Object obj) throws IOException { if (obj instanceof HopObject) return new HopObjectProxy((HopObject) obj); - if (obj instanceof helma.objectmodel.db.Node) - return new HopObjectProxy((helma.objectmodel.db.Node) obj); + if (obj instanceof Node) + return new HopObjectProxy((Node) obj); if (obj instanceof GlobalObject) return new GlobalProxy((GlobalObject) obj); if (obj instanceof ApplicationBean) @@ -557,7 +558,7 @@ public class RhinoEngine implements ScriptingEngine { sout.writeObject(obj); sout.flush(); } finally { - core.contextFactory.exit(); + Context.exit(); } } @@ -571,7 +572,7 @@ public class RhinoEngine implements ScriptingEngine { * @throws java.io.IOException */ public Object deserialize(InputStream in) throws IOException, ClassNotFoundException { - core.contextFactory.enter(); + core.contextFactory.enterContext(); engines.set(this); try { ObjectInputStream sin = new ScriptableInputStream(in, core.global) { @@ -584,7 +585,7 @@ public class RhinoEngine implements ScriptingEngine { }; return sin.readObject(); } finally { - core.contextFactory.exit(); + Context.exit(); } } diff --git a/src/helma/scripting/rhino/SerializationProxy.java b/src/helma/scripting/rhino/SerializationProxy.java index c179d99f..ab7b72e5 100644 --- a/src/helma/scripting/rhino/SerializationProxy.java +++ b/src/helma/scripting/rhino/SerializationProxy.java @@ -18,6 +18,7 @@ package helma.scripting.rhino; import helma.objectmodel.INode; import helma.objectmodel.db.NodeHandle; +import helma.objectmodel.db.Node; import org.mozilla.javascript.Context; import java.io.Serializable; @@ -80,18 +81,19 @@ class HopObjectProxy implements SerializationProxy { HopObjectProxy(HopObject obj) { INode n = obj.getNode(); - if (n == null) + if (n == null) { ref = obj.getClassName(); - else { - if (n instanceof helma.objectmodel.db.Node) - ref = new NodeHandle(((helma.objectmodel.db.Node) n).getKey()); - else + } else { + if (n instanceof Node) { + ref = new NodeHandle((Node) n); + } else { ref = n; + } } wrapped = true; } - HopObjectProxy(helma.objectmodel.db.Node node) { + HopObjectProxy(Node node) { ref = new NodeHandle(node.getKey()); } diff --git a/src/helma/scripting/rhino/extensions/XmlObject.java b/src/helma/scripting/rhino/extensions/XmlObject.java index 860bc2b1..3f7d4a0e 100644 --- a/src/helma/scripting/rhino/extensions/XmlObject.java +++ b/src/helma/scripting/rhino/extensions/XmlObject.java @@ -246,7 +246,7 @@ public class XmlObject { converter = new XmlConverter(); } - INode node = new helma.objectmodel.db.Node(null, null, + INode node = new Node(null, null, core.getApplication().getWrappedNodeManager()); INode result = converter.convert(url, node); diff --git a/src/helma/servlet/AbstractServletClient.java b/src/helma/servlet/AbstractServletClient.java index d2a78c13..0a349843 100644 --- a/src/helma/servlet/AbstractServletClient.java +++ b/src/helma/servlet/AbstractServletClient.java @@ -345,17 +345,17 @@ public abstract class AbstractServletClient extends HttpServlet { res.setContentLength(hopres.getContentLength()); res.setContentType(hopres.getContentType()); - if ("HEAD".equalsIgnoreCase(req.getMethod())) { - return; - } - - try { - OutputStream out = res.getOutputStream(); - - out.write(hopres.getContent()); - out.flush(); - } catch (Exception iox) { - log("Exception in writeResponse: " + iox); + if (!"HEAD".equalsIgnoreCase(req.getMethod())) { + byte[] content = hopres.getContent(); + if (content != null) { + try { + OutputStream out = res.getOutputStream(); + out.write(content); + out.flush(); + } catch (Exception iox) { + log("Exception in writeResponse: " + iox); + } + } } } } @@ -545,24 +545,25 @@ public abstract class AbstractServletClient extends HttpServlet { addIPAddress(buffer, request.getHeader("X-Forwarded-For")); addIPAddress(buffer, request.getHeader("Client-ip")); if (reqtrans.getSession() == null || !reqtrans.getSession().startsWith(buffer.toString())) { - response.addCookie(createSession(buffer.toString(), reqtrans, domain)); + createSession(response, buffer.toString(), reqtrans, domain); } } else if (reqtrans.getSession() == null) { - response.addCookie(createSession("", reqtrans, domain)); + createSession(response, "", reqtrans, domain); } } /** * Create a new session cookie. * + * @param response the servlet response * @param prefix the session id prefix * @param reqtrans the request object * @param domain the cookie domain - * @return the session cookie */ - private Cookie createSession(String prefix, - RequestTrans reqtrans, - String domain) { + private void createSession(HttpServletResponse response, + String prefix, + RequestTrans reqtrans, + String domain) { Application app = getApplication(); String id = null; while (id == null || app.getSession(id) != null) { @@ -575,12 +576,20 @@ public abstract class AbstractServletClient extends HttpServlet { } reqtrans.setSession(id); - Cookie cookie = new Cookie(sessionCookieName, id); - cookie.setPath("/"); - if (domain != null) - cookie.setDomain(domain); - return cookie; + StringBuffer buffer = new StringBuffer(sessionCookieName); + buffer.append("=").append(id).append("; Path=/"); + if (domain != null) { + // lowercase domain for IE + buffer.append("; Domain=").append(domain.toLowerCase()); + } + if (!"false".equalsIgnoreCase(app.getProperty("httpOnlySessionCookie"))) { + buffer.append("; HttpOnly"); + } + if ("true".equalsIgnoreCase(app.getProperty("secureSessionCookie"))) { + buffer.append("; Secure"); + } + response.addHeader("Set-Cookie", buffer.toString()); } /** diff --git a/src/helma/servlet/StandaloneServletClient.java b/src/helma/servlet/StandaloneServletClient.java index 43827d6d..40a6f4d3 100644 --- a/src/helma/servlet/StandaloneServletClient.java +++ b/src/helma/servlet/StandaloneServletClient.java @@ -19,6 +19,9 @@ package helma.servlet; import helma.framework.repository.Repository; import helma.framework.core.Application; import helma.framework.repository.FileRepository; +import helma.main.ServerConfig; +import helma.main.Server; + import java.io.*; import javax.servlet.*; import java.util.*; @@ -40,7 +43,7 @@ public final class StandaloneServletClient extends AbstractServletClient { private String appName; private String appDir; private String dbDir; - // private String hopDir; + private String hopDir; private Repository[] repositories; /** @@ -53,7 +56,12 @@ public final class StandaloneServletClient extends AbstractServletClient { public void init(ServletConfig init) throws ServletException { super.init(init); - // hopDir = init.getInitParameter("hopdir"); + hopDir = init.getInitParameter("hopdir"); + + if (hopDir == null) { + // assume helmaDir to be current directory + hopDir = "."; + } appName = init.getInitParameter("application"); @@ -70,7 +78,7 @@ public final class StandaloneServletClient extends AbstractServletClient { } Class[] parameters = { String.class }; - ArrayList repositoryList = new ArrayList(); + ArrayList repositoryList = new ArrayList(); for (int i = 0; true; i++) { String repositoryArgs = init.getInitParameter("repository." + i); @@ -92,7 +100,7 @@ public final class StandaloneServletClient extends AbstractServletClient { try { Repository newRepository = (Repository) Class.forName(repositoryImpl) .getConstructor(parameters) - .newInstance(repositoryArgs); + .newInstance(new Object[] {repositoryArgs}); repositoryList.add(newRepository); log("adding repository: " + repositoryArgs); } catch (Exception ex) { @@ -115,7 +123,7 @@ public final class StandaloneServletClient extends AbstractServletClient { } repositories = new Repository[repositoryList.size()]; - repositories = repositoryList.toArray(repositories); + repositories = (Repository[]) repositoryList.toArray(repositories); } @@ -146,8 +154,14 @@ public final class StandaloneServletClient extends AbstractServletClient { try { File dbHome = new File(dbDir); File appHome = new File(appDir); + File hopHome = new File(hopDir); - app = new Application(appName, null, repositories, appHome, dbHome); + ServerConfig config = new ServerConfig(); + config.setHomeDir(hopHome); + Server server = new Server(config); + server.init(); + + app = new Application(appName, server, repositories, appHome, dbHome); app.init(); app.start(); } catch (Exception x) { diff --git a/src/helma/util/HtmlEncoder.java b/src/helma/util/HtmlEncoder.java index c23307d1..897af8ae 100644 --- a/src/helma/util/HtmlEncoder.java +++ b/src/helma/util/HtmlEncoder.java @@ -633,9 +633,12 @@ public final class HtmlEncoder { } } - // we didn't reach a break, so encode the ampersand as HTML entity - ret.append("&"); - + // we didn't reach a break, so encode as entity unless inside a tag + if (insideMacroTag) { + ret.append('&'); + } else { + ret.append("&"); + } break; case '\\': @@ -763,7 +766,7 @@ public final class HtmlEncoder { break; } } - if (c < 128) { + if (c < 128 || insideMacroTag) { ret.append(c); } else if ((c >= 128) && (c < 256)) { ret.append(transform[c - 128]); diff --git a/src/helma/util/Logger.java b/src/helma/util/Logger.java index c24554af..32beccc6 100644 --- a/src/helma/util/Logger.java +++ b/src/helma/util/Logger.java @@ -143,7 +143,9 @@ public class Logger implements Log { // has gone. the 2000 entries threshold is somewhat arbitrary. if (entries.size() < 2000) { String message = msg == null ? "null" : msg.toString(); - entries.add(new Entry(dateCache, level, message, exception)); + Thread thread = Thread.currentThread(); + String threadId = "[" + thread.getName() + "] "; + entries.add(new Entry(dateCache, level, message, threadId, exception)); } } @@ -164,6 +166,7 @@ public class Logger implements Log { Entry entry = (Entry) entries.remove(0); writer.print(entry.date); writer.print(entry.level); + writer.print(entry.threadId); writer.println(entry.message); if (entry.exception != null) entry.exception.printStackTrace(writer); @@ -294,13 +297,14 @@ public class Logger implements Log { } class Entry { - final String date, level, message; + final String date, level, message, threadId; final Throwable exception; - Entry(String date, String level, String message, Throwable exception) { + Entry(String date, String level, String message, String threadId, Throwable exception) { this.date = date; this.level = level; this.message = message; + this.threadId = threadId; this.exception = exception; } } diff --git a/src/helma/util/ResourceProperties.java b/src/helma/util/ResourceProperties.java index 8aa567d2..bdd9a170 100644 --- a/src/helma/util/ResourceProperties.java +++ b/src/helma/util/ResourceProperties.java @@ -59,6 +59,12 @@ public class ResourceProperties extends Properties { // lower case key to original key mapping for case insensitive lookups private Properties keyMap = new Properties(); + // prefix for sub-properties + private String prefix; + + // parent properties for sub-properties + private ResourceProperties parentProperties; + /** * Constructs an empty ResourceProperties * Resources must be added manually afterwards @@ -123,6 +129,22 @@ public class ResourceProperties extends Properties { forceUpdate(); } + /** + * Constructs a properties object containing all entries where the key matches + * the given string prefix from the source map to the target map, cutting off + * the prefix from the original key. + * @see #getSubProperties(String) + * @param parentProperties the parent properties + * @param prefix the property name prefix + */ + private ResourceProperties(ResourceProperties parentProperties, String prefix) { + this.parentProperties = parentProperties; + this.prefix = prefix; + resources = new HashSet(); + setIgnoreCase(parentProperties.ignoreCase); + forceUpdate(); + } + /** * Updates the properties regardless of an actual need */ @@ -208,6 +230,21 @@ public class ResourceProperties extends Properties { } } + // if these are subproperties, reload them from the parent properties + if (parentProperties != null && prefix != null) { + parentProperties.update(); + Iterator it = parentProperties.entrySet().iterator(); + int prefixLength = prefix.length(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + String key = entry.getKey().toString(); + if (key.regionMatches(ignoreCase, 0, prefix, 0, prefixLength)) { + temp.put(key.substring(prefixLength), entry.getValue()); + } + } + + } + // at last we try to load properties from the resource list if (resources != null) { Iterator iterator = resources.iterator(); @@ -247,25 +284,13 @@ public class ResourceProperties extends Properties { * against the prefix. * * @param prefix the string prefix to match against + * @return a new subproperties instance */ public ResourceProperties getSubProperties(String prefix) { - if (prefix == null) + if (prefix == null) { throw new NullPointerException("prefix"); - if ((System.currentTimeMillis() - lastCheck) > CACHE_TIME) { - update(); } - ResourceProperties subprops = new ResourceProperties(); - subprops.setIgnoreCase(ignoreCase); - Iterator it = entrySet().iterator(); - int prefixLength = prefix.length(); - while (it.hasNext()) { - Map.Entry entry = (Map.Entry) it.next(); - String key = entry.getKey().toString(); - if (key.regionMatches(ignoreCase, 0, prefix, 0, prefixLength)) { - subprops.put(key.substring(prefixLength), entry.getValue()); - } - } - return subprops; + return new ResourceProperties(this, prefix); } /** @@ -322,7 +347,7 @@ public class ResourceProperties extends Properties { if (strkey == null) return null; } - return (String) super.get(strkey); + return super.get(strkey); } /**