Merging changes from 1.2.4 to 1.2.5

This commit is contained in:
hns 2003-06-10 13:20:45 +00:00
parent 65e4db3d8a
commit 676b70519d
32 changed files with 1498 additions and 865 deletions

View file

@ -410,15 +410,21 @@ public abstract class ESLoader extends ESObject {
// The simplest case is direct object compatibility
sourceClass = params[i].getClass();
accepted = targetClass.isAssignableFrom(sourceClass);
if (targetClass != sourceClass) {
if (targetClass == Object.class)
distance += 2;
else
distance += 1;
}
debugInfo = " accepted (subclassing)";
if (!accepted) {
// If we do not have direct object compatibility, we check various
// allowed conversions.
// Handle number and number widening
if ((isPrimitiveNumberClass(sourceClass) ||
sourceClass == Character.class)
sourceClass == Character.class)
&& isPrimitiveNumberClass(targetClass)) {
// Can be widened ?
int targetSize = getNumberSize(targetClass);
@ -431,7 +437,7 @@ public abstract class ESLoader extends ESObject {
} else {
debugInfo = " rejected (not widening numbers)";
}
// Handle String of length 1 as a Char, which can be converted to a number
// Handle String of length 1 as a Char, which can be converted to a number
} else if (targetClass == Character.class
&& params[i] instanceof String) {
if (((String) params[i]).length()==1) {
@ -440,6 +446,7 @@ public abstract class ESLoader extends ESObject {
convertToChar = new boolean[n];
}
convertToChar[i] = true;
distance += 1;
debugInfo = " accepted (String(1) as Character)";
} else {
debugInfo = " rejected (String not of length 1)";

View file

@ -77,12 +77,6 @@ public final class Application implements IPathElement, Runnable {
*/
protected SkinManager skinmgr;
/**
* Each application has one internal request evaluator for calling
* the scheduler and other internal functions.
*/
RequestEvaluator eval;
/**
* Collections for evaluator thread pooling
*/
@ -283,24 +277,25 @@ public final class Application implements IPathElement, Runnable {
loadSessionData(null);
}
// create and init type mananger
typemgr = new TypeManager(this);
typemgr.createPrototypes();
// logEvent ("Started type manager for "+name);
// eval = new RequestEvaluator (this);
logEvent("Starting evaluators for " + name);
// create and init evaluator/thread lists
freeThreads = new Stack();
allThreads = new Vector();
// allThreads.addElement (eval);
// preallocate minThreads request evaluators
int minThreads = 0;
try {
minThreads = Integer.parseInt(props.getProperty("minThreads"));
} catch (Exception ignore) {
// not parsable as number, keep 0
}
logEvent("Starting "+minThreads+" evaluator(s) for " + name);
for (int i = 0; i < minThreads; i++) {
RequestEvaluator ev = new RequestEvaluator(this);
@ -339,7 +334,7 @@ public final class Application implements IPathElement, Runnable {
public void start() {
starttime = System.currentTimeMillis();
worker = new Thread(this, "Worker-" + name);
worker.setPriority(Thread.NORM_PRIORITY + 2);
// worker.setPriority(Thread.NORM_PRIORITY + 2);
worker.start();
// logEvent ("session cleanup and scheduler thread started");
@ -740,6 +735,13 @@ public final class Application implements IPathElement, Runnable {
return p;
}
/**
* Return the prototype with the given name, if it exists
*/
public Prototype getPrototypeByName(String name) {
return (Prototype) typemgr.prototypes.get(name);
}
/**
* Return a collection containing all prototypes defined for this application
*/
@ -1101,17 +1103,7 @@ public final class Application implements IPathElement, Runnable {
* by an active RequestEvaluator thread.
*/
private Object invokeFunction(Object obj, String func, Object[] args) {
Thread thread = Thread.currentThread();
RequestEvaluator reval = null;
int l = allThreads.size();
for (int i = 0; i < l; i++) {
RequestEvaluator r = (RequestEvaluator) allThreads.get(i);
if ((r != null) && (r.rtx == thread)) {
reval = r;
}
}
RequestEvaluator reval = getCurrentRequestEvaluator();
if (reval != null) {
try {
@ -1225,8 +1217,8 @@ public final class Application implements IPathElement, Runnable {
// we use the classes from helma.doc-pacakge for introspection.
// the first time an url like /appname/api/ is parsed, the application is read again
// parsed for comments and exposed as an IPathElement
if (name.equals("api")) {
return eval.scriptingEngine.getIntrospector();
if (name.equals("api") && allThreads.size() > 0) {
return ((RequestEvaluator) allThreads.get(0)).scriptingEngine.getIntrospector();
}
return null;
@ -1306,44 +1298,47 @@ public final class Application implements IPathElement, Runnable {
* kicking out expired user sessions.
*/
public void run() {
long cleanupSleep = 60000; // thread sleep interval (fixed)
long scheduleSleep = 60000; // interval for scheduler invocation
long lastScheduler = 0; // run scheduler immediately
long lastCleanup = System.currentTimeMillis();
// interval between session cleanups
long sessionCleanupInterval = 60000;
long lastSessionCleanup = System.currentTimeMillis();
// logEvent ("Starting scheduler for "+name);
// as first thing, invoke function onStart in the root object
eval = new RequestEvaluator(this);
allThreads.addElement(eval);
// read in standard prototypes to make first request go faster
typemgr.updatePrototype("root");
typemgr.updatePrototype("global");
// as first thing, invoke function onStart in the root object
RequestEvaluator eval = getEvaluator();
try {
eval.invokeFunction((INode) null, "onStart", new Object[0]);
} catch (Exception ignore) {
logEvent("Error in " + name + "/onStart(): " + ignore);
} finally {
if (!stopped) {
releaseEvaluator(eval);
}
}
while (Thread.currentThread() == worker) {
// get session timeout
int sessionTimeout = 30;
try {
sessionTimeout = Math.max(0,
Integer.parseInt(props.getProperty("sessionTimeout",
"30")));
} catch (Exception ignore) {
System.out.println(ignore.toString());
}
long now = System.currentTimeMillis();
// check if we should clean up user sessions
if ((now - lastCleanup) > cleanupSleep) {
if ((now - lastSessionCleanup) > sessionCleanupInterval) {
lastSessionCleanup = now;
// get session timeout
int sessionTimeout = 30;
try {
sessionTimeout = Math.max(0,
Integer.parseInt(props.getProperty("sessionTimeout",
"30")));
} catch (Exception ignore) {}
try {
lastCleanup = now;
Hashtable cloned = (Hashtable) sessions.clone();
@ -1398,7 +1393,7 @@ public final class Application implements IPathElement, Runnable {
try {
thisEvaluator = getEvaluator();
} catch (RuntimeException rt) {
if (stopped == false) {
if (!stopped) {
logEvent("couldn't execute " + j +
", maximum thread count reached");
@ -1414,8 +1409,8 @@ public final class Application implements IPathElement, Runnable {
(CronJob.millisToNextFullMinute() < 30000)) {
CronRunner r = new CronRunner(thisEvaluator, j);
r.start();
activeCronJobs.put(j.getName(), r);
r.start();
} else {
try {
thisEvaluator.invokeFunction((INode) null, j.getFunction(),
@ -1423,19 +1418,23 @@ public final class Application implements IPathElement, Runnable {
} catch (Exception ex) {
logEvent("error running " + j + ": " + ex.toString());
} finally {
if (stopped == false) {
if (!stopped) {
releaseEvaluator(thisEvaluator);
}
}
}
thisEvaluator = null;
}
}
long sleepInterval = CronJob.millisToNextFullMinute();
try {
sleepInterval = Integer.parseInt(props.getProperty("schedulerInterval"))*1000;
} catch (Exception ignore) {}
// sleep until the next full minute
try {
worker.sleep(CronJob.millisToNextFullMinute());
worker.sleep(sleepInterval);
} catch (InterruptedException x) {
logEvent("Scheduler for " + name + " interrupted");
worker = null;
@ -1645,14 +1644,14 @@ public final class Application implements IPathElement, Runnable {
*
*/
public int countThreads() {
return threadgroup.activeCount() - 1;
return threadgroup.activeCount();
}
/**
*
*/
public int countEvaluators() {
return allThreads.size() - 1;
return allThreads.size();
}
/**
@ -1666,7 +1665,7 @@ public final class Application implements IPathElement, Runnable {
*
*/
public int countActiveEvaluators() {
return allThreads.size() - freeThreads.size() - 1;
return allThreads.size() - freeThreads.size();
}
/**
@ -1838,14 +1837,14 @@ public final class Application implements IPathElement, Runnable {
thisEvaluator.invokeFunction((INode) null, job.getFunction(),
new Object[0], job.getTimeout());
} catch (Exception ex) {
// gets logged in RequestEvaluator
} finally {
if (!stopped) {
releaseEvaluator(thisEvaluator);
}
thisEvaluator = null;
activeCronJobs.remove(job.getName());
}
if (stopped == false) {
releaseEvaluator(thisEvaluator);
}
thisEvaluator = null;
activeCronJobs.remove(job.getName());
}
}
}

View file

@ -121,7 +121,7 @@ public final class Prototype {
public long getChecksum() {
// long start = System.currentTimeMillis();
File[] f = getFiles();
long c = 0;
long c = directory.lastModified();
for (int i = 0; i < f.length; i++)
c += f[i].lastModified();

View file

@ -392,6 +392,11 @@ public final class RequestEvaluator implements Runnable {
app.logEvent("Exception in " +
Thread.currentThread() + ": " + x);
// Dump the profiling data to System.err
if (app.debug && !(x instanceof ScriptingException)) {
x.printStackTrace ();
}
// set done to false so that the error will be processed
done = false;
error = x.getMessage();

View file

@ -166,7 +166,7 @@ public final class TypeManager {
if (zipped == null) {
File f = new File(appDir, list[i]);
if (!f.isDirectory()) {
if (!f.isDirectory() && f.exists()) {
zipped = new ZippedAppFile(f, app);
zipfiles.put(list[i], zipped);
}
@ -237,7 +237,8 @@ public final class TypeManager {
if ((dbmap != null) && dbmap.needsUpdate()) {
dbmap.update();
if ((proto != hopobjectProto) && (proto != globalProto)) {
// this is now done in dbmap.update()!!!
/*if ((proto != hopobjectProto) && (proto != globalProto)) {
// set parent prototype, in case it has changed.
String parentName = dbmap.getExtends();
@ -246,7 +247,7 @@ public final class TypeManager {
} else if (!app.isJavaPrototype(proto.getName())) {
proto.setParentPrototype(hopobjectProto);
}
}
} */
}
}

View file

@ -80,7 +80,17 @@ public class ZippedAppFile implements Updatable {
String ename = entry.getName();
StringTokenizer st = new StringTokenizer(ename, "/");
if (st.countTokens() == 2) {
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();

View file

@ -21,6 +21,7 @@ import helma.framework.core.*;
import helma.objectmodel.*;
import helma.servlet.*;
import helma.util.SystemProperties;
import helma.util.StringUtils;
import org.apache.xmlrpc.XmlRpcHandler;
import org.mortbay.http.*;
import org.mortbay.http.handler.*;
@ -28,7 +29,6 @@ import org.mortbay.jetty.servlet.*;
import org.mortbay.util.*;
import java.io.*;
import java.lang.reflect.*;
import java.net.URLEncoder;
import java.rmi.*;
import java.rmi.server.*;
import java.util.*;
@ -38,9 +38,9 @@ import javax.servlet.Servlet;
* This class is responsible for starting and stopping Helma applications.
*/
public class ApplicationManager implements XmlRpcHandler {
private Hashtable descriptors;
private Hashtable applications;
private Hashtable xmlrpcHandlers;
private Properties mountpoints;
private int port;
private File hopHome;
private SystemProperties props;
@ -50,10 +50,10 @@ public class ApplicationManager implements XmlRpcHandler {
/**
* Creates a new ApplicationManager object.
*
* @param port ...
* @param hopHome ...
* @param props ...
* @param server ...
* @param port The RMI port we're binding to
* @param hopHome The Helma home directory
* @param props the properties defining the running apps
* @param server the server instance
*/
public ApplicationManager(int port, File hopHome, SystemProperties props,
Server server) {
@ -61,13 +61,16 @@ public class ApplicationManager implements XmlRpcHandler {
this.hopHome = hopHome;
this.props = props;
this.server = server;
descriptors = new Hashtable();
applications = new Hashtable();
xmlrpcHandlers = new Hashtable();
mountpoints = new Properties();
lastModified = 0;
}
// regularely check applications property file to create and start new applications
/**
* Called regularely check applications property file
* to create and start new applications.
*/
protected void checkForChanges() {
if (props.lastModified() > lastModified) {
try {
@ -76,79 +79,28 @@ public class ApplicationManager implements XmlRpcHandler {
if ((appName.indexOf(".") == -1) &&
(applications.get(appName) == null)) {
start(appName);
register(appName);
AppDescriptor appDesc = new AppDescriptor(appName);
appDesc.start();
appDesc.bind();
}
}
// then stop deleted ones
for (Enumeration e = applications.keys(); e.hasMoreElements();) {
String appName = (String) e.nextElement();
for (Enumeration e = descriptors.elements(); e.hasMoreElements();) {
AppDescriptor appDesc = (AppDescriptor) e.nextElement();
// check if application has been removed and should be stopped
if (!props.containsKey(appName)) {
stop(appName);
if (!props.containsKey(appDesc.appName)) {
appDesc.stop();
} else if (server.http != null) {
// check if application should be remounted at a
// different location on embedded web server
String oldMountpoint = mountpoints.getProperty(appName);
String mountpoint = getMountpoint(appName);
String pattern = getPathPattern(mountpoint);
// If application continues to run, remount
// as the mounting options may have changed.
appDesc.unbind();
AppDescriptor ndesc = new AppDescriptor(appDesc.appName);
ndesc.app = appDesc.app;
ndesc.bind();
descriptors.put(ndesc.appName, ndesc);
if (!pattern.equals(oldMountpoint)) {
Server.getLogger().log("Moving application " + appName +
" from " + oldMountpoint + " to " +
pattern);
HttpContext oldContext = server.http.getContext(null,
oldMountpoint);
if (oldContext != null) {
// oldContext.setContextPath(pattern);
oldContext.stop();
oldContext.destroy();
}
Application app = (Application) applications.get(appName);
if (!app.hasExplicitBaseURI()) {
app.setBaseURI(mountpoint);
}
ServletHttpContext context = new ServletHttpContext();
context.setContextPath(pattern);
server.http.addContext(context);
ServletHolder holder = context.addServlet(appName, "/*",
"helma.servlet.EmbeddedServletClient");
holder.setInitParameter("application", appName);
holder.setInitParameter("mountpoint", mountpoint);
if ("true".equalsIgnoreCase(props.getProperty(appName +
".responseEncoding"))) {
context.addHandler(new ContentEncodingHandler());
}
String cookieDomain = props.getProperty(appName +
".cookieDomain");
if (cookieDomain != null) {
holder.setInitParameter("cookieDomain", cookieDomain);
}
String uploadLimit = props.getProperty(appName +
".uploadLimit");
if (uploadLimit != null) {
holder.setInitParameter("uploadLimit", uploadLimit);
}
// holder.start ();
context.start();
mountpoints.setProperty(appName, pattern);
}
}
}
} catch (Exception mx) {
@ -159,134 +111,38 @@ public class ApplicationManager implements XmlRpcHandler {
}
}
void start(String appName) {
Server.getLogger().log("Building application " + appName);
try {
// check if application and db dirs are set, otherwise go with
// the defaults, passing null dirs to the constructor.
String appDirName = props.getProperty(appName + ".appdir");
File appDir = (appDirName == null) ? null : new File(appDirName);
String dbDirName = props.getProperty(appName + ".dbdir");
File dbDir = (dbDirName == null) ? null : new File(dbDirName);
// create the application instance
Application app = new Application(appName, server, appDir, dbDir);
applications.put(appName, app);
// the application is started later in the register method, when it's bound
app.init();
} catch (Exception x) {
Server.getLogger().log("Error creating application " + appName + ": " + x);
x.printStackTrace();
}
/**
* Start an application by name
*/
public void start(String appName) {
AppDescriptor desc = new AppDescriptor(appName);
desc.start();
}
void stop(String appName) {
Server.getLogger().log("Stopping application " + appName);
try {
Application app = (Application) applications.get(appName);
// unbind from RMI server
if (port > 0) {
Naming.unbind("//:" + port + "/" + appName);
}
// unbind from Jetty HTTP server
if (server.http != null) {
String mountpoint = mountpoints.getProperty(appName);
HttpContext context = server.http.getContext(null, mountpoint);
if (context != null) {
context.stop();
context.destroy();
}
}
// unregister as XML-RPC handler
xmlrpcHandlers.remove(app.getXmlRpcHandlerName());
app.stop();
Server.getLogger().log("Unregistered application " + appName);
} catch (Exception x) {
Server.getLogger().log("Couldn't unregister app: " + x);
}
applications.remove(appName);
}
void register(String appName) {
try {
Server.getLogger().log("Binding application " + appName);
Application app = (Application) applications.get(appName);
// bind to RMI server
if (port > 0) {
Naming.rebind("//:" + port + "/" + appName, new RemoteApplication(app));
}
// bind to Jetty HTTP server
if (server.http != null) {
String mountpoint = getMountpoint(appName);
// if using embedded webserver (not AJP) set application URL prefix
if (!app.hasExplicitBaseURI()) {
app.setBaseURI(mountpoint);
}
String pattern = getPathPattern(mountpoint);
ServletHttpContext context = new ServletHttpContext();
context.setContextPath(pattern);
server.http.addContext(context);
ServletHolder holder = context.addServlet(appName, "/*",
"helma.servlet.EmbeddedServletClient");
holder.setInitParameter("application", appName);
holder.setInitParameter("mountpoint", mountpoint);
if ("true".equalsIgnoreCase(props.getProperty(appName +
".responseEncoding"))) {
context.addHandler(new ContentEncodingHandler());
}
String cookieDomain = props.getProperty(appName + ".cookieDomain");
if (cookieDomain != null) {
holder.setInitParameter("cookieDomain", cookieDomain);
}
String uploadLimit = props.getProperty(appName + ".uploadLimit");
if (uploadLimit != null) {
holder.setInitParameter("uploadLimit", uploadLimit);
}
String debug = props.getProperty(appName + ".debug");
if (debug != null) {
holder.setInitParameter("debug", debug);
}
// holder.start ();
context.start();
mountpoints.setProperty(appName, pattern);
}
// register as XML-RPC handler
xmlrpcHandlers.put(app.getXmlRpcHandlerName(), app);
app.start();
} catch (Exception x) {
Server.getLogger().log("Couldn't register and start app: " + x);
x.printStackTrace();
/**
* Bind an application by name
*/
public void register(String appName) {
AppDescriptor desc = (AppDescriptor) descriptors.get(appName);
if (desc != null) {
desc.bind();
}
}
/**
*
* Stop an application by name
*/
public void stop(String appName) {
AppDescriptor desc = (AppDescriptor) descriptors.get(appName);
if (desc != null) {
desc.stop();
}
}
/**
* Start all applications listed in the properties
*/
public void startAll() {
try {
@ -294,33 +150,14 @@ public class ApplicationManager implements XmlRpcHandler {
String appName = (String) e.nextElement();
if (appName.indexOf(".") == -1) {
start(appName);
AppDescriptor desc = new AppDescriptor(appName);
desc.start();
}
}
for (Enumeration e = props.keys(); e.hasMoreElements();) {
String appName = (String) e.nextElement();
if (appName.indexOf(".") == -1) {
register(appName);
}
}
if (server.http != null) {
// add handler for static files.
File staticContent = new File(server.getHopHome(), "static");
Server.getLogger().log("Serving static content from " +
staticContent.getAbsolutePath());
HttpContext context = server.http.addContext("/static/*");
context.setResourceBase(staticContent.getAbsolutePath());
ResourceHandler handler = new ResourceHandler();
context.addHandler(handler);
context.start();
for (Enumeration e = descriptors.elements(); e.hasMoreElements();) {
AppDescriptor appDesc = (AppDescriptor) e.nextElement();
appDesc.bind();
}
lastModified = System.currentTimeMillis();
@ -331,13 +168,13 @@ public class ApplicationManager implements XmlRpcHandler {
}
/**
*
* Stop all running applications.
*/
public void stopAll() {
for (Enumeration en = applications.keys(); en.hasMoreElements();) {
String appName = (String) en.nextElement();
for (Enumeration en = descriptors.elements(); en.hasMoreElements();) {
AppDescriptor appDesc = (AppDescriptor) en.nextElement();
stop(appName);
appDesc.stop();
}
}
@ -375,6 +212,12 @@ public class ApplicationManager implements XmlRpcHandler {
String method2 = method.substring(dot + 1);
Application app = (Application) xmlrpcHandlers.get(handler);
if (app == null) {
app = (Application) xmlrpcHandlers.get("*");
// use the original method name, the handler is resolved within the app.
method2 = method;
}
if (app == null) {
throw new Exception("Handler \"" + handler + "\" not found for " + method);
}
@ -382,13 +225,7 @@ public class ApplicationManager implements XmlRpcHandler {
return app.executeXmlRpc(method2, params);
}
private String getMountpoint(String appName) {
String mountpoint = props.getProperty(appName + ".mountpoint");
if (mountpoint == null) {
return "/" + URLEncoder.encode(appName);
}
private String getMountpoint(String mountpoint) {
mountpoint = mountpoint.trim();
if ("".equals(mountpoint)) {
@ -400,15 +237,227 @@ public class ApplicationManager implements XmlRpcHandler {
return mountpoint;
}
private String joinMountpoint(String prefix, String suffix) {
if (prefix.endsWith("/") || suffix.startsWith("/")) {
return prefix+suffix;
} else {
return prefix+"/"+suffix;
}
}
private String getPathPattern(String mountpoint) {
if (!mountpoint.startsWith("/")) {
mountpoint = "/"+mountpoint;
}
if ("/".equals(mountpoint)) {
return "/";
}
if (!mountpoint.endsWith("/")) {
return mountpoint + "/*";
if (mountpoint.endsWith("/")) {
return mountpoint + "*";
}
return mountpoint + "/*";
}
/**
* Inner class that describes an application and its start settings.
*/
class AppDescriptor {
Application app;
String appName;
File appDir;
File dbDir;
String mountpoint;
String pathPattern;
String staticDir;
String staticMountpoint;
String xmlrpcHandlerName;
String cookieDomain;
String uploadLimit;
String debug;
String charset;
boolean encode;
/**
* Creates an AppDescriptor from the properties.
*/
AppDescriptor(String name) {
appName = name;
mountpoint = getMountpoint(props.getProperty(name+".mountpoint",
appName));
pathPattern = getPathPattern(mountpoint);
staticDir = props.getProperty(name+".static");
staticMountpoint = getPathPattern(props.getProperty(name+".staticMountpoint",
joinMountpoint(mountpoint, "static")));
cookieDomain = props.getProperty(name+".cookieDomain");
uploadLimit = props.getProperty(name+".uploadLimit");
debug = props.getProperty(name+".debug");
encode = "true".equalsIgnoreCase(props.getProperty(name +
".responseEncoding"));
String appDirName = props.getProperty(name + ".appdir");
appDir = (appDirName == null) ? null : new File(appDirName);
String dbDirName = props.getProperty(name + ".dbdir");
dbDir = (dbDirName == null) ? null : new File(dbDirName);
}
void start() {
Server.getLogger().log("Building application " + appName);
try {
// create the application instance
app = new Application(appName, server, appDir, dbDir);
// register ourselves
descriptors.put(appName, this);
applications.put(appName, app);
// the application is started later in the register method, when it's bound
app.init();
app.start();
} catch (Exception x) {
Server.getLogger().log("Error creating application " + appName + ": " + x);
x.printStackTrace();
}
}
void stop() {
Server.getLogger().log("Stopping application " + appName);
// unbind application
unbind();
// stop application
try {
app.stop();
Server.getLogger().log("Stopped application " + appName);
} catch (Exception x) {
Server.getLogger().log("Couldn't stop app: " + x);
}
descriptors.remove(appName);
applications.remove(appName);
}
void bind() {
try {
Server.getLogger().log("Binding application " + appName);
// bind to RMI server
if (port > 0) {
Naming.rebind("//:" + port + "/" + appName, new RemoteApplication(app));
}
// bind to Jetty HTTP server
if (server.http != null) {
// if using embedded webserver (not AJP) set application URL prefix
if (!app.hasExplicitBaseURI()) {
app.setBaseURI(mountpoint);
}
ServletHttpContext context = new ServletHttpContext();
context.setContextPath(pathPattern);
server.http.addContext(context);
if (encode) {
context.addHandler(new ContentEncodingHandler());
}
ServletHolder holder = context.addServlet(appName, "/*",
"helma.servlet.EmbeddedServletClient");
holder.setInitParameter("application", appName);
// holder.setInitParameter("mountpoint", mountpoint);
if (cookieDomain != null) {
holder.setInitParameter("cookieDomain", cookieDomain);
}
if (uploadLimit != null) {
holder.setInitParameter("uploadLimit", uploadLimit);
}
if (debug != null) {
holder.setInitParameter("debug", debug);
}
holder.setInitParameter("charset", app.getCharset());
context.start();
if (staticDir != null) {
File staticContent = new File(staticDir);
if (!staticContent.isAbsolute()) {
staticContent = new File(server.getHopHome(), staticDir);
}
Server.getLogger().log("Serving static from " +
staticContent.getAbsolutePath());
Server.getLogger().log("Mounting static at " +
staticMountpoint);
HttpContext cx = server.http.addContext(staticMountpoint);
cx.setResourceBase(staticContent.getAbsolutePath());
ResourceHandler handler = new ResourceHandler();
cx.addHandler(handler);
cx.start();
}
}
// register as XML-RPC handler
xmlrpcHandlerName = app.getXmlRpcHandlerName();
xmlrpcHandlers.put(xmlrpcHandlerName, app);
// app.start();
} catch (Exception x) {
Server.getLogger().log("Couldn't bind app: " + x);
x.printStackTrace();
}
}
void unbind() {
Server.getLogger().log("Unbinding application " + appName);
try {
// unbind from RMI server
if (port > 0) {
Naming.unbind("//:" + port + "/" + appName);
}
// unbind from Jetty HTTP server
if (server.http != null) {
HttpContext context = server.http.getContext(null, pathPattern);
if (context != null) {
context.stop();
context.destroy();
}
if (staticDir != null) {
context = server.http.getContext(null, staticMountpoint);
if (context != null) {
context.stop();
context.destroy();
}
}
}
// unregister as XML-RPC handler
xmlrpcHandlers.remove(xmlrpcHandlerName);
} catch (Exception x) {
Server.getLogger().log("Couldn't unbind app: " + x);
}
}
return mountpoint + "*";
}
}

View file

@ -36,7 +36,7 @@ import java.util.*;
* Helma server main class.
*/
public class Server implements IPathElement, Runnable {
public static final String version = "1.2.4 (2003/04/16)";
public static final String version = "1.2.5 (2003/06/06)";
// server-wide properties
static SystemProperties appsProps;

View file

@ -26,10 +26,16 @@ public final class DbColumn {
private final int type;
private final Relation relation;
private final boolean isId;
private final boolean isPrototype;
private final boolean isName;
private final boolean isMapped;
/**
* Constructor
*/
public DbColumn(String name, int type, Relation rel) {
public DbColumn(String name, int type, Relation rel, DbMapping dbmap) {
this.name = name;
this.type = type;
this.relation = rel;
@ -37,6 +43,12 @@ public final class DbColumn {
if (relation != null) {
relation.setColumnType(type);
}
isId = name.equalsIgnoreCase(dbmap.getIDField());
isPrototype = name.equalsIgnoreCase(dbmap.getPrototypeField());
isName = name.equalsIgnoreCase(dbmap.getNameField());
isMapped = relation != null || isId || isPrototype || isName;
}
/**
@ -59,4 +71,33 @@ public final class DbColumn {
public Relation getRelation() {
return relation;
}
/**
* Returns true if this column serves as ID field for the prototype.
*/
public boolean isIdField() {
return isId;
}
/**
* Returns true if this column serves as prototype field for the prototype.
*/
public boolean isPrototypeField() {
return isPrototype;
}
/**
* Returns true if this column serves as name field for the prototype.
*/
public boolean isNameField() {
return isName;
}
/**
* Returns true if this field is mapped by the prototype's db mapping.
*/
public boolean isMapped() {
return isMapped;
}
}

View file

@ -46,9 +46,9 @@ public final class DbKey implements Key, Serializable {
/**
*
*
* @param what ...
* @param what the other key to be compared with this one
*
* @return ...
* @return true if both keys are identical
*/
public boolean equals(Object what) {
if (what == this) {
@ -69,7 +69,7 @@ public final class DbKey implements Key, Serializable {
/**
*
*
* @return ...
* @return this key's hash code
*/
public int hashCode() {
if (hashcode == 0) {
@ -84,7 +84,7 @@ public final class DbKey implements Key, Serializable {
/**
*
*
* @return ...
* @return the key of this key's object's parent object
*/
public Key getParentKey() {
return null;
@ -93,7 +93,7 @@ public final class DbKey implements Key, Serializable {
/**
*
*
* @return ...
* @return the unique storage name for this key's object
*/
public String getStorageName() {
return storageName;
@ -102,7 +102,7 @@ public final class DbKey implements Key, Serializable {
/**
*
*
* @return ...
* @return this key's object's id
*/
public String getID() {
return id;
@ -111,7 +111,7 @@ public final class DbKey implements Key, Serializable {
/**
*
*
* @return ...
* @return a string representation for this key
*/
public String toString() {
return (storageName == null) ? ("[" + id + "]") : (storageName + "[" + id + "]");

View file

@ -17,12 +17,14 @@
package helma.objectmodel.db;
import helma.framework.core.Application;
import helma.framework.core.Prototype;
import helma.util.SystemProperties;
import helma.util.Updatable;
import java.sql.*;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.StringTokenizer;
@ -75,6 +77,9 @@ public final class DbMapping implements Updatable {
// Map of db columns by name
HashMap columnMap;
// Array of aggressively loaded references
Relation[] joins;
// pre-rendered select statement
String selectString = null;
String insertString = null;
@ -159,6 +164,7 @@ public final class DbMapping implements Updatable {
return props.lastModified() != lastTypeChange;
}
/**
* Read the mapping from the Properties. Return true if the properties were changed.
* The read is split in two, this method and the rewire method. The reason is that in order
@ -173,9 +179,6 @@ public final class DbMapping implements Updatable {
// can be stored in this table
prototypeField = props.getProperty("_prototypefield");
// see if this prototype extends (inherits from) any other prototype
extendsProto = props.getProperty("_extends");
dbSourceName = props.getProperty("_db");
if (dbSourceName != null) {
@ -220,19 +223,43 @@ public final class DbMapping implements Updatable {
lastTypeChange = props.lastModified();
// see if this prototype extends (inherits from) any other prototype
extendsProto = props.getProperty("_extends");
if (extendsProto != null) {
parentMapping = app.getDbMapping(extendsProto);
if (parentMapping != null && parentMapping.needsUpdate()) {
parentMapping.update();
}
} else {
parentMapping = null;
}
// set the parent prototype in the corresponding Prototype object!
// this was previously done by TypeManager, but we need to do it
// ourself because DbMapping.update() may be called by other code than
// the TypeManager.
if (typename != null &&
!"global".equalsIgnoreCase(typename) &&
!"hopobject".equalsIgnoreCase(typename)) {
Prototype proto = app.getPrototypeByName(typename);
if (proto != null) {
if (extendsProto != null) {
proto.setParentPrototype(app.getPrototypeByName(extendsProto));
} else if (!app.isJavaPrototype(typename)) {
proto.setParentPrototype(app.getPrototypeByName("hopobject"));
}
}
}
// null the cached columns and select string
columns = null;
columnMap.clear();
selectString = insertString = updateString = null;
if (extendsProto != null) {
parentMapping = app.getDbMapping(extendsProto);
}
// if (tableName != null && dbSource != null) {
// app.logEvent ("set data dbSource for "+typename+" to "+dbSource);
HashMap p2d = new HashMap();
HashMap d2p = new HashMap();
ArrayList joinList = new ArrayList();
for (Enumeration e = props.keys(); e.hasMoreElements();) {
String propName = (String) e.nextElement();
@ -259,7 +286,22 @@ public final class DbMapping implements Updatable {
if ((rel.columnName != null) &&
((rel.reftype == Relation.PRIMITIVE) ||
(rel.reftype == Relation.REFERENCE))) {
d2p.put(rel.columnName.toUpperCase(), rel);
Relation old = (Relation) d2p.put(rel.columnName.toUpperCase(), rel);
// check if we're overwriting another relation
// if so, primitive relations get precendence to references
if (old != null) {
app.logEvent("*** Duplicate mapping for "+typename+"."+rel.columnName);
if (old.reftype == Relation.PRIMITIVE) {
d2p.put(old.columnName.toUpperCase(), old);
}
}
}
// check if a reference is aggressively fetched
if ((rel.reftype == Relation.REFERENCE ||
rel.reftype == Relation.COMPLEX_REFERENCE) &&
rel.aggressiveLoading) {
joinList.add(rel);
}
// app.logEvent ("Mapping "+propName+" -> "+dbField);
@ -272,6 +314,9 @@ public final class DbMapping implements Updatable {
prop2db = p2d;
db2prop = d2p;
joins = new Relation[joinList.size()];
joins = (Relation[]) joinList.toArray(joins);
String subnodeMapping = props.getProperty("_children");
if (subnodeMapping != null) {
@ -806,20 +851,31 @@ public final class DbMapping implements Updatable {
// ok, we have the meta data, now loop through mapping...
int ncols = meta.getColumnCount();
columns = new DbColumn[ncols];
ArrayList list = new ArrayList(ncols);
for (int i = 0; i < ncols; i++) {
String colName = meta.getColumnName(i + 1);
Relation rel = columnNameToRelation(colName);
columns[i] = new DbColumn(colName, meta.getColumnType(i + 1), rel);
DbColumn col = new DbColumn(colName, meta.getColumnType(i + 1), rel, this);
// if (col.isMapped()) {
list.add(col);
// }
}
columns = new DbColumn[list.size()];
columns = (DbColumn[]) list.toArray(columns);
}
return columns;
}
/**
* Return the array of relations that are fetched with objects of this type.
*/
public Relation[] getJoins() {
return joins;
}
/**
*
*
@ -832,6 +888,7 @@ public final class DbMapping implements Updatable {
*/
public DbColumn getColumn(String columnName)
throws ClassNotFoundException, SQLException {
DbColumn col = (DbColumn) columnMap.get(columnName);
if (col == null) {
@ -849,10 +906,6 @@ public final class DbMapping implements Updatable {
}
}
if (col == null) {
throw new SQLException("Column " + columnName + " not found in " + this);
}
columnMap.put(columnName, col);
}
@ -860,12 +913,13 @@ public final class DbMapping implements Updatable {
}
/**
* Get a StringBuffer initialized to the first part of the select statement
* for objects defined by this DbMapping
*
* @return the StringBuffer containing the first part of the select query
*
* @return ...
*
* @throws SQLException ...
* @throws ClassNotFoundException ...
* @throws SQLException if the table meta data could not be retrieved
* @throws ClassNotFoundException if the JDBC driver class was not found
*/
public StringBuffer getSelect() throws SQLException, ClassNotFoundException {
String sel = selectString;
@ -874,11 +928,43 @@ public final class DbMapping implements Updatable {
return new StringBuffer(sel);
}
StringBuffer s = new StringBuffer("SELECT * FROM ");
StringBuffer s = new StringBuffer("SELECT ");
/* DbColumn[] cols = columns;
if (cols == null) {
cols = getColumns();
}
for (int i = 0; i < cols.length; i++) {
s.append(cols[i].getName());
if (i < cols.length-1) {
s.append(',');
}
}
for (int i = 0; i < joins.length; i++) {
} */
s.append ("*");
s.append(" FROM ");
s.append(getTableName());
s.append(" ");
for (int i = 0; i < joins.length; i++) {
if (!joins[i].otherType.isRelational()) {
continue;
}
s.append("LEFT JOIN ");
s.append(joins[i].otherType.getTableName());
s.append(" AS _HLM_");
s.append(joins[i].propName);
s.append(" ON ");
joins[i].renderJoinConstraints(s);
}
// cache rendered string for later calls.
selectString = s.toString();
@ -944,6 +1030,11 @@ public final class DbMapping implements Updatable {
try {
DbColumn col = getColumn(columnName);
// This is not a mapped column. In case of doubt, add quotes.
if (col == null) {
return true;
}
switch (col.getType()) {
case Types.CHAR:
case Types.VARCHAR:

View file

@ -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.objectmodel.db;
import java.io.Serializable;
import java.util.Map;
/**
* This is the internal representation of a database key with multiple
* columns. It is constructed from the logical table (type) name and the
* column name/column value pairs that identify the key's object
*
* NOTE: This class doesn't fully support the Key interface - getID always
* returns null since there is no unique key (at least we don't know about it).
*/
public final class MultiKey implements Key, Serializable {
// the name of the prototype which defines the storage of this object.
// this is the name of the object's prototype, or one of its ancestors.
// If null, the object is stored in the embedded db.
private final String storageName;
// the id that defines this key's object within the above storage space
private final Map parts;
// lazily initialized hashcode
private transient int hashcode = 0;
/**
* make a key for a persistent Object, describing its datasource and key parts.
*/
public MultiKey(DbMapping dbmap, Map parts) {
this.parts = parts;
this.storageName = (dbmap == null) ? null : dbmap.getStorageTypeName();
}
/**
*
*
* @param what the other key to be compared with this one
*
* @return true if both keys are identical
*/
public boolean equals(Object what) {
if (what == this) {
return true;
}
if (!(what instanceof MultiKey)) {
return false;
}
MultiKey k = (MultiKey) what;
// storageName is an interned string (by DbMapping, from where we got it)
// so we can compare by using == instead of the equals method.
return (storageName == k.storageName) &&
((parts == k.parts) || parts.equals(k.parts));
}
/**
*
*
* @return this key's hash code
*/
public int hashCode() {
if (hashcode == 0) {
hashcode = (storageName == null) ? (17 + (37 * parts.hashCode()))
: (17 + (37 * storageName.hashCode()) +
(+37 * parts.hashCode()));
}
return hashcode;
}
/**
*
*
* @return the key of this key's object's parent object
*/
public Key getParentKey() {
return null;
}
/**
*
*
* @return the unique storage name for this key's object
*/
public String getStorageName() {
return storageName;
}
/**
*
*
* @return this key's object's id
*/
public String getID() {
return null;
}
/**
*
*
* @return a string representation for this key
*/
public String toString() {
return (storageName == null) ? ("[" + parts + "]") : (storageName + "[" + parts + "]");
}
}

View file

@ -76,7 +76,7 @@ public final class Node implements INode, Serializable {
transient private int state;
/**
* This constructor is only used for instances of the NullNode subclass. Do not use for ordinary Nodes.
* This constructor is only used for NullNode instance. Do not use for ordinary Nodes.
*/
Node() {
created = lastmodified = System.currentTimeMillis();
@ -145,183 +145,28 @@ public final class Node implements INode, Serializable {
}
/**
* Constructor used for nodes being stored in a relational database table.
* Initializer used for nodes being stored in a relational database table.
*/
public Node(DbMapping dbm, ResultSet rs, DbColumn[] columns, WrappedNodeManager nmgr)
throws SQLException, IOException {
public void init(DbMapping dbm, String id, String name, String protoName,
Hashtable propMap, WrappedNodeManager nmgr) {
this.nmgr = nmgr;
// see what prototype/DbMapping this object should use
dbmap = dbm;
this.dbmap = dbm;
// set the prototype name
this.prototype = protoName;
String protoField = dbmap.getPrototypeField();
if (protoField != null) {
String protoName = rs.getString(protoField);
if (protoName != null) {
dbmap = nmgr.getDbMapping(protoName);
if (dbmap == null) {
// invalid prototype name!
System.err.println("Warning: Invalid prototype name: " + protoName +
" - using default");
dbmap = dbm;
}
}
}
setPrototype(dbmap.getTypeName());
id = rs.getString(dbmap.getIDField());
// checkWriteLock ();
String nameField = dbmap.getNameField();
name = (nameField == null) ? id : rs.getString(nameField);
this.id = id;
this.name = name;
// If name was not set from resultset, create a synthetical name now.
if ((name == null) || (name.length() == 0)) {
name = dbmap.getTypeName() + " " + id;
this.name = dbmap.getTypeName() + " " + id;
}
created = lastmodified = System.currentTimeMillis();
this.propMap = propMap;
for (int i = 0; i < columns.length; i++) {
Relation rel = columns[i].getRelation();
if ((rel == null) ||
((rel.reftype != Relation.PRIMITIVE) &&
(rel.reftype != Relation.REFERENCE))) {
continue;
}
Property newprop = new Property(rel.propName, this);
switch (columns[i].getType()) {
case Types.BIT:
newprop.setBooleanValue(rs.getBoolean(columns[i].getName()));
break;
case Types.TINYINT:
case Types.BIGINT:
case Types.SMALLINT:
case Types.INTEGER:
newprop.setIntegerValue(rs.getLong(columns[i].getName()));
break;
case Types.REAL:
case Types.FLOAT:
case Types.DOUBLE:
newprop.setFloatValue(rs.getDouble(columns[i].getName()));
break;
case Types.DECIMAL:
case Types.NUMERIC:
BigDecimal num = rs.getBigDecimal(columns[i].getName());
if (num == null) {
break;
}
if (num.scale() > 0) {
newprop.setFloatValue(num.doubleValue());
} else {
newprop.setIntegerValue(num.longValue());
}
break;
case Types.VARBINARY:
case Types.BINARY:
newprop.setStringValue(rs.getString(columns[i].getName()));
break;
case Types.LONGVARBINARY:
case Types.LONGVARCHAR:
try {
newprop.setStringValue(rs.getString(columns[i].getName()));
} catch (SQLException x) {
Reader in = rs.getCharacterStream(columns[i].getName());
char[] buffer = new char[2048];
int read = 0;
int r = 0;
while ((r = in.read(buffer, read, buffer.length - read)) > -1) {
read += r;
if (read == buffer.length) {
// grow input buffer
char[] newBuffer = new char[buffer.length * 2];
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
buffer = newBuffer;
}
}
newprop.setStringValue(new String(buffer, 0, read));
}
break;
case Types.CHAR:
case Types.VARCHAR:
case Types.OTHER:
newprop.setStringValue(rs.getString(columns[i].getName()));
break;
case Types.DATE:
case Types.TIME:
case Types.TIMESTAMP:
newprop.setDateValue(rs.getTimestamp(columns[i].getName()));
break;
case Types.NULL:
newprop.setStringValue(null);
break;
// continue;
default:
newprop.setStringValue(rs.getString(columns[i].getName()));
break;
}
if (rs.wasNull()) {
newprop.setStringValue(null);
}
if (propMap == null) {
propMap = new Hashtable();
}
propMap.put(rel.propName.toLowerCase(), newprop);
// if the property is a pointer to another node, change the property type to NODE
if ((rel.reftype == Relation.REFERENCE) && rel.usesPrimaryKey()) {
// FIXME: References to anything other than the primary key are not supported
newprop.convertToNodeReference(rel.otherType);
// newprop.nhandle = new NodeHandle (new DbKey (rel.otherType, newprop.getStringValue ()));
// newprop.type = IProperty.NODE;
}
// mark property as clean, since it's fresh from the db
newprop.dirty = false;
}
// again set created and lastmodified. This is because
// lastmodified has been been updated, and we want both values to
// be identical to show that the node hasn't been changed since
// it was first created.
// set lastmodified and created timestamps and mark as clean
created = lastmodified = System.currentTimeMillis();
markAs(CLEAN);
}
@ -609,7 +454,7 @@ public final class Node implements INode, Serializable {
} catch (Exception ignore) {
// just fall back to default method
}
lastNameCheck = System.currentTimeMillis();
}
@ -914,7 +759,7 @@ public final class Node implements INode, Serializable {
if (pn != null) {
setParent((Node) pn);
anonymous = !pinfo.named;
// anonymous = !pinfo.named;
lastParentSet = System.currentTimeMillis();
return pn;
@ -1577,7 +1422,7 @@ public final class Node implements INode, Serializable {
// do not fetch subnodes for nodes that haven't been persisted yet or are in
// the process of being persistified - except if "manual" subnoderelation is set.
if (subRel.aggressiveLoading &&
if (subRel.aggressiveLoading && subRel.getGroup() == null &&
(((state != TRANSIENT) && (state != NEW)) ||
(subnodeRelation != null))) {
// we don't want to load *all* nodes if we just want to count them
@ -1813,23 +1658,28 @@ public final class Node implements INode, Serializable {
}
// so if we have a property relation and it does in fact link to another object...
if ((propRel != null) && propRel.isCollection()) {
if ((propRel != null) && (propRel.isCollection() || propRel.isComplexReference())) {
// in some cases we just want to create and set a generic node without consulting
// the NodeManager if it exists: When we get a collection (aka virtual node)
// from a transient node for the first time, or when we get a collection whose
// content objects are stored in the embedded XML data storage.
if ((state == TRANSIENT) && propRel.virtual) {
INode node = new Node(propname, propRel.getPrototype(), nmgr);
Node pn = new Node(propname, propRel.getPrototype(), nmgr);
node.setDbMapping(propRel.getVirtualMapping());
setNode(propname, node);
prop = (Property) propMap.get(propname);
pn.setDbMapping(propRel.getVirtualMapping());
pn.setParent(this);
if (propRel.needsPersistence()) {
setNode(propname, pn);
prop = (Property) propMap.get(propname);
} else {
prop = new Property(propname, this, pn);
}
}
// if this is from relational database only fetch if this node
// is itself persistent.
else if ((state != TRANSIENT) && propRel.createPropertyOnDemand()) {
else if ((state != TRANSIENT) && propRel.createOnDemand()) {
// this may be a relational node stored by property name
try {
// try {
Node pn = nmgr.getNode(this, propname, propRel);
if (pn != null) {
@ -1842,9 +1692,9 @@ public final class Node implements INode, Serializable {
prop = new Property(propname, this, pn);
}
} catch (RuntimeException nonode) {
// } catch (RuntimeException nonode) {
// wasn't a node after all
}
// }
}
}
}
@ -1985,6 +1835,41 @@ public final class Node implements INode, Serializable {
return null;
}
/**
* Directly set a property on this node
*
* @param propname ...
* @param value ...
*/
protected void set(String propname, Object value, int type) {
checkWriteLock();
if (propMap == null) {
propMap = new Hashtable();
}
propname = propname.trim();
String p2 = propname.toLowerCase();
Property prop = (Property) propMap.get(p2);
if (prop != null) {
prop.setValue(value, type);
} else {
prop = new Property(propname, this);
prop.setValue(value, type);
propMap.put(p2, prop);
}
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED));
lastmodified = System.currentTimeMillis();
if (state == CLEAN) {
markAs(MODIFIED);
}
}
/**
*
*
@ -2324,6 +2209,17 @@ public final class Node implements INode, Serializable {
String p2 = propname.toLowerCase();
Relation rel = (dbmap == null) ? null : dbmap.getPropertyRelation(propname);
if (rel != null && (rel.countConstraints() > 1 || rel.isComplexReference())) {
rel.setConstraints(this, n);
if (rel.isComplexReference()) {
Key key = new MultiKey(n.getDbMapping(), rel.getKeyParts(this));
nmgr.nmgr.registerNode(n, key);
return;
}
}
Property prop = (propMap == null) ? null : (Property) propMap.get(p2);
if (prop != null) {
@ -2346,8 +2242,6 @@ public final class Node implements INode, Serializable {
prop.setNodeValue(n);
Relation rel = (dbmap == null) ? null : dbmap.getPropertyRelation(propname);
if ((rel == null) || (rel.reftype == Relation.REFERENCE) || rel.virtual ||
(rel.otherType == null) || !rel.otherType.isRelational()) {
// the node must be stored as explicit property
@ -2435,6 +2329,15 @@ public final class Node implements INode, Serializable {
if (state == CLEAN) {
markAs(MODIFIED);
}
} else if (dbmap != null) {
// check if this is a complex constraint and we have to
// unset constraints.
Relation rel = dbmap.getExactPropertyRelation(propname);
if (rel != null && (rel.isComplexReference())) {
p = getProperty(propname);
rel.unsetConstraints(this, p.getNodeValue());
}
}
} catch (Exception ignore) {
}
@ -2538,8 +2441,8 @@ public final class Node implements INode, Serializable {
* This method walks down node path to the first non-virtual node and return it.
* limit max depth to 3, since there shouldn't be more then 2 layers of virtual nodes.
*/
public INode getNonVirtualParent() {
INode node = this;
public Node getNonVirtualParent() {
Node node = this;
for (int i = 0; i < 5; i++) {
if (node == null) {
@ -2550,7 +2453,7 @@ public final class Node implements INode, Serializable {
return node;
}
node = node.getParent();
node = (Node) node.getParent();
}
return null;

View file

@ -19,6 +19,7 @@ package helma.objectmodel.db;
import helma.framework.core.Application;
import helma.objectmodel.*;
import helma.util.CacheMap;
import java.math.BigDecimal;
import java.io.*;
import java.sql.*;
import java.util.*;
@ -270,7 +271,10 @@ public final class NodeManager {
Key key = null;
// check what kind of object we're looking for and make an apropriate key
if (rel.virtual || (rel.groupby != null) || !rel.usesPrimaryKey()) {
if (rel.isComplexReference()) {
// a key for a complex reference
key = new MultiKey(rel.otherType, rel.getKeyParts(home));
} else if (rel.virtual || (rel.groupby != null) || !rel.usesPrimaryKey()) {
// a key for a virtually defined object that's never actually stored in the db
// or a key for an object that represents subobjects grouped by some property, generated on the fly
key = new SyntheticKey(home.getKey(), kstr);
@ -395,6 +399,14 @@ public final class NodeManager {
cache.put(node.getKey(), node);
}
/**
* Register a node in the node cache using the key argument.
*/
protected void registerNode(Node node, Key key) {
cache.put(key, node);
}
/**
* Remove a node from the node cache. If at a later time it is accessed again,
* it will be refetched from the database.
@ -515,6 +527,7 @@ public final class NodeManager {
try {
int stmtNumber = 1;
// first column of insert statement is always the primary key
stmt.setString(stmtNumber, node.getID());
Hashtable propMap = node.getPropMap();
@ -523,7 +536,7 @@ public final class NodeManager {
Relation rel = columns[i].getRelation();
Property p = null;
if ((rel != null) && (rel.isPrimitive() || rel.isReference())) {
if (rel != null && propMap != null && (rel.isPrimitive() || rel.isReference())) {
p = (Property) propMap.get(rel.getPropName());
}
@ -961,6 +974,7 @@ public final class NodeManager {
public List getNodeIDs(Node home, Relation rel) throws Exception {
// Transactor tx = (Transactor) Thread.currentThread ();
// tx.timer.beginEvent ("getNodeIDs "+home);
if ((rel == null) || (rel.otherType == null) || !rel.otherType.isRelational()) {
// this should never be called for embedded nodes
throw new RuntimeException("NodeMgr.getNodeIDs called for non-relational node " +
@ -982,7 +996,8 @@ public final class NodeManager {
if (home.getSubnodeRelation() != null) {
// subnode relation was explicitly set
q = new StringBuffer("SELECT ").append(idfield).append(" FROM ")
q = new StringBuffer("SELECT ").append(table).append('.')
.append(idfield).append(" FROM ")
.append(table).append(" ")
.append(home.getSubnodeRelation())
.toString();
@ -1075,6 +1090,7 @@ public final class NodeManager {
Connection con = dbm.getConnection();
Statement stmt = con.createStatement();
DbColumn[] columns = dbm.getColumns();
Relation[] joins = dbm.getJoins();
StringBuffer q = dbm.getSelect();
try {
@ -1102,7 +1118,10 @@ public final class NodeManager {
while (rs.next()) {
// create new Nodes.
Node node = new Node(rel.otherType, rs, columns, safe);
Node node = createNode(rel.otherType, rs, columns, 0);
if (node == null) {
continue;
}
Key primKey = node.getKey();
retval.add(new NodeHandle(primKey));
@ -1115,7 +1134,10 @@ public final class NodeManager {
cache.put(primKey, oldnode);
}
}
fetchJoinedNodes(rs, joins, columns.length);
}
} finally {
// tx.timer.endEvent ("getNodes "+home);
if (stmt != null) {
@ -1147,6 +1169,7 @@ public final class NodeManager {
Connection con = dbm.getConnection();
Statement stmt = con.createStatement();
DbColumn[] columns = dbm.getColumns();
Relation[] joins = dbm.getJoins();
StringBuffer q = dbm.getSelect();
try {
@ -1154,6 +1177,8 @@ public final class NodeManager {
boolean needsQuotes = dbm.needsQuotes(idfield);
q.append("WHERE ");
q.append(dbm.getTableName());
q.append(".");
q.append(idfield);
q.append(" IN (");
@ -1212,7 +1237,10 @@ public final class NodeManager {
while (rs.next()) {
// create new Nodes.
Node node = new Node(dbm, rs, columns, safe);
Node node = createNode(dbm, rs, columns, 0);
if (node == null) {
continue;
}
Key primKey = node.getKey();
// for grouped nodes, collect subnode lists for the intermediary
@ -1260,6 +1288,8 @@ public final class NodeManager {
}
}
}
fetchJoinedNodes(rs, joins, columns.length);
}
// If these are grouped nodes, build the intermediary group nodes
@ -1280,6 +1310,8 @@ public final class NodeManager {
groupnode.lastSubnodeFetch = System.currentTimeMillis();
}
}
} catch (Exception x) {
System.err.println ("ERROR IN PREFETCHNODES: "+x);
} finally {
if (stmt != null) {
try {
@ -1440,9 +1472,12 @@ public final class NodeManager {
stmt = con.createStatement();
DbColumn[] columns = dbm.getColumns();
Relation[] joins = dbm.getJoins();
StringBuffer q = dbm.getSelect();
q.append("WHERE ");
q.append(dbm.getTableName());
q.append(".");
q.append(idfield);
q.append(" = ");
@ -1464,7 +1499,9 @@ public final class NodeManager {
return null;
}
node = new Node(dbm, rs, columns, safe);
node = createNode(dbm, rs, columns, 0);
fetchJoinedNodes(rs, joins, columns.length);
if (rs.next()) {
throw new RuntimeException("More than one value returned by query.");
@ -1521,17 +1558,21 @@ public final class NodeManager {
Connection con = dbm.getConnection();
DbColumn[] columns = dbm.getColumns();
Relation[] joins = dbm.getJoins();
StringBuffer q = dbm.getSelect();
if (home.getSubnodeRelation() != null) {
if (home.getSubnodeRelation() != null && !rel.isComplexReference()) {
// combine our key with the constraints in the manually set subnode relation
q.append("WHERE ");
q.append(dbm.getTableName());
q.append(".");
q.append(rel.accessName);
q.append(" = '");
q.append(escape(kstr));
q.append("'");
q.append(" AND ");
q.append(" AND (");
q.append(home.getSubnodeRelation().trim().substring(5));
q.append(")");
} else {
q.append(rel.buildQuery(home, home.getNonVirtualParent(), kstr,
"WHERE ", false));
@ -1549,7 +1590,9 @@ public final class NodeManager {
return null;
}
node = new Node(rel.otherType, rs, columns, safe);
node = createNode(rel.otherType, rs, columns, 0);
fetchJoinedNodes(rs, joins, columns.length);
if (rs.next()) {
throw new RuntimeException("More than one value returned by query.");
@ -1564,6 +1607,7 @@ public final class NodeManager {
node = existing;
}
}
} finally {
if (stmt != null) {
try {
@ -1577,6 +1621,221 @@ public final class NodeManager {
return node;
}
/**
* Create a new Node from a ResultSet.
*/
public Node createNode(DbMapping dbm, ResultSet rs, DbColumn[] columns, int offset)
throws SQLException, IOException {
Hashtable propMap = new Hashtable();
String id = null;
String name = null;
String protoName = dbm.getTypeName();
DbMapping dbmap = dbm;
Node node = new Node();
for (int i = 0; i < columns.length; i++) {
// set prototype?
if (columns[i].isPrototypeField()) {
protoName = rs.getString(i+1+offset);
if (protoName != null) {
dbmap = getDbMapping(protoName);
if (dbmap == null) {
// invalid prototype name!
System.err.println("Warning: Invalid prototype name: " + protoName +
" - using default");
dbmap = dbm;
}
}
}
// set id?
if (columns[i].isIdField()) {
id = rs.getString(i+1+offset);
// if id == null, the object doesn't actually exist - return null
if (id == null) {
return null;
}
}
// set name?
if (columns[i].isNameField()) {
name = rs.getString(i+1+offset);
}
Relation rel = columns[i].getRelation();
if ((rel == null) ||
((rel.reftype != Relation.PRIMITIVE) &&
(rel.reftype != Relation.REFERENCE))) {
continue;
}
Property newprop = new Property(rel.propName, node);
switch (columns[i].getType()) {
case Types.BIT:
newprop.setBooleanValue(rs.getBoolean(i+1+offset));
break;
case Types.TINYINT:
case Types.BIGINT:
case Types.SMALLINT:
case Types.INTEGER:
newprop.setIntegerValue(rs.getLong(i+1+offset));
break;
case Types.REAL:
case Types.FLOAT:
case Types.DOUBLE:
newprop.setFloatValue(rs.getDouble(i+1+offset));
break;
case Types.DECIMAL:
case Types.NUMERIC:
BigDecimal num = rs.getBigDecimal(i+1+offset);
if (num == null) {
break;
}
if (num.scale() > 0) {
newprop.setFloatValue(num.doubleValue());
} else {
newprop.setIntegerValue(num.longValue());
}
break;
case Types.VARBINARY:
case Types.BINARY:
newprop.setStringValue(rs.getString(i+1+offset));
break;
case Types.LONGVARBINARY:
case Types.LONGVARCHAR:
try {
newprop.setStringValue(rs.getString(i+1+offset));
} catch (SQLException x) {
Reader in = rs.getCharacterStream(i+1+offset);
char[] buffer = new char[2048];
int read = 0;
int r = 0;
while ((r = in.read(buffer, read, buffer.length - read)) > -1) {
read += r;
if (read == buffer.length) {
// grow input buffer
char[] newBuffer = new char[buffer.length * 2];
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
buffer = newBuffer;
}
}
newprop.setStringValue(new String(buffer, 0, read));
}
break;
case Types.CHAR:
case Types.VARCHAR:
case Types.OTHER:
newprop.setStringValue(rs.getString(i+1+offset));
break;
case Types.DATE:
newprop.setDateValue(rs.getDate(i+1+offset));
break;
case Types.TIME:
newprop.setDateValue(rs.getTime(i+1+offset));
break;
case Types.TIMESTAMP:
newprop.setDateValue(rs.getTimestamp(i+1+offset));
break;
case Types.NULL:
newprop.setStringValue(null);
break;
// continue;
default:
newprop.setStringValue(rs.getString(i+1+offset));
break;
}
if (rs.wasNull()) {
newprop.setStringValue(null);
}
propMap.put(rel.propName.toLowerCase(), newprop);
// if the property is a pointer to another node, change the property type to NODE
if ((rel.reftype == Relation.REFERENCE) && rel.usesPrimaryKey()) {
// FIXME: References to anything other than the primary key are not supported
newprop.convertToNodeReference(rel.otherType);
}
// mark property as clean, since it's fresh from the db
newprop.dirty = false;
}
if (id == null) {
return null;
}
node.init(dbmap, id, name, protoName, propMap, safe);
return node;
}
/**
* Fetch nodes that are fetched additionally to another node via join.
*/
private void fetchJoinedNodes(ResultSet rs, Relation[] joins, int offset)
throws ClassNotFoundException, SQLException, IOException {
int resultSetOffset = offset;
// create joined objects
for (int i = 0; i < joins.length; i++) {
DbMapping jdbm = joins[i].otherType;
Node node = createNode(jdbm, rs, jdbm.getColumns(), resultSetOffset);
if (node != null) {
Key primKey = node.getKey();
// register new nodes with the cache. If an up-to-date copy
// existed in the cache, use that.
synchronized (cache) {
Node oldnode = (Node) cache.put(primKey, node);
if ((oldnode != null) &&
(oldnode.getState() != INode.INVALID)) {
// found an ok version in the cache, use it.
cache.put(primKey, oldnode);
}
}
}
resultSetOffset += jdbm.getColumns().length;
}
}
/**
* Get a DbMapping for a given prototype name. This is just a proxy
* method to the app's getDbMapping() method.

View file

@ -190,7 +190,7 @@ public final class Property implements IProperty, Serializable, Cloneable {
/**
*
*
* @return ...
* @return the property's value in its native class
*/
public Object getValue() {
return value;
@ -199,12 +199,24 @@ public final class Property implements IProperty, Serializable, Cloneable {
/**
*
*
* @return ...
* @return the property's type as defined in helma.objectmodel.IProperty.java
*/
public int getType() {
return type;
}
/**
* Directly set the value of this property.
*/
protected void setValue(Object value, int type) {
if (type == NODE) {
unregisterNode();
}
this.value = value;
this.type = type;
dirty = true;
}
/**
*
*
@ -419,7 +431,7 @@ public final class Property implements IProperty, Serializable, Cloneable {
case DATE:
SimpleDateFormat format = new SimpleDateFormat("dd.MM.yy hh:mm:ss");
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
return format.format((Date) value);

View file

@ -21,6 +21,8 @@ import helma.objectmodel.*;
import java.sql.SQLException;
import java.util.Properties;
import java.util.Vector;
import java.util.Map;
import java.util.HashMap;
/**
* This describes how a property of a persistent Object is stored in a
@ -41,6 +43,10 @@ public final class Relation {
// a 1-to-many relation, a field in another table points to objects of this type
public final static int COLLECTION = 2;
// a 1-to-1 reference with multiple or otherwise not-trivial constraints
// this is managed differently than REFERENCE, hence the separate type.
public final static int COMPLEX_REFERENCE = 3;
// direct mapping is a very powerful feature: objects of some types can be directly accessed
// by one of their properties/db fields.
// public final static int DIRECT = 3;
@ -63,7 +69,8 @@ public final class Relation {
boolean readonly;
boolean aggressiveLoading;
boolean aggressiveCaching;
boolean isPrivate;
boolean isPrivate = false;
boolean referencesPrimaryKey = false;
String accessName; // db column used to access objects through this relation
String order;
String groupbyOrder;
@ -102,6 +109,7 @@ public final class Relation {
////////////////////////////////////////////////////////////////////////////////////////////
public void update(String desc, Properties props) {
Application app = ownType.getApplication();
boolean notPrimitive = false;
if ((desc == null) || "".equals(desc.trim())) {
if (propName != null) {
@ -130,7 +138,9 @@ public final class Relation {
prototype = proto;
} else if ("object".equalsIgnoreCase(ref)) {
virtual = false;
reftype = REFERENCE;
if (reftype != COMPLEX_REFERENCE) {
reftype = REFERENCE;
}
} else {
throw new RuntimeException("Invalid property Mapping: " + desc);
}
@ -141,6 +151,12 @@ public final class Relation {
throw new RuntimeException("DbMapping for " + proto +
" not found from " + ownType.typename);
}
// make sure the type we're referring to is up to date!
if (otherType != null && otherType.needsUpdate()) {
otherType.update();
}
} else {
virtual = false;
columnName = desc;
@ -148,13 +164,7 @@ public final class Relation {
}
}
String rdonly = props.getProperty(propName + ".readonly");
if ((rdonly != null) && "true".equalsIgnoreCase(rdonly)) {
readonly = true;
} else {
readonly = false;
}
readonly = "true".equalsIgnoreCase(props.getProperty(propName + ".readonly"));
isPrivate = "true".equalsIgnoreCase(props.getProperty(propName + ".private"));
@ -167,6 +177,33 @@ public final class Relation {
constraints = new Constraint[newConstraints.size()];
newConstraints.copyInto(constraints);
if (reftype == REFERENCE || reftype == COMPLEX_REFERENCE) {
if (constraints.length == 0) {
referencesPrimaryKey = true;
} else {
boolean rprim = false;
for (int i=0; i<constraints.length; i++) {
if (constraints[0].foreignKeyIsPrimary()) {
rprim = true;
}
}
referencesPrimaryKey = rprim;
}
// check if this is a non-trivial reference
if (constraints.length > 0 && !usesPrimaryKey()) {
reftype = COMPLEX_REFERENCE;
} else {
reftype = REFERENCE;
}
}
if (reftype == COLLECTION) {
referencesPrimaryKey = (accessName == null) ||
accessName.equalsIgnoreCase(otherType.getIDField());
}
// if DbMapping for virtual nodes has already been created,
// update its subnode relation.
// FIXME: needs to be synchronized?
@ -175,6 +212,8 @@ public final class Relation {
virtualMapping.subRelation = getVirtualSubnodeRelation();
virtualMapping.propRelation = getVirtualPropertyRelation();
}
} else {
referencesPrimaryKey = false;
}
}
@ -199,8 +238,13 @@ public final class Relation {
// get additional filter property
filter = props.getProperty(propName + ".filter");
if ((filter != null) && (filter.trim().length() == 0)) {
filter = null;
if (filter != null) {
if (filter.trim().length() == 0) {
filter = null;
} else {
// parenthesise filter
filter = "("+filter+")";
}
}
// get max size of collection
@ -237,7 +281,7 @@ public final class Relation {
}
// aggressive loading and caching is not supported for groupby-nodes
aggressiveLoading = aggressiveCaching = false;
// aggressiveLoading = aggressiveCaching = false;
}
// check if subnode condition should be applied for property relations
@ -248,9 +292,19 @@ public final class Relation {
String foreign = props.getProperty(propName + ".foreign");
if ((local != null) && (foreign != null)) {
cnst.addElement(new Constraint(local, otherType.getTableName(), foreign, false));
cnst.addElement(new Constraint(local, foreign, false));
columnName = local;
}
// parse additional contstraints from *.1 to *.9
for (int i=1; i<10; i++) {
local = props.getProperty(propName + ".local."+i);
foreign = props.getProperty(propName + ".foreign."+i);
if ((local != null) && (foreign != null)) {
cnst.addElement(new Constraint(local, foreign, false));
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -283,6 +337,13 @@ public final class Relation {
return reftype == COLLECTION;
}
/**
* Returns true if this Relation describes a complex object reference property
*/
public boolean isComplexReference() {
return reftype == COMPLEX_REFERENCE;
}
/**
* Tell wether the property described by this relation is to be handled as private, i.e.
* a change on it should not result in any changed object/collection relations.
@ -291,14 +352,30 @@ public final class Relation {
return isPrivate;
}
/**
* Check whether aggressive loading is set for this relation
*/
public boolean loadAggressively() {
return aggressiveLoading;
}
/**
* Returns the number of constraints for this relation.
*/
public int countConstraints() {
if (constraints == null)
return 0;
return constraints.length;
}
/**
* Returns true if the object represented by this Relation has to be
* created dynamically by the Helma objectmodel runtime as a virtual
* node. Virtual nodes are objects which are only generated on demand
* and never stored to a persistent storage.
*/
public boolean createPropertyOnDemand() {
return virtual || (accessName != null) || (groupby != null);
public boolean createOnDemand() {
return virtual || (accessName != null) || (groupby != null) || isComplexReference();
}
/**
@ -355,6 +432,15 @@ public final class Relation {
return columnType;
}
/**
* Get the group for a collection relation, if defined.
*
* @return the name of the column used to group child objects, if any.
*/
public String getGroup() {
return groupby;
}
/**
* Add a constraint to the current list of constraints
*/
@ -374,21 +460,11 @@ public final class Relation {
/**
*
*
* @return ...
* @return true if the foreign key used for this relation is the
* other object's primary key.
*/
public boolean usesPrimaryKey() {
if (otherType != null) {
if (reftype == REFERENCE) {
return (constraints.length == 1) && constraints[0].foreignKeyIsPrimary();
}
if (reftype == COLLECTION) {
return (accessName == null) ||
accessName.equalsIgnoreCase(otherType.getIDField());
}
}
return false;
return referencesPrimaryKey;
}
/**
@ -435,13 +511,13 @@ public final class Relation {
return null;
}
// if the collection node is prototyped, return the app's DbMapping
// if the collection node is prototyped, return the app's DbMapping
// for that prototype
if (prototype != null) {
return otherType;
}
// create a synthetic DbMapping that describes how to fetch the
// create a synthetic DbMapping that describes how to fetch the
// collection's child objects.
if (virtualMapping == null) {
virtualMapping = new DbMapping(ownType.app);
@ -510,7 +586,7 @@ public final class Relation {
vr.prototype = groupbyPrototype;
vr.filter = filter;
vr.constraints = constraints;
vr.addConstraint(new Constraint(null, null, groupby, true));
vr.addConstraint(new Constraint(null, groupby, true));
vr.aggressiveLoading = aggressiveLoading;
vr.aggressiveCaching = aggressiveCaching;
@ -531,7 +607,7 @@ public final class Relation {
vr.prototype = groupbyPrototype;
vr.filter = filter;
vr.constraints = constraints;
vr.addConstraint(new Constraint(null, null, groupby, true));
vr.addConstraint(new Constraint(null, groupby, true));
return vr;
}
@ -545,11 +621,13 @@ public final class Relation {
StringBuffer q = new StringBuffer();
String prefix = pre;
if (kstr != null) {
if (kstr != null && !isComplexReference()) {
q.append(prefix);
String accessColumn = (accessName == null) ? otherType.getIDField() : accessName;
q.append(otherType.getTableName());
q.append(".");
q.append(accessColumn);
q.append(" = ");
@ -602,21 +680,39 @@ public final class Relation {
public String renderConstraints(INode home, INode nonvirtual)
throws SQLException {
StringBuffer q = new StringBuffer();
String suffix = " AND ";
String prefix = " AND ";
for (int i = 0; i < constraints.length; i++) {
q.append(prefix);
constraints[i].addToQuery(q, home, nonvirtual);
q.append(suffix);
}
if (filter != null) {
q.append(prefix);
q.append(filter);
q.append(suffix);
}
return q.toString();
}
public void renderJoinConstraints(StringBuffer select) {
for (int i = 0; i < constraints.length; i++) {
select.append(ownType.getTableName());
select.append(".");
select.append(constraints[i].localName);
select.append(" = _HLM_");
select.append(propName);
select.append(".");
select.append(constraints[i].foreignName);
if (i == constraints.length-1) {
select.append(" ");
} else {
select.append(" AND ");
}
}
}
/**
* Get the order section to use for this relation
*/
@ -656,16 +752,14 @@ public final class Relation {
if (propname != null) {
INode home = constraints[i].isGroupby ? parent
: parent.getNonVirtualParent();
String localName = constraints[i].localName;
String value = null;
if ((localName == null) ||
localName.equalsIgnoreCase(ownType.getIDField())) {
if (constraints[i].localKeyIsPrimary(home.getDbMapping())) {
value = home.getID();
} else if (ownType.isRelational()) {
value = home.getString(ownType.columnNameToProperty(localName));
value = home.getString(constraints[i].localProperty());
} else {
value = home.getString(localName);
value = home.getString(constraints[i].localName);
}
if ((value != null) && !value.equals(child.getString(propname))) {
@ -682,8 +776,7 @@ public final class Relation {
* appropriate properties
*/
public void setConstraints(Node parent, Node child) {
INode home = parent.getNonVirtualParent();
Node home = parent.getNonVirtualParent();
for (int i = 0; i < constraints.length; i++) {
// don't set groupby constraints since we don't know if the
// parent node is the base node or a group node
@ -691,14 +784,26 @@ public final class Relation {
continue;
}
Relation crel = otherType.columnNameToRelation(constraints[i].foreignName);
// check if we update the local or the other object, depending on
// whether the primary key of either side is used.
if (constraints[i].foreignKeyIsPrimary()) {
String localProp = constraints[i].localProperty();
if (localProp == null) {
System.err.println ("Error: column "+constraints[i].localName+
" must be mapped in order to be used as constraint in "+
Relation.this);
} else {
home.setString(localProp, child.getID());
}
continue;
}
Relation crel = otherType.columnNameToRelation(constraints[i].foreignName);
if (crel != null) {
// INode home = constraints[i].isGroupby ? parent : nonVirtual;
String localName = constraints[i].localName;
if ((localName == null) ||
localName.equalsIgnoreCase(ownType.getIDField())) {
if (constraints[i].localKeyIsPrimary(home.getDbMapping())) {
// only set node if property in child object is defined as reference.
if (crel.reftype == REFERENCE) {
INode currentValue = child.getNode(crel.propName);
@ -717,22 +822,92 @@ public final class Relation {
child.setString(crel.propName, home.getID());
}
} else if (crel.reftype == PRIMITIVE) {
String value = null;
Property prop = null;
if (ownType.isRelational()) {
value = home.getString(ownType.columnNameToProperty(localName));
prop = home.getProperty(constraints[i].localProperty());
} else {
value = home.getString(localName);
prop = home.getProperty(constraints[i].localName);
}
if (value != null) {
child.setString(crel.propName, value);
if (prop != null) {
child.set(crel.propName, prop.getValue(), prop.getType());
}
}
}
}
}
/**
* Unset the constraints that link two objects together.
*/
public void unsetConstraints(Node parent, INode child) {
Node home = parent.getNonVirtualParent();
for (int i = 0; i < constraints.length; i++) {
// don't set groupby constraints since we don't know if the
// parent node is the base node or a group node
if (constraints[i].isGroupby) {
continue;
}
// check if we update the local or the other object, depending on
// whether the primary key of either side is used.
if (constraints[i].foreignKeyIsPrimary()) {
String localProp = constraints[i].localProperty();
if (localProp != null) {
home.setString(localProp, null);
}
continue;
}
Relation crel = otherType.columnNameToRelation(constraints[i].foreignName);
if (crel != null) {
// INode home = constraints[i].isGroupby ? parent : nonVirtual;
if (constraints[i].localKeyIsPrimary(home.getDbMapping())) {
// only set node if property in child object is defined as reference.
if (crel.reftype == REFERENCE) {
INode currentValue = child.getNode(crel.propName);
if ((currentValue == home)) {
child.setString(crel.propName, null);
}
} else if (crel.reftype == PRIMITIVE) {
child.setString(crel.propName, null);
}
} else if (crel.reftype == PRIMITIVE) {
Property prop = null;
if (ownType.isRelational()) {
prop = home.getProperty(constraints[i].localProperty());
} else {
prop = home.getProperty(constraints[i].localName);
}
if (prop != null) {
child.setString(crel.propName, null);
}
}
}
}
}
/**
* Returns a map containing the key/value pairs for a specific Node
*/
public Map getKeyParts(INode home) {
Map map = new HashMap();
for (int i=0; i<constraints.length; i++) {
if (ownType.getIDField().equals(constraints[i].localName)) {
map.put(constraints[i].foreignName, home.getID());
} else {
map.put(constraints[i].foreignName, home.getString(constraints[i].localProperty()));
}
}
return map;
}
// a utility method to escape single quotes
String escape(String str) {
if (str == null) {
@ -766,13 +941,20 @@ public final class Relation {
*/
public String toString() {
String c = "";
String spacer = "";
if (constraints != null) {
for (int i = 0; i < constraints.length; i++)
c = " constraints: ";
for (int i = 0; i < constraints.length; i++) {
c += spacer;
c += constraints[i].toString();
spacer = ", ";
}
}
return "Relation[" + ownType + "." + propName + ">" + otherType + "]" + c;
String target = otherType == null ? columnName : otherType.toString();
return "Relation " + ownType+"."+propName + " -> " + target + c;
}
/**
@ -781,13 +963,11 @@ public final class Relation {
*/
class Constraint {
String localName;
String tableName;
String foreignName;
boolean isGroupby;
Constraint(String local, String table, String foreign, boolean groupby) {
Constraint(String local, String foreign, boolean groupby) {
localName = local;
tableName = table;
foreignName = foreign;
isGroupby = groupby;
}
@ -806,6 +986,8 @@ public final class Relation {
local = ref.getString(homeprop);
}
q.append(otherType.getTableName());
q.append(".");
q.append(foreignName);
q.append(" = ");
@ -823,6 +1005,11 @@ public final class Relation {
foreignName.equalsIgnoreCase(otherType.getIDField());
}
public boolean localKeyIsPrimary(DbMapping homeMapping) {
return (homeMapping == null) || (localName == null) ||
localName.equalsIgnoreCase(homeMapping.getIDField());
}
public String foreignProperty() {
return otherType.columnNameToProperty(foreignName);
}
@ -832,7 +1019,7 @@ public final class Relation {
}
public String toString() {
return ownType + "." + localName + "=" + tableName + "." + foreignName;
return localName + "=" + otherType.getTypeName() + "." + foreignName;
}
}
}

View file

@ -238,7 +238,7 @@ public class ESNode extends ObjectPrototype {
}
if (!(what[1] instanceof ESNode)) {
throw new EcmaScriptException("Can ony add Node objects as subnodes");
throw new EcmaScriptException("Can only add Node objects as subnodes");
}
ESNode esn = (ESNode) what[1];
@ -341,23 +341,6 @@ public class ESNode extends ObjectPrototype {
((Node) node).prefetchChildren(start, length);
}
/**
* This used to be different from add(), it isn't anymore. It's left here for
* compatibility.
*/
public boolean link(ESValue[] args) {
checkNode();
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof ESNode) {
ESNode esn = (ESNode) args[i];
node.addNode(esn.getNode());
}
}
return true;
}
/**
*

View file

@ -107,7 +107,6 @@ public final class HopExtension {
esNodePrototype.putHiddenProperty("addAt", new NodeAddAt("addAt", evaluator, fp));
esNodePrototype.putHiddenProperty("remove",
new NodeRemove("remove", evaluator, fp));
esNodePrototype.putHiddenProperty("link", new NodeLink("link", evaluator, fp));
esNodePrototype.putHiddenProperty("list", new NodeList("list", evaluator, fp));
esNodePrototype.putHiddenProperty("set", new NodeSet("set", evaluator, fp));
esNodePrototype.putHiddenProperty("get", new NodeGet("get", evaluator, fp));
@ -270,19 +269,6 @@ public final class HopExtension {
}
}
class NodeLink extends BuiltinFunctionObject {
NodeLink(String name, Evaluator evaluator, FunctionPrototype fp) {
super(fp, evaluator, name, 1);
}
public ESValue callFunction(ESObject thisObject, ESValue[] arguments)
throws EcmaScriptException {
ESNode node = (ESNode) thisObject;
return ESBoolean.makeBoolean(node.link(arguments));
}
}
class NodeList extends BuiltinFunctionObject {
NodeList(String name, Evaluator evaluator, FunctionPrototype fp) {
super(fp, evaluator, name, 0);

View file

@ -16,6 +16,7 @@
/* Portierung von helma.asp.AspClient auf Servlets */
/* Author: Raphael Spannocchi Datum: 27.11.1998 */
package helma.servlet;
import helma.framework.*;
@ -292,6 +293,7 @@ public abstract class AbstractServletClient extends HttpServlet {
if (debug) {
sendError(response, response.SC_INTERNAL_SERVER_ERROR,
"Error in request handler:" + x);
x.printStackTrace();
} else {
sendError(response, response.SC_INTERNAL_SERVER_ERROR,
"The server encountered an error while processing your request. " +

View file

@ -32,9 +32,6 @@ public final class EmbeddedServletClient extends AbstractServletClient {
private Application app = null;
private String appName;
// The path where this servlet is mounted
String mountpoint;
/**
* Creates a new EmbeddedServletClient object.
*/
@ -56,12 +53,6 @@ public final class EmbeddedServletClient extends AbstractServletClient {
if (appName == null) {
throw new ServletException("Application name not set in init parameters");
}
mountpoint = init.getInitParameter("mountpoint");
if (mountpoint == null) {
mountpoint = "/" + appName;
}
}
ResponseTrans execute(RequestTrans req) throws Exception {

View file

@ -38,7 +38,7 @@ package helma.util;
* software without prior written permission. For written
* permission, please contact support@protomatter.com.
*
* 5. Products derived from this software may not be called "Protomatter",
* 5. Products derived from this software may not be called "Protomatter",
* nor may "Protomatter" appear in their name, without prior written
* permission of the Protomatter Software Project
* (support@protomatter.com).
@ -62,7 +62,7 @@ import java.util.*;
/**
* A cron entry, derived from Protomatter's CronEntry class.
* This class encapsulates a function call, a timeout value
* This class encapsulates a function call, a timeout value
* and a specification for when the given event should be
* delivered to the given topics. The specification of when
* the event should be delivered is based on the UNIX cron
@ -72,17 +72,18 @@ import java.util.*;
public class CronJob {
// used as the value in hashtables
private static Object value = new Object();
private static Hashtable all = new Hashtable ();
private static HashSet all = new HashSet (2);
private static String ALL_VALUE = "*";
static {
all.add (ALL_VALUE);
}
private Hashtable year;
private Hashtable month;
private Hashtable day;
private Hashtable weekday;
private Hashtable hour;
private Hashtable minute;
private HashSet year;
private HashSet month;
private HashSet day;
private HashSet weekday;
private HashSet hour;
private HashSet minute;
private String name = null;
private String function = null;
@ -161,21 +162,22 @@ public class CronJob {
*/
public static CronJob newJob (String functionName, String year, String month, String day, String weekday, String hour, String minute) {
public static CronJob newJob (String functionName, String year, String month,
String day, String weekday, String hour, String minute) {
CronJob job = new CronJob (functionName);
job.setFunction (functionName);
if (year != null)
parseYear (job, year);
job.parseYear (year);
if (month != null)
parseMonth (job, month);
job.parseMonth (month);
if (day != null)
parseDay (job, day);
job.parseDay (day);
if (weekday != null)
parseWeekDay (job, weekday);
job.parseWeekDay (weekday);
if (hour != null)
parseHour (job, hour);
job.parseHour (hour);
if (minute != null)
parseMinute (job, minute);
job.parseMinute (minute);
return job;
}
@ -203,19 +205,19 @@ public class CronJob {
if (jobSpec.equalsIgnoreCase("function")) {
job.setFunction(value);
} else if (jobSpec.equalsIgnoreCase("year")) {
parseYear (job, value);
job.parseYear (value);
} else if (jobSpec.equalsIgnoreCase("month")) {
parseMonth (job, value);
job.parseMonth (value);
} else if (jobSpec.equalsIgnoreCase("day")) {
parseDay (job, value);
job.parseDay (value);
} else if (jobSpec.equalsIgnoreCase("weekday")) {
parseWeekDay (job, value);
job.parseWeekDay (value);
} else if (jobSpec.equalsIgnoreCase("hour")) {
parseHour (job, value);
job.parseHour (value);
} else if (jobSpec.equalsIgnoreCase("minute")) {
parseMinute (job, value);
job.parseMinute (value);
} else if (jobSpec.equalsIgnoreCase("timeout")) {
parseTimeout (job, value);
job.parseTimeout (value);
}
} catch (NoSuchElementException nsee) {
}
@ -249,9 +251,9 @@ public class CronJob {
}
public static void parseYear (CronJob job, String value) {
public void parseYear (String value) {
if (value.equals("*")) {
job.setAllYears(true);
setAllYears(true);
} else {
StringTokenizer st = new StringTokenizer(value.trim(), ",");
while (st.hasMoreTokens()) {
@ -260,54 +262,54 @@ public class CronJob {
int start = Integer.parseInt(s.substring(0, s.indexOf("-")));
int finish = Integer.parseInt(s.substring(s.indexOf("-") +1));
for (int i=start; i<=finish; i++) {
job.addYear(i);
addYear(i);
}
} else {
int y = Integer.parseInt(s);
job.addYear(y);
addYear(y);
}
}
}
}
public static void parseMonth (CronJob job, String value) {
public void parseMonth (String value) {
if (value.equals("*")) {
job.setAllMonths(true);
setAllMonths(true);
} else {
StringTokenizer st = new StringTokenizer(value.trim(), ",");
while (st.hasMoreTokens()) {
String m = st.nextToken();
if (m.equalsIgnoreCase("january"))
job.addMonth(Calendar.JANUARY);
addMonth(Calendar.JANUARY);
if (m.equalsIgnoreCase("february"))
job.addMonth(Calendar.FEBRUARY);
addMonth(Calendar.FEBRUARY);
if (m.equalsIgnoreCase("march"))
job.addMonth(Calendar.MARCH);
addMonth(Calendar.MARCH);
if (m.equalsIgnoreCase("april"))
job.addMonth(Calendar.APRIL);
addMonth(Calendar.APRIL);
if (m.equalsIgnoreCase("may"))
job.addMonth(Calendar.MAY);
addMonth(Calendar.MAY);
if (m.equalsIgnoreCase("june"))
job.addMonth(Calendar.JUNE);
addMonth(Calendar.JUNE);
if (m.equalsIgnoreCase("july"))
job.addMonth(Calendar.JULY);
addMonth(Calendar.JULY);
if (m.equalsIgnoreCase("august"))
job.addMonth(Calendar.AUGUST);
addMonth(Calendar.AUGUST);
if (m.equalsIgnoreCase("september"))
job.addMonth(Calendar.SEPTEMBER);
addMonth(Calendar.SEPTEMBER);
if (m.equalsIgnoreCase("october"))
job.addMonth(Calendar.OCTOBER);
addMonth(Calendar.OCTOBER);
if (m.equalsIgnoreCase("november"))
job.addMonth(Calendar.NOVEMBER);
addMonth(Calendar.NOVEMBER);
if (m.equalsIgnoreCase("december"))
job.addMonth(Calendar.DECEMBER);
addMonth(Calendar.DECEMBER);
}
}
}
public static void parseDay (CronJob job, String day) {
public void parseDay (String day) {
if (day.equals("*")) {
job.setAllDays(true);
setAllDays(true);
} else {
StringTokenizer st = new StringTokenizer(day.trim(), ",");
while (st.hasMoreTokens()) {
@ -316,46 +318,46 @@ public class CronJob {
int start = Integer.parseInt(s.substring(0, s.indexOf("-")));
int finish = Integer.parseInt(s.substring(s.indexOf("-") +1));
for (int i=start; i<=finish; i++) {
job.addDay(i);
addDay(i);
}
} else {
int d = Integer.parseInt(s);
job.addDay(d);
addDay(d);
}
}
}
}
public static void parseWeekDay (CronJob job, String weekday) {
public void parseWeekDay (String weekday) {
if (weekday.equals("*")) {
job.setAllWeekdays(true);
setAllWeekdays(true);
} else {
StringTokenizer st = new StringTokenizer(weekday.trim(), ",");
while (st.hasMoreTokens()) {
String d = st.nextToken();
if (d.equalsIgnoreCase("monday"))
job.addWeekday(Calendar.MONDAY);
addWeekday(Calendar.MONDAY);
if (d.equalsIgnoreCase("tuesday"))
job.addWeekday(Calendar.TUESDAY);
addWeekday(Calendar.TUESDAY);
if (d.equalsIgnoreCase("wednesday"))
job.addWeekday(Calendar.WEDNESDAY);
addWeekday(Calendar.WEDNESDAY);
if (d.equalsIgnoreCase("thursday"))
job.addWeekday(Calendar.THURSDAY);
addWeekday(Calendar.THURSDAY);
if (d.equalsIgnoreCase("friday"))
job.addWeekday(Calendar.FRIDAY);
addWeekday(Calendar.FRIDAY);
if (d.equalsIgnoreCase("saturday"))
job.addWeekday(Calendar.SATURDAY);
addWeekday(Calendar.SATURDAY);
if (d.equalsIgnoreCase("sunday"))
job.addWeekday(Calendar.SUNDAY);
addWeekday(Calendar.SUNDAY);
}
}
}
public static void parseHour (CronJob job, String hour) {
public void parseHour (String hour) {
if (hour.equals("*")) {
job.setAllHours(true);
setAllHours(true);
} else {
StringTokenizer st = new StringTokenizer(hour.trim (), ",");
while (st.hasMoreTokens()) {
@ -364,20 +366,20 @@ public class CronJob {
int start = Integer.parseInt(s.substring(0, s.indexOf("-")));
int finish = Integer.parseInt(s.substring(s.indexOf("-") +1));
for (int i=start; i<=finish; i++) {
job.addHour(i);
addHour(i);
}
} else {
int h = Integer.parseInt(s);
job.addHour(h);
addHour(h);
}
}
}
}
public static void parseMinute (CronJob job, String minute) {
public void parseMinute (String minute) {
if (minute.equals("*")) {
job.setAllMinutes(true);
setAllMinutes(true);
} else {
StringTokenizer st = new StringTokenizer(minute.trim (), ",");
while (st.hasMoreTokens()) {
@ -386,46 +388,44 @@ public class CronJob {
int start = Integer.parseInt(s.substring(0, s.indexOf("-")));
int finish = Integer.parseInt(s.substring(s.indexOf("-") +1));
for (int i=start; i<=finish; i++) {
job.addMinute(i);
addMinute(i);
}
} else {
int m = Integer.parseInt(s);
job.addMinute(m);
addMinute(m);
}
}
}
}
public static void parseTimeout (CronJob job, String timeout) {
public void parseTimeout (String timeout) {
long timeoutValue = 1000 * Long.valueOf(timeout).longValue ();
job.setTimeout (timeoutValue);
setTimeout (timeoutValue);
}
public static long nextFullMinute () {
long now = System.currentTimeMillis();
long millisAfterMinute = (now % 60000);
return (now + 60000 - millisAfterMinute);
}
public static long nextFullMinute () {
long now = System.currentTimeMillis();
long millisAfterMinute = (now % 60000);
return (now + 60000 - millisAfterMinute);
}
public static long millisToNextFullMinute () {
long now = System.currentTimeMillis();
long millisAfterMinute = (now % 60000);
return (60000 - millisAfterMinute);
}
public static long millisToNextFullMinute () {
long now = System.currentTimeMillis();
long millisAfterMinute = (now % 60000);
return (60000 - millisAfterMinute);
}
/**
* Create an empty CronJob.
*/
public CronJob (String name) {
this.name = name;
all.put (ALL_VALUE, value);
year = new Hashtable (all);
month = new Hashtable (all);
day = new Hashtable (all);
weekday = new Hashtable (all);
hour = new Hashtable (all);
minute = new Hashtable (all);
year = new HashSet (all);
month = new HashSet (all);
day = new HashSet (all);
weekday = new HashSet (all);
hour = new HashSet (all);
minute = new HashSet (all);
}
/**
@ -439,29 +439,29 @@ public class CronJob {
// try and short-circuit as fast as possible.
Integer theYear = new Integer(cal.get(Calendar.YEAR));
if (!year.containsKey(ALL_VALUE) && !year.containsKey(theYear))
if (!year.contains(ALL_VALUE) && !year.contains(theYear))
return false;
Integer theMonth = new Integer(cal.get(Calendar.MONTH));
if (!month.containsKey(ALL_VALUE) && !month.containsKey(theMonth))
if (!month.contains(ALL_VALUE) && !month.contains(theMonth))
return false;
Integer theDay = new Integer(cal.get(Calendar.DAY_OF_MONTH));
if (!day.containsKey(ALL_VALUE) && !day.containsKey(theDay))
if (!day.contains(ALL_VALUE) && !day.contains(theDay))
return false;
Integer theWeekDay = new Integer(cal.get(Calendar.DAY_OF_WEEK));
if (!weekday.containsKey(ALL_VALUE) && !weekday.containsKey(theWeekDay))
if (!weekday.contains(ALL_VALUE) && !weekday.contains(theWeekDay))
return false;
Integer theHour = new Integer(cal.get(Calendar.HOUR_OF_DAY));
if (!hour.containsKey(ALL_VALUE) && !hour.containsKey(theHour))
if (!hour.contains(ALL_VALUE) && !hour.contains(theHour))
return false;
Integer theMinute = new Integer(cal.get(Calendar.MINUTE));
if (!minute.containsKey(ALL_VALUE) && !minute.containsKey(theMinute))
if (!minute.contains(ALL_VALUE) && !minute.contains(theMinute))
return false;
return true;
}
@ -472,7 +472,7 @@ public class CronJob {
public void addYear(int year)
{
this.year.remove(ALL_VALUE);
this.year.put(new Integer(year), value);
this.year.add(new Integer(year));
}
/**
@ -494,7 +494,7 @@ public class CronJob {
public void setAllYears(boolean set)
{
if (set)
this.year.put(ALL_VALUE, value);
this.year.add(ALL_VALUE);
else
this.year.remove(ALL_VALUE);
}
@ -508,7 +508,7 @@ public class CronJob {
public void addMonth(int month)
{
this.month.remove(ALL_VALUE);
this.month.put(new Integer(month), value);
this.month.add(new Integer(month));
}
/**
@ -532,7 +532,7 @@ public class CronJob {
public void setAllMonths(boolean set)
{
if (set)
this.month.put(ALL_VALUE, value);
this.month.add(ALL_VALUE);
else
this.month.remove(ALL_VALUE);
}
@ -544,7 +544,7 @@ public class CronJob {
public void addDay(int day)
{
this.day.remove(ALL_VALUE);
this.day.put(new Integer(day), value);
this.day.add(new Integer(day));
}
/**
@ -566,7 +566,7 @@ public class CronJob {
public void setAllDays(boolean set)
{
if (set)
this.day.put(ALL_VALUE, value);
this.day.add(ALL_VALUE);
else
this.day.remove(ALL_VALUE);
}
@ -580,7 +580,7 @@ public class CronJob {
public void addWeekday(int weekday)
{
this.weekday.remove(ALL_VALUE);
this.weekday.put(new Integer(weekday), value);
this.weekday.add(new Integer(weekday));
}
/**
@ -604,7 +604,7 @@ public class CronJob {
public void setAllWeekdays(boolean set)
{
if (set)
this.weekday.put(ALL_VALUE, value);
this.weekday.add(ALL_VALUE);
else
this.weekday.remove(ALL_VALUE);
}
@ -616,7 +616,7 @@ public class CronJob {
public void addHour(int hour)
{
this.hour.remove(ALL_VALUE);
this.hour.put(new Integer(hour), value);
this.hour.add(new Integer(hour));
}
/**
@ -638,7 +638,7 @@ public class CronJob {
public void setAllHours(boolean set)
{
if (set)
this.hour.put(ALL_VALUE, value);
this.hour.add(ALL_VALUE);
else
this.hour.remove(ALL_VALUE);
}
@ -650,7 +650,7 @@ public class CronJob {
public void addMinute(int minute)
{
this.minute.remove(ALL_VALUE);
this.minute.put(new Integer(minute), value);
this.minute.add(new Integer(minute));
}
/**
@ -672,12 +672,11 @@ public class CronJob {
public void setAllMinutes(boolean set)
{
if (set)
this.minute.put(ALL_VALUE, value);
this.minute.add(ALL_VALUE);
else
this.minute.remove(ALL_VALUE);
}
/**
* Set this entry's name
*/
@ -727,9 +726,10 @@ public class CronJob {
{
return this.timeout;
}
public String toString () {
return "[CronJob " + name + "]";
}
public String toString ()
{
return "[CronJob " + name + "]";
}
}

View file

@ -310,6 +310,7 @@ public final class HtmlEncoder {
swallowTwo.add("ul");
/// to be treated as block level elements
swallowTwo.add("br");
swallowTwo.add("dd");
swallowTwo.add("dt");
swallowTwo.add("frameset");
@ -322,6 +323,26 @@ public final class HtmlEncoder {
swallowAll.add("tr");
}
// set of tags that are always empty
static final HashSet emptyTags = new HashSet();
static {
emptyTags.add("area");
emptyTags.add("base");
emptyTags.add("basefont");
emptyTags.add("br");
emptyTags.add("col");
emptyTags.add("frame");
emptyTags.add("hr");
emptyTags.add("img");
emptyTags.add("input");
emptyTags.add("isindex");
emptyTags.add("link");
emptyTags.add("meta");
emptyTags.add("param");
}
/**
* Do "smart" encodging on a string. This means that valid HTML entities and tags,
* Helma macros and HTML comments are passed through unescaped, while
@ -470,9 +491,12 @@ public final class HtmlEncoder {
continue;
} else if (t > 1) {
for (int k = 1; k < t; k++) {
ret.append("</");
ret.append(openTags.pop());
ret.append(">");
Object tag = openTags.pop();
if (!emptyTags.contains(tag)) {
ret.append("</");
ret.append(tag);
ret.append(">");
}
}
}
@ -496,7 +520,7 @@ public final class HtmlEncoder {
// if (i < l-2)
}
if ((linebreaks > 0) && !Character.isWhitespace(c)) {
if ((linebreaks > 0 || swallowLinebreaks > 0) && !Character.isWhitespace(c)) {
if (!insidePreTag && (linebreaks > swallowLinebreaks)) {
linebreaks -= swallowLinebreaks;
@ -659,9 +683,12 @@ public final class HtmlEncoder {
if (o > 0) {
for (int k = 0; k < o; k++) {
ret.append("</");
ret.append(openTags.pop());
ret.append(">");
Object tag = openTags.pop();
if (!emptyTags.contains(tag)) {
ret.append("</");
ret.append(tag);
ret.append(">");
}
}
}

View file

@ -0,0 +1,51 @@
/*
* 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.util.StringTokenizer;
/**
* Utility class for String manipulation.
*/
public class StringUtils {
/**
* Split a string into an array of strings. Use comma and space
* as delimiters.
*/
public static String[] split(String str) {
return split(str, ", \t\n\r\f");
}
/**
* Split a string into an array of strings.
*/
public static String[] split(String str, String delim) {
if (str == null) {
return new String[0];
}
StringTokenizer st = new StringTokenizer(str, delim);
String[] s = new String[st.countTokens()];
for (int i=0; i<s.length; i++) {
s[i] = st.nextToken();
}
return s;
}
}