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
|
@ -1,10 +1,13 @@
|
|||
# List of apps to start.
|
||||
|
||||
# mount antville as root application
|
||||
antville
|
||||
antville.mountpoint = /
|
||||
antville.static = static
|
||||
antville.staticMountpoint = /static
|
||||
|
||||
# mount antville as /managehop to avoid
|
||||
# conflict with antville's manage.hac action
|
||||
manage
|
||||
manage.mountpoint = /manage/hop
|
||||
|
||||
# mount antville as root application
|
||||
antville
|
||||
antville.mountpoint = /
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<target name="init">
|
||||
<property name="Name" value="helma"/>
|
||||
<property name="year" value="1998-${year}"/>
|
||||
<property name="version" value="1.3cvs"/>
|
||||
<property name="version" value="1.2.5"/>
|
||||
<property name="project" value="helma"/>
|
||||
<property name="build.compiler" value="classic"/>
|
||||
|
||||
|
@ -28,8 +28,7 @@
|
|||
|
||||
<property name="jar.name" value="${project}"/>
|
||||
<property name="package.name" value="${project}-${version}"/>
|
||||
<property name="antclick.name" value="antclick-1.0cvs"/>
|
||||
<property name="antville.name" value="antville-1.0cvs"/>
|
||||
<property name="antclick.name" value="antclick-1.0.1-pre1"/>
|
||||
|
||||
<property name="debug" value="on"/>
|
||||
<property name="optimize" value="on"/>
|
||||
|
@ -68,8 +67,6 @@
|
|||
<echo message=" docs --> tries to retrieve the HTML documentation "/>
|
||||
<echo message=" (may need proxy settings in startscript)"/>
|
||||
<echo message=" package --> generates the distribution (zip and tar.gz)"/>
|
||||
<echo message=" antville --> generates the antville-distribution"/>
|
||||
<echo message=" (zip and tar.gz)"/>
|
||||
<echo message=" antclick --> generates the distribution (zip and tar.gz)"/>
|
||||
<echo message=" with antville preconfigured"/>
|
||||
<echo message=" app [name] --> gets an application from the cvs and zips it"/>
|
||||
|
@ -210,8 +207,8 @@
|
|||
<!-- copy all libraries except helma-YYYYMMDD.jar -->
|
||||
<copy todir="${build.work}/lib">
|
||||
<fileset dir="${home.dir}/lib">
|
||||
<exclude name="**/helma*.jar" />
|
||||
<include name="**/*.jar" />
|
||||
<exclude name="helma*.jar" />
|
||||
<include name="*.jar" />
|
||||
</fileset>
|
||||
</copy>
|
||||
|
||||
|
@ -251,17 +248,6 @@
|
|||
|
||||
<antcall target="package-manage" />
|
||||
|
||||
<!-- write out apps.properties file -->
|
||||
<echo file="${build.work}/apps.properties" append="false">
|
||||
# list of applications to be started by helma
|
||||
base
|
||||
base.mountpoint = /
|
||||
bloggerapi
|
||||
himp
|
||||
gong
|
||||
lillebror
|
||||
manage
|
||||
</echo>
|
||||
</target>
|
||||
|
||||
|
||||
|
@ -360,14 +346,16 @@ manage
|
|||
|
||||
<!-- unzip images -->
|
||||
<mkdir dir="${build.work}/static/images"/>
|
||||
<unzip src="${build.work}/apps/antville/images.zip" dest="${build.work}/static/images" />
|
||||
<unzip src="${build.work}/apps/antville/images.zip" dest="${build.work}/static/images">
|
||||
<patternset>
|
||||
<include name="**"/>
|
||||
</patternset>
|
||||
</unzip>
|
||||
|
||||
<!-- zip db_support directory and remove it afterwards -->
|
||||
<zip zipfile="${build.work}/apps/antville/db_support.zip">
|
||||
<zipfileset dir="${build.work}/apps/antville/db_support" prefix="db_support" includes="**" />
|
||||
</zip>
|
||||
<delete dir="${build.work}/apps/antville/db_support" />
|
||||
<delete file="${build.work}/apps/antville/images.zip" />
|
||||
<!-- delete antville's mysql-scripts, image-zip etc -->
|
||||
<delete>
|
||||
<fileset dir="${build.work}/apps/antville" includes="images.zip,*.sql" />
|
||||
</delete>
|
||||
|
||||
<!-- get and zip manage-app -->
|
||||
<antcall target="package-manage" />
|
||||
|
@ -411,36 +399,4 @@ manage
|
|||
<delete dir="${build.work}" />
|
||||
</target>
|
||||
|
||||
<!-- =================================================================== -->
|
||||
<!-- Gets antville from the cvs, cleans and zips/targzs it -->
|
||||
<!-- =================================================================== -->
|
||||
<target name="antville" depends="init">
|
||||
<mkdir dir="${build.dist}" />
|
||||
<mkdir dir="${build.work}" />
|
||||
|
||||
<!-- to retrieve special versions of an application insert
|
||||
additional attributes: tag="TAGNAME" or date="1972-09-24 20:05" -->
|
||||
<cvs cvsRoot="${cvs.root.apps}" command="export" tag="HEAD" package="antville" dest="${build.work}" />
|
||||
|
||||
<!-- zip db_support directory and remove it afterwards -->
|
||||
<zip zipfile="${build.work}/antville/db_support.zip">
|
||||
<zipfileset dir="${build.work}/antville/db_support" prefix="db_support" includes="**" />
|
||||
</zip>
|
||||
<delete dir="${build.work}/antville/db_support" />
|
||||
|
||||
<fixcrlf srcdir="${build.work}/antville/" eol="crlf" excludes="**/*.zip" />
|
||||
<zip zipfile="${build.dist}/${antville.name}.zip" basedir="${build.work}" includes="**" />
|
||||
|
||||
<fixcrlf srcdir="${build.work}/antville/" eol="lf" eof="remove" excludes="**/*.zip" />
|
||||
<tar tarfile="${build.dist}/${antville.name}.tar" basedir="${build.work}">
|
||||
<tarfileset dir="${build.work}">
|
||||
<include name="${build.work}/**"/>
|
||||
</tarfileset>
|
||||
</tar>
|
||||
<gzip zipfile="${build.dist}/${antville.name}.tar.gz" src="${build.dist}/${antville.name}.tar" />
|
||||
|
||||
<delete file="${build.dist}/${antville.name}.tar"/>
|
||||
<delete dir="${build.work}" />
|
||||
</target>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
# List of apps to start.
|
||||
# List of applications to start.
|
||||
|
||||
base
|
||||
base.mountpoint = /
|
||||
base.static = static
|
||||
base.staticMountpoint = /static
|
||||
|
||||
manage
|
||||
|
||||
gong
|
||||
himp
|
||||
bloggerapi
|
||||
lillebror
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<!-- printed by helma object publisher -->
|
||||
<!-- created Fri May 31 16:36:44 CEST 2002 -->
|
||||
<xmlroot xmlns:hop="http://www.helma.org/docs/guide/features/database">
|
||||
<hopobject id="0" name="root" prototype="root" created="1022855750998" lastModified="1022855804064">
|
||||
<hop:child idref="3" prototyperef="weblogstory"/>
|
||||
</hopobject>
|
||||
</xmlroot>
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<!-- printed by helma object publisher -->
|
||||
<!-- created Fri May 31 16:36:30 CEST 2002 -->
|
||||
<xmlroot xmlns:hop="http://www.helma.org/docs/guide/features/database">
|
||||
<hopobject id="1" name="users" prototype="hopobject" created="1022855751010" lastModified="1022855790024">
|
||||
<helma idref="2" prototyperef="user"/>
|
||||
</hopobject>
|
||||
</xmlroot>
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<!-- printed by helma object publisher -->
|
||||
<!-- created Fri May 31 16:36:30 CEST 2002 -->
|
||||
<xmlroot xmlns:hop="http://www.helma.org/docs/guide/features/database">
|
||||
<hopobject id="2" name="helma" prototype="user" created="1022855790020" lastModified="1022855790025">
|
||||
<hop:parent idref="1" prototyperef="hopobject"/>
|
||||
<email>admin@somedomain.at</email>
|
||||
<password>helma</password>
|
||||
<name>helma</name>
|
||||
</hopobject>
|
||||
</xmlroot>
|
|
@ -1,24 +0,0 @@
|
|||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<!-- printed by helma object publisher -->
|
||||
<!-- created Fri May 31 16:36:44 CEST 2002 -->
|
||||
<xmlroot xmlns:hop="http://www.helma.org/docs/guide/features/database">
|
||||
<hopobject id="3" name="weblogstory" prototype="weblogstory" created="1022855804062" lastModified="1022855804064">
|
||||
<hop:parent idref="0" prototyperef="root"/>
|
||||
<postdate type="date">31.05.2002 16:36:44 CEST</postdate>
|
||||
<day>2002.05.31</day>
|
||||
<author idref="2" prototyperef="user"/>
|
||||
<text>Congratulations! You successfully created your Helma HopBlog!
|
||||
|
||||
As a first step you can login to your HopBlog using "helma" as user name and password (certainly without the quotes) and create or edit stories.
|
||||
|
||||
Or you set-up HopBlog administration for yourself. You can do this by opening the file app.properties in the apps/hopblog directory of your Helma installation and editing the settings for siteAdmin, adminEmail and smtp.
|
||||
|
||||
After that you should register the new administrator as HopBlog user. Simply enter the data you chose for siteAdmin and adminEmail in the appropriate fields of the registration form, fill in the other form fields and submit your data. If you have set a valid e-mail address and smtp server, you should get a message confirming the registration.
|
||||
|
||||
Now you can login to your HopBlog using name and password of the newly created user.
|
||||
|
||||
Let the fun begin...
|
||||
:)</text>
|
||||
<moddate type="date">31.05.2002 16:36:44 CEST</moddate>
|
||||
</hopobject>
|
||||
</xmlroot>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<!-- printed by helma object publisher -->
|
||||
<!-- created Fri May 31 16:36:44 CEST 2002 -->
|
||||
<xmlroot>
|
||||
<counter>3</counter>
|
||||
</xmlroot>
|
BIN
build/main/lib/ext/mysql.jar
Normal file
BIN
build/main/lib/ext/mysql.jar
Normal file
Binary file not shown.
|
@ -410,6 +410,12 @@ 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) {
|
||||
|
@ -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,27 +1298,37 @@ 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) {
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
// check if we should clean up user sessions
|
||||
if ((now - lastSessionCleanup) > sessionCleanupInterval) {
|
||||
|
||||
lastSessionCleanup = now;
|
||||
|
||||
// get session timeout
|
||||
int sessionTimeout = 30;
|
||||
|
||||
|
@ -1334,16 +1336,9 @@ public final class Application implements IPathElement, Runnable {
|
|||
sessionTimeout = Math.max(0,
|
||||
Integer.parseInt(props.getProperty("sessionTimeout",
|
||||
"30")));
|
||||
} catch (Exception ignore) {
|
||||
System.out.println(ignore.toString());
|
||||
}
|
||||
} catch (Exception ignore) {}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
// check if we should clean up user sessions
|
||||
if ((now - lastCleanup) > cleanupSleep) {
|
||||
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) {
|
||||
}
|
||||
|
||||
if (stopped == false) {
|
||||
// gets logged in RequestEvaluator
|
||||
} finally {
|
||||
if (!stopped) {
|
||||
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("/")) {
|
||||
if (mountpoint.endsWith("/")) {
|
||||
return mountpoint + "*";
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
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;
|
||||
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)) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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,20 +388,19 @@ 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 () {
|
||||
|
@ -419,13 +420,12 @@ public class 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,27 +439,27 @@ 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
|
||||
*/
|
||||
|
@ -728,7 +727,8 @@ public class CronJob {
|
|||
return this.timeout;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
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,11 +491,14 @@ public final class HtmlEncoder {
|
|||
continue;
|
||||
} else if (t > 1) {
|
||||
for (int k = 1; k < t; k++) {
|
||||
Object tag = openTags.pop();
|
||||
if (!emptyTags.contains(tag)) {
|
||||
ret.append("</");
|
||||
ret.append(openTags.pop());
|
||||
ret.append(tag);
|
||||
ret.append(">");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
openTags.pop();
|
||||
} else {
|
||||
|
@ -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,11 +683,14 @@ public final class HtmlEncoder {
|
|||
|
||||
if (o > 0) {
|
||||
for (int k = 0; k < o; k++) {
|
||||
Object tag = openTags.pop();
|
||||
if (!emptyTags.contains(tag)) {
|
||||
ret.append("</");
|
||||
ret.append(openTags.pop());
|
||||
ret.append(tag);
|
||||
ret.append(">");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add remaining newlines we may have collected
|
||||
if ((linebreaks > 0) && (linebreaks > swallowLinebreaks)) {
|
||||
|
|
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
Reference in a new issue