Merging changes from 1.2.4 to 1.2.5
This commit is contained in:
parent
65e4db3d8a
commit
676b70519d
32 changed files with 1498 additions and 865 deletions
|
@ -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)";
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
} */
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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 + "*";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 + "]");
|
||||
|
|
|
@ -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:
|
||||
|
|
124
src/helma/objectmodel/db/MultiKey.java
Normal file
124
src/helma/objectmodel/db/MultiKey.java
Normal 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 + "]");
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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. " +
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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(">");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
51
src/helma/util/StringUtils.java
Normal file
51
src/helma/util/StringUtils.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue