Merged Stefan Pollach's XML Database branch.

This commit is contained in:
hns 2002-05-23 19:27:54 +00:00
parent ca2a42e204
commit 14a6f0840e
18 changed files with 629 additions and 470 deletions

View file

@ -13,7 +13,6 @@ import helma.objectmodel.*;
import helma.objectmodel.db.*;
import helma.xmlrpc.*;
import helma.util.*;
import com.sleepycat.db.DbException;
import java.util.*;
import java.io.*;
import java.net.URLEncoder;
@ -231,7 +230,7 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IPat
/**
* Get the application ready to run, initializing the evaluators and type manager.
*/
public void init () throws DbException, ScriptingException {
public void init () throws DatabaseException, ScriptingException {
scriptingEngine = new helma.scripting.fesi.FesiScriptingEnvironment ();
scriptingEngine.init (this, props);
@ -317,7 +316,7 @@ public class Application extends UnicastRemoteObject implements IRemoteApp, IPat
// shut down node manager and embedded db
try {
nmgr.shutdown ();
} catch (DbException dbx) {
} catch (DatabaseException dbx) {
System.err.println ("Error shutting down embedded db: "+dbx);
}

View file

@ -15,7 +15,6 @@ import helma.framework.*;
import helma.framework.core.*;
import helma.xmlrpc.*;
import helma.util.*;
import com.sleepycat.db.*;
/**

View file

@ -0,0 +1,49 @@
// DatabaseException.java
package helma.objectmodel;
/**
* Thrown on any kind of Database-Error
*/
public class DatabaseException extends RuntimeException {
public DatabaseException (String msg) {
super (msg);
}
}

View file

@ -0,0 +1,35 @@
// IDatabase.java
package helma.objectmodel;
import helma.objectmodel.db.IDGenerator;
import helma.objectmodel.INode;
/**
* Interface that is implemented by Database wrappers
*/
public interface IDatabase {
// db-related
public void shutdown ();
// id-related
public String nextID() throws ObjectNotFoundException;
public IDGenerator getIDGenerator (ITransaction transaction) throws Exception;
public void saveIDGenerator (ITransaction transaction, IDGenerator idgen) throws Exception;
// node-related
public INode getNode (ITransaction transaction, String key) throws Exception;
public void saveNode (ITransaction transaction, String key, INode node) throws Exception;
public void deleteNode (ITransaction transaction, String key) throws Exception;
// transaction-related
public ITransaction beginTransaction ();
public void commitTransaction (ITransaction transaction) throws DatabaseException;
public void abortTransaction (ITransaction transaction) throws DatabaseException;
}

View file

@ -0,0 +1,18 @@
// ITransaction.java
package helma.objectmodel;
/**
* This interface is kept for databases that are able
* to run transactions. Transactions were used for the
* Berkeley database and might be used in other future
* databases, so we leave transactions in.
*/
public interface ITransaction {
}

View file

@ -1,292 +0,0 @@
// DbWrapper.java
// Copyright (c) Hannes Wallnöfer 1999-2000
package helma.objectmodel.db;
import com.sleepycat.db.*;
import helma.objectmodel.ObjectNotFoundException;
import java.io.*;
/**
* A wrapper around a Berkeley embedded database. Used to gracefully handle the case
* when the native library can not be loaded.
*/
public class DbWrapper {
private boolean loaded, useTransactions;
private Db db;
DbEnv dbenv;
final int checkpointPause = 600000; // min. 10 minutes between checkpoints
volatile long lastCheckpoint = 0;
volatile long txncount=0;
private File dbBaseDir;
private NodeManager nmgr;
private String dbHome;
public DbWrapper (String dbHome, String dbFilename, NodeManager nmgr, boolean useTx) throws DbException {
this.dbHome = dbHome;
this.nmgr = nmgr;
try {
dbBaseDir = new File (dbHome);
if (!dbBaseDir.exists())
dbBaseDir.mkdirs();
useTransactions = useTx;
int dbInitFlags = Db.DB_CREATE | Db.DB_THREAD | Db.DB_INIT_MPOOL;
if (useTransactions) {
dbInitFlags = dbInitFlags | Db.DB_INIT_TXN;
}
dbenv = new DbEnv (0);
try {
dbenv.open (dbHome, dbInitFlags, 0); // for berkeley 3.0, add second parameter (null)
} catch (FileNotFoundException fnf) {
// we just created the dirs, so this shouldn't happen
}
try {
dbenv.set_error_stream(System.err);
dbenv.set_errpfx("Sleepycat");
} catch (Exception e) {
System.err.println("Error in DbWrapper: "+e.toString());
}
db = new Db (dbenv, 0);
try {
db.upgrade (dbFilename, 0);
} catch (Exception ignore) {
// nothing to upgrade, db doesn't exist
}
try {
db.open (dbFilename, null, Db.DB_BTREE, Db.DB_CREATE, 0644);
} catch (FileNotFoundException fnf) {
// we just created the dirs, so this shouldn't happen
}
loaded = true;
} catch (NoClassDefFoundError noclass) {
nmgr.app.logEvent ("Warning: Using internal file based db as fallback.");
nmgr.app.logEvent ("Reason: "+noclass);
loaded = false;
} catch (UnsatisfiedLinkError nolib) {
nmgr.app.logEvent ("Warning: Using internal file based db as fallback.");
nmgr.app.logEvent ("Reason: "+nolib);
loaded = false;
}
}
public void shutdown () throws DbException {
if (loaded) {
db.close (0);
// closing the dbenv leads to segfault when app is restarted
// dbenv.close (0);
// dbenv.remove (dbHome, Db.DB_FORCE);
nmgr.app.logEvent ("Closed Berkeley DB");
}
}
public DbTxn beginTransaction () throws DbException {
if (loaded && useTransactions)
return dbenv.txn_begin (null, 0);
else
return null;
}
public void commitTransaction (DbTxn txn) throws DbException {
if (txn == null || !loaded || !useTransactions)
return;
txn.commit (0);
if (++txncount%100 == 0 && System.currentTimeMillis()-checkpointPause > lastCheckpoint) {
// checkpoint transaction logs in time interval specified by server.checkpointPause
// if there are more then 100 transactions to checkpoint.
checkpoint ();
}
}
public void abortTransaction (DbTxn txn) throws DbException {
if (txn == null || !loaded || !useTransactions)
return;
txn.abort ();
}
protected void checkpoint () throws DbException {
if (!loaded || !useTransactions || txncount == 0)
return;
long now = System.currentTimeMillis();
if (now - lastCheckpoint < checkpointPause)
return;
dbenv.txn_checkpoint (0, 0, 0); // for berkeley 3.0, remove third 0 parameter
txncount = 0;
lastCheckpoint = now;
nmgr.app.logEvent ("Spent "+(System.currentTimeMillis()-now)+" in checkpoint");
}
public IDGenerator getIDGenerator (DbTxn txn, String kstr) throws Exception {
if (loaded)
return getIDGenFromDB (txn, kstr);
else
return getIDGenFromFile (kstr);
}
public Node getNode (DbTxn txn, String kstr) throws Exception {
if (loaded)
return getNodeFromDB (txn, kstr);
else
return getNodeFromFile (kstr);
}
public void save (DbTxn txn, String kstr, Object obj) throws Exception {
if (loaded)
saveToDB (txn, kstr, obj);
else
saveToFile (kstr, obj);
}
public void delete (DbTxn txn, String kstr) throws Exception {
if (loaded)
deleteFromDB (txn, kstr);
else
deleteFromFile (kstr);
}
private IDGenerator getIDGenFromDB (DbTxn txn, String kstr) throws Exception {
long now = System.currentTimeMillis ();
byte[] kbuf = kstr.getBytes ();
Dbt key = new Dbt (kbuf);
key.set_size (kbuf.length);
Dbt data = new Dbt ();
data.set_flags (Db.DB_DBT_MALLOC);
db.get (txn, key, data, 0);
byte[] b = data.get_data ();
if (b == null)
throw new ObjectNotFoundException ("Object not found for key "+kstr+".");
IDGenerator idgen = null;
ByteArrayInputStream bin = new ByteArrayInputStream (b);
ObjectInputStream oin = new ObjectInputStream (bin);
idgen = (IDGenerator) oin.readObject ();
oin.close ();
return idgen;
}
private Node getNodeFromDB (DbTxn txn, String kstr) throws Exception {
long now = System.currentTimeMillis ();
byte[] kbuf = kstr.getBytes ();
Dbt key = new Dbt (kbuf);
key.set_size (kbuf.length);
Dbt data = new Dbt ();
data.set_flags (Db.DB_DBT_MALLOC);
db.get (txn, key, data, 0);
byte[] b = data.get_data ();
if (b == null)
throw new ObjectNotFoundException ("Object not found for key "+kstr+".");
Node node = null;
ByteArrayInputStream bin = new ByteArrayInputStream (b);
ObjectInputStream oin = new ObjectInputStream (bin);
node = (Node) oin.readObject ();
oin.close ();
return node;
}
private void saveToDB (DbTxn txn, String kstr, Object obj) throws Exception {
long now = System.currentTimeMillis ();
byte kbuf[] = kstr.getBytes();
ByteArrayOutputStream bout = new ByteArrayOutputStream ();
ObjectOutputStream oout = new ObjectOutputStream (bout);
oout.writeObject (obj);
oout.close ();
byte vbuf[] = bout.toByteArray ();
Dbt key = new Dbt (kbuf);
key.set_size (kbuf.length);
Dbt value = new Dbt (vbuf);
value.set_size (vbuf.length);
db.put (txn, key, value, 0);
// nmgr.app.logEvent ("saved "+obj+", size = "+vbuf.length);
}
private void deleteFromDB (DbTxn txn, String kstr) throws Exception {
byte kbuf[] = kstr.getBytes();
Dbt key = new Dbt (kbuf);
key.set_size (kbuf.length);
db.del (txn, key, 0);
}
////////////////////////////////////////////////////////////////////////////////
// File based fallback methods
///////////////////////////////////////////////////////////////////////////////
private IDGenerator getIDGenFromFile (String kstr) throws Exception {
File f = new File (dbBaseDir, kstr);
if ( ! f.exists() )
throw new ObjectNotFoundException ("Object not found for key "+kstr+".");
IDGenerator idgen = null;
FileInputStream bin = new FileInputStream (f);
ObjectInputStream oin = new ObjectInputStream (bin);
idgen = (IDGenerator) oin.readObject ();
oin.close ();
return idgen;
}
private Node getNodeFromFile (String kstr) throws Exception {
File f = new File (dbBaseDir, kstr);
if ( ! f.exists() )
throw new ObjectNotFoundException ("Object not found for key "+kstr+".");
Node node = null;
FileInputStream bin = new FileInputStream (f);
ObjectInputStream oin = new ObjectInputStream (bin);
node = (Node) oin.readObject ();
oin.close ();
return node;
}
private void saveToFile (String kstr, Object obj) throws Exception {
File f = new File (dbBaseDir, kstr);
FileOutputStream bout = new FileOutputStream (f);
ObjectOutputStream oout = new ObjectOutputStream (bout);
oout.writeObject (obj);
oout.close ();
}
private void deleteFromFile (String kstr) throws Exception {
File f = new File (dbBaseDir, kstr);
f.delete();
}
}

View file

@ -58,7 +58,7 @@ public final class IDGenerator implements Serializable {
/**
* Get the current counter value
*/
protected long getValue () {
public long getValue () {
return counter;
}

View file

@ -124,6 +124,20 @@ public final class Node implements INode, Serializable {
out.writeObject (prototype);
}
/**
* used by Xml deserialization
*/
public void setPropMap (Hashtable propMap) {
this.propMap = propMap;
}
/**
* used by Xml deserialization
*/
public void setSubnodes (List subnodes) {
this.subnodes = subnodes;
}
private transient String prototype;
private transient NodeHandle handle;
@ -164,8 +178,9 @@ public final class Node implements INode, Serializable {
/**
* Creates a new Node with the given name. Only used by NodeManager for "root nodes" and
* not in a Transaction context, which is why we can immediately mark it as CLEAN.
* ADD: used by wrapped database to re-create an existing Node.
*/
protected Node (String name, String id, String prototype, WrappedNodeManager nmgr) {
public Node (String name, String id, String prototype, WrappedNodeManager nmgr) {
this.nmgr = nmgr;
this.id = id;
this.name = name == null || "".equals (name) ? id : name;
@ -176,6 +191,17 @@ public final class Node implements INode, Serializable {
}
/**
* Constructor used to create a Node with a given name from a wrapped database.
*/
public Node (String name, String id, String prototype, WrappedNodeManager nmgr, long created, long lastmodified) {
this (name,id,prototype,nmgr);
this.created = created;
this.lastmodified = lastmodified;
}
/**
* Constructor used for virtual nodes.
*/
@ -459,6 +485,8 @@ public final class Node implements INode, Serializable {
} else {
anonymous = true;
}
} else if (p.contains (this) > -1) {
anonymous = true;
}
} catch (Exception ignore) {
// just fall back to default method
@ -577,6 +605,10 @@ public final class Node implements INode, Serializable {
parentHandle = parent == null ? null : parent.getHandle ();
}
public void setParentHandle (NodeHandle parent) {
parentHandle = parent;
}
/**
* This version of setParent additionally marks the node as anonymous or non-anonymous,
* depending on the string argument. This is the version called from the scripting framework,
@ -1197,6 +1229,10 @@ public final class Node implements INode, Serializable {
return new Enum ();
}
public List getSubnodeList() {
return subnodes;
}
private boolean ignoreSubnodeChange () {
// return true if a change in subnodes can be ignored because it is
// stored in the subnodes themselves.
@ -1233,7 +1269,9 @@ public final class Node implements INode, Serializable {
// return propMap == null ? new Vector ().elements () : propMap.elements ();
}
public Hashtable getPropMap() {
return propMap;
}
public IProperty get (String propname, boolean inherit) {
return getProperty (propname, inherit);

View file

@ -6,7 +6,6 @@ package helma.objectmodel.db;
import helma.util.CacheMap;
import helma.objectmodel.*;
import helma.framework.core.Application;
import com.sleepycat.db.*;
import java.sql.*;
import java.io.*;
import java.util.*;
@ -26,7 +25,7 @@ public final class NodeManager {
private Replicator replicator;
protected DbWrapper db;
protected IDatabase db;
protected IDGenerator idgen;
@ -42,7 +41,7 @@ public final class NodeManager {
* Create a new NodeManager for Application app. An embedded database will be
* created in dbHome if one doesn't already exist.
*/
public NodeManager (Application app, String dbHome, Properties props) throws DbException {
public NodeManager (Application app, String dbHome, Properties props) throws DatabaseException {
this.app = app;
int cacheSize = Integer.parseInt (props.getProperty ("cachesize", "1000"));
// Make actual cache size bigger, since we use it only up to the threshold
@ -68,7 +67,7 @@ public final class NodeManager {
idBaseValue = Math.max (1l, idBaseValue); // 0 and 1 are reserved for root nodes
} catch (NumberFormatException ignore) {}
db = new DbWrapper (dbHome, helma.main.Server.dbFilename, this, helma.main.Server.useTransactions);
db = new XmlDatabase (dbHome, helma.main.Server.dbFilename, this);
initDb ();
logSql = "true".equalsIgnoreCase(props.getProperty ("logsql"));
@ -77,44 +76,44 @@ public final class NodeManager {
/**
* Method used to create the root node and id-generator, if they don't exist already.
*/
public void initDb () throws DbException {
public void initDb () throws DatabaseException {
DbTxn txn = null;
ITransaction txn = null;
try {
txn = db.beginTransaction ();
try {
idgen = db.getIDGenerator (txn, "idgen");
idgen = db.getIDGenerator (txn);
if (idgen.getValue() < idBaseValue) {
idgen.setValue (idBaseValue);
db.save (txn, "idgen", idgen);
db.saveIDGenerator (txn, idgen);
}
} catch (ObjectNotFoundException notfound) {
// will start with idBaseValue+1
idgen = new IDGenerator (idBaseValue);
db.save (txn, "idgen", idgen);
db.saveIDGenerator (txn, idgen);
}
// check if we need to set the id generator to a base value
Node node = null;
try {
node = db.getNode (txn, "0");
node = (Node)db.getNode (txn, "0");
node.nmgr = safe;
} catch (ObjectNotFoundException notfound) {
node = new Node ("root", "0", "root", safe);
node.setDbMapping (app.getDbMapping ("root"));
db.save (txn, node.getID (), node);
db.saveNode (txn, node.getID (), node);
registerNode (node); // register node with nodemanager cache
}
try {
node = db.getNode (txn, "1");
node = (Node)db.getNode (txn, "1");
node.nmgr = safe;
} catch (ObjectNotFoundException notfound) {
node = new Node ("users", "1", null, safe);
node.setDbMapping (app.getDbMapping ("__userroot__"));
db.save (txn, node.getID (), node);
db.saveNode (txn, node.getID (), node);
registerNode (node); // register node with nodemanager cache
}
@ -125,7 +124,7 @@ public final class NodeManager {
try {
db.abortTransaction (txn);
} catch (Exception ignore) {}
throw (new DbException ("Error initializing db"));
throw (new DatabaseException ("Error initializing db"));
}
}
@ -134,7 +133,7 @@ public final class NodeManager {
* Shut down this node manager. This is called when the application using this
* node manager is stopped.
*/
public void shutdown () throws DbException {
public void shutdown () throws DatabaseException {
db.shutdown ();
if (cache != null) {
synchronized (cache) {
@ -360,7 +359,7 @@ public final class NodeManager {
* Insert a new node in the embedded database or a relational database table, depending
* on its db mapping.
*/
public void insertNode (DbWrapper db, DbTxn txn, Node node) throws Exception {
public void insertNode (IDatabase db, ITransaction txn, Node node) throws Exception {
Transactor tx = (Transactor) Thread.currentThread ();
// tx.timer.beginEvent ("insertNode "+node);
@ -368,7 +367,7 @@ public final class NodeManager {
DbMapping dbm = node.getDbMapping ();
if (dbm == null || !dbm.isRelational ()) {
db.save (txn, node.getID (), node);
db.saveNode (txn, node.getID (), node);
} else {
app.logEvent ("inserting relational node: "+node.getID ());
TableDataSet tds = null;
@ -438,7 +437,7 @@ public final class NodeManager {
* Updates a modified node in the embedded db or an external relational database, depending
* on its database mapping.
*/
public void updateNode (DbWrapper db, DbTxn txn, Node node) throws Exception {
public void updateNode (IDatabase db, ITransaction txn, Node node) throws Exception {
Transactor tx = (Transactor) Thread.currentThread ();
// tx.timer.beginEvent ("updateNode "+node);
@ -446,7 +445,7 @@ public final class NodeManager {
DbMapping dbm = node.getDbMapping ();
if (dbm == null || !dbm.isRelational ()) {
db.save (txn, node.getID (), node);
db.saveNode (txn, node.getID (), node);
} else {
TableDataSet tds = null;
@ -537,7 +536,7 @@ public final class NodeManager {
/**
* Performs the actual deletion of a node from either the embedded or an external SQL database.
*/
public void deleteNode (DbWrapper db, DbTxn txn, Node node) throws Exception {
public void deleteNode (IDatabase db, ITransaction txn, Node node) throws Exception {
Transactor tx = (Transactor) Thread.currentThread ();
// tx.timer.beginEvent ("deleteNode "+node);
@ -545,7 +544,7 @@ public final class NodeManager {
DbMapping dbm = node.getDbMapping ();
if (dbm == null || !dbm.isRelational ()) {
db.delete (txn, node.getID ());
db.deleteNode (txn, node.getID ());
} else {
Statement st = null;
try {
@ -865,14 +864,14 @@ public final class NodeManager {
// private getNode methods
///////////////////////////////////////////////////////////////////////////////////////
private Node getNodeByKey (DbTxn txn, DbKey key) throws Exception {
private Node getNodeByKey (ITransaction txn, DbKey key) throws Exception {
// Note: Key must be a DbKey, otherwise will not work for relational objects
Node node = null;
DbMapping dbm = app.getDbMapping (key.getStorageName ());
String kstr = key.getID ();
if (dbm == null || !dbm.isRelational ()) {
node = db.getNode (txn, kstr);
node = (Node)db.getNode (txn, kstr);
node.nmgr = safe;
if (node != null && dbm != null)
node.setDbMapping (dbm);
@ -905,7 +904,7 @@ public final class NodeManager {
return node;
}
private Node getNodeByRelation (DbTxn txn, Node home, String kstr, Relation rel) throws Exception {
private Node getNodeByRelation (ITransaction txn, Node home, String kstr, Relation rel) throws Exception {
Node node = null;
if (rel.virtual) {
@ -922,13 +921,13 @@ public final class NodeManager {
} else if (rel != null && rel.groupby != null) {
node = home.getGroupbySubnode (kstr, false);
if (node == null && (rel.otherType == null || !rel.otherType.isRelational ())) {
node = db.getNode (txn, kstr);
node = (Node)db.getNode (txn, kstr);
node.nmgr = safe;
}
return node;
} else if (rel == null || rel.otherType == null || !rel.otherType.isRelational ()) {
node = db.getNode (txn, kstr);
node = (Node)db.getNode (txn, kstr);
node.nmgr = safe;
node.setDbMapping (rel.otherType);
return node;

View file

@ -210,6 +210,17 @@ public final class Property implements IProperty, Serializable, Cloneable {
dirty = true;
}
public void setNodeHandle (NodeHandle value) {
if (type == NODE)
unregisterNode ();
if (type == JAVAOBJECT)
this.jvalue = null;
// registerNode (value);
type = NODE;
nhandle = value;
dirty = true;
}
public void setJavaObjectValue (Object value) {
if (type == NODE)
unregisterNode ();

View file

@ -9,7 +9,6 @@ import java.sql.*;
import helma.objectmodel.*;
import helma.util.Timer;
import helma.framework.TimeoutException;
import com.sleepycat.db.*;
/**
* A subclass of thread that keeps track of changed nodes and triggers
@ -30,7 +29,7 @@ public class Transactor extends Thread {
private volatile boolean killed;
// Transaction for the embedded database
protected DbTxn txn;
protected ITransaction txn;
// Transactions for SQL data sources
protected HashMap sqlCon;
@ -108,7 +107,7 @@ public class Transactor extends Thread {
public synchronized void begin (String tnm) throws Exception {
if (killed)
throw new DbException ("Transaction started on killed thread");
throw new DatabaseException ("Transaction started on killed thread");
if (active)
abort ();
@ -171,7 +170,7 @@ public class Transactor extends Thread {
cleannodes.clear ();
if (nmgr.idgen.dirty) {
nmgr.db.save (txn, "idgen", nmgr.idgen);
nmgr.db.saveIDGenerator (txn, nmgr.idgen);
nmgr.idgen.dirty = false;
}

View file

@ -0,0 +1,95 @@
package helma.objectmodel.db;
import java.io.*;
import java.util.Date;
import org.w3c.dom.Element;
import org.w3c.dom.Document;
import helma.objectmodel.*;
import helma.objectmodel.dom.*;
/**
* A simple XML-database
*/
public class XmlDatabase implements IDatabase {
private String dbHome;
private File dbBaseDir;
private NodeManager nmgr;
private IDGenerator idgen;
// character encoding to use when writing files.
// use standard encoding by default.
private String encoding = null;
public XmlDatabase (String dbHome, String dbFilename, NodeManager nmgr) throws DatabaseException {
this.dbHome = dbHome;
this.nmgr = nmgr;
dbBaseDir = new File (dbHome);
if (!dbBaseDir.exists() && !dbBaseDir.mkdirs() )
throw new RuntimeException("Couldn't create DB-directory");
}
public void shutdown () { }
public ITransaction beginTransaction () throws DatabaseException { return null; }
public void commitTransaction (ITransaction txn) throws DatabaseException { }
public void abortTransaction (ITransaction txn) throws DatabaseException { }
public String nextID() throws ObjectNotFoundException {
if (idgen==null) {
getIDGenerator(null);
}
return idgen.newID();
}
public IDGenerator getIDGenerator (ITransaction txn) throws ObjectNotFoundException {
File file = new File (dbBaseDir, "idgen.xml");
this.idgen = IDGenParser.getIDGenerator(file);
return idgen;
}
public void saveIDGenerator (ITransaction txn, IDGenerator idgen) throws Exception {
File file = new File (dbBaseDir, "idgen.xml");
IDGenParser.saveIDGenerator(idgen,file);
this.idgen = idgen;
}
public INode getNode (ITransaction txn, String kstr) throws Exception {
File f = new File (dbBaseDir, kstr+".xml");
if ( ! f.exists() )
throw new ObjectNotFoundException ("Object not found for key "+kstr+".");
try {
XmlReader reader = new XmlReader (nmgr);
Node node = (Node)reader.read (f, null);
return node;
} catch ( RuntimeException x ) {
nmgr.app.logEvent("error reading node from XmlDatbase: " + x.toString() );
throw new ObjectNotFoundException(x.toString());
}
}
public void saveNode (ITransaction txn, String kstr, INode node) throws Exception {
XmlWriter writer = null;
File file = new File (dbBaseDir,kstr+".xml");
if (encoding != null)
writer = new XmlWriter (file, encoding);
else
writer = new XmlWriter (file);
writer.setMaxLevels(1);
boolean result = writer.write((Node)node);
writer.close();
}
public void deleteNode (ITransaction txn, String kstr) throws Exception {
File f = new File (dbBaseDir, kstr+".xml");
f.delete();
}
public void setEncoding (String enc) {
this.encoding = encoding;
}
public String getEncoding () {
return encoding;
}
}

View file

@ -0,0 +1,37 @@
package helma.objectmodel.dom;
import java.io.*;
import java.util.Date;
import org.w3c.dom.*;
import helma.objectmodel.ObjectNotFoundException;
import helma.objectmodel.db.IDGenerator;
public class IDGenParser {
public static IDGenerator getIDGenerator (File file) throws ObjectNotFoundException {
if ( ! file.exists() )
throw new ObjectNotFoundException ("IDGenerator not found in idgen.xml");
try {
Document document = XmlUtil.parse(new FileInputStream (file));
org.w3c.dom.Element tmp = (Element)document.getDocumentElement().getElementsByTagName("counter").item(0);
return new IDGenerator( Long.parseLong (XmlUtil.getTextContent(tmp)) );
} catch (Exception e) {
throw new ObjectNotFoundException(e.toString());
}
}
public static IDGenerator saveIDGenerator (IDGenerator idgen, File file) throws Exception {
OutputStreamWriter out = new OutputStreamWriter (new FileOutputStream (file));
out.write ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
out.write ("<!-- printed by helma object publisher -->\n");
out.write ("<!-- created " + (new Date()).toString() + " -->\n" );
out.write ("<xmlroot>\n");
out.write (" <counter>" + idgen.getValue() + "</counter>\n");
out.write ("</xmlroot>\n");
out.close ();
return idgen;
}
}

View file

@ -2,7 +2,7 @@ package helma.objectmodel.dom;
public interface XmlConstants {
public final String NAMESPACE = "http://www.helma.org/";
public final String NAMESPACE = "http://www.helma.org/docs/guide/features/database";
public final String DATEFORMAT = "dd.MM.yyyy HH:mm:ss z";
}

View file

@ -108,6 +108,8 @@ public class XmlConverter implements XmlConstants {
continue;
}
// FIXME: handle CDATA!
// it's some kind of element (property or child)
if ( childNode.getNodeType()==org.w3c.dom.Node.ELEMENT_NODE ) {

View file

@ -1,91 +1,95 @@
package helma.objectmodel.dom;
import java.io.*;
import java.net.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.*;
import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import helma.objectmodel.*;
import helma.objectmodel.INode;
import helma.objectmodel.db.DbKey;
import helma.objectmodel.db.ExternalizableVector;
import helma.objectmodel.db.Node;
import helma.objectmodel.db.NodeHandle;
import helma.objectmodel.db.NodeManager;
import helma.objectmodel.db.Property;
public class XmlReader implements XmlConstants {
private int offset = 0;
private HashMap convertedNodes;
private NodeManager nmgr = null;
public XmlReader() {
public XmlReader () {
}
public INode read( String desc ) {
return read(desc, new TransientNode() );
public XmlReader (NodeManager nmgr) {
this.nmgr = nmgr;
}
public INode read( String desc, INode helmaNode ) throws RuntimeException {
/**
* main entry to read an xml-file.
*/
public INode read (File file, INode helmaNode) throws RuntimeException {
try {
return read( new File(desc), helmaNode );
} catch ( FileNotFoundException notfound ) {
throw new RuntimeException( "couldn't find xml-file: " + desc );
} catch ( IOException ioerror ) {
throw new RuntimeException( "couldn't read xml: " + desc );
}
}
public INode read( File file, INode helmaNode ) throws RuntimeException, FileNotFoundException {
return read( new FileInputStream(file), helmaNode );
}
public INode read( InputStream in, INode helmaNode ) throws RuntimeException {
Document document = XmlUtil.parse(in);
if ( document!=null && document.getDocumentElement()!=null ) {
Node tmp = document.getDocumentElement().getFirstChild();
Element workelement = null;
while( tmp!=null ) {
tmp = tmp.getNextSibling();
if ( tmp.getNodeType()==Node.ELEMENT_NODE ) {
workelement = (Element) tmp;
break;
}
}
return startConversion( helmaNode, workelement );
} else {
return read (new FileInputStream(file), helmaNode);
} catch (FileNotFoundException notfound) {
System.err.println ("couldn't find xml-file: " + file.getAbsolutePath ());
return helmaNode;
}
}
public INode startConversion( INode helmaNode, Element element ) {
convertedNodes = new HashMap();
INode convertedNode = convert(helmaNode, element );
/**
* read an InputStream with xml-content.
*/
public INode read (InputStream in, INode helmaNode) throws RuntimeException {
if (helmaNode==null && nmgr==null)
throw new RuntimeException ("can't create a new Node without a NodeManager");
Document document = XmlUtil.parse (in);
Element element = XmlUtil.getFirstElement(document);
if (element==null)
throw new RuntimeException ("corrupted xml-file");
if (helmaNode==null) {
return convert (element);
} else {
convertedNodes = new HashMap ();
INode convertedNode = convert (element, helmaNode);
convertedNodes = null;
return convertedNode;
}
}
public INode convert( INode helmaNode, Element element ) {
offset++;
String idref = element.getAttributeNS(NAMESPACE, "idref");
String key = idref + "-" + element.getAttributeNS(NAMESPACE, "prototyperef");
/**
* convert children of an Element to a given helmaNode
*/
public INode convert (Element element, INode helmaNode) {
String idref = element.getAttribute("idref");
String key = idref + "-" + element.getAttribute("prototyperef");
if( idref!=null && !idref.equals("") ) {
if( convertedNodes.containsKey(key) ) {
offset--;
return (INode)convertedNodes.get(key);
}
}
key = element.getAttributeNS(NAMESPACE, "id") + "-" + element.getAttributeNS(NAMESPACE, "prototype");
key = element.getAttribute("id") + "-" + element.getAttribute("prototype");
convertedNodes.put( key, helmaNode );
// FIXME: import id on persistent nodes
String prototype = element.getAttributeNS(NAMESPACE, "prototype");
String prototype = element.getAttribute("prototype");
if( !prototype.equals("") && !prototype.equals("hopobject") ) {
helmaNode.setPrototype( prototype );
}
children(helmaNode, element);
offset--;
return helmaNode;
}
// used by convert(Element,INode)
private INode children( INode helmaNode, Element element ) {
NodeList list = element.getChildNodes();
int len = list.getLength();
@ -94,16 +98,23 @@ public class XmlReader implements XmlConstants {
try {
childElement = (Element)list.item(i);
} catch( ClassCastException e ) {
continue;
continue; // ignore CDATA, comments etc
}
INode workNode = null;
if ( childElement.getTagName().equals("hop:child") ) {
convert( helmaNode.createNode(null), childElement );
} else if ( !"".equals(childElement.getAttributeNS(NAMESPACE,"id")) || !"".equals(childElement.getAttributeNS(NAMESPACE,"idref")) ) {
// we've got an object!
helmaNode.setNode( childElement.getTagName(), convert( helmaNode.createNode(childElement.getTagName()), childElement ) );
convert (childElement, helmaNode.createNode(null));
} else if ( !"".equals(childElement.getAttribute("id")) || !"".equals(childElement.getAttribute("idref")) ) {
String childTagName = childElement.getTagName();
INode newNode = convert (childElement, helmaNode.createNode (childTagName));
helmaNode.setNode (childTagName, newNode);
} else {
String type = childElement.getAttribute("hop:type");
String type = childElement.getAttribute("type");
String key = childElement.getTagName();
String content = XmlUtil.getTextContent(childElement);
if ( type.equals("boolean") ) {
@ -132,26 +143,101 @@ public class XmlReader implements XmlConstants {
return helmaNode;
}
/** for testing */
public static void main ( String args[] ) throws Exception {
/**
* This is a basic de-serialization method for XML-2-Node conversion.
* It reads a Node from a database-like file and should return a Node
* that matches exactly the one dumped to that file before.
* It only supports persistent-capable Nodes (from objectmodel.db-package).
*/
public helma.objectmodel.db.Node convert (Element element) {
// FIXME: this method should use Element.getAttributeNS():
// FIXME: do we need the name value or is it retrieved through mappings anyway?
String name = element.getAttribute("name");
// String name = null;
String id = element.getAttribute("id");
String prototype = element.getAttribute("prototype");
if ( "".equals(prototype) )
prototype = "hopobject";
helma.objectmodel.db.Node helmaNode = null;
try {
XmlReader x = new XmlReader ();
INode node = x.read("test.xml");
} catch ( Exception e ) {
System.out.println("exception " + e.toString() );
throw new RuntimeException(e.toString());
}
long created = Long.parseLong (element.getAttribute ("created"));
long lastmodified = Long.parseLong (element.getAttribute ("lastModified"));
helmaNode = new helma.objectmodel.db.Node (name,id,prototype,nmgr.safe,created,lastmodified);
} catch ( NumberFormatException e ) {
helmaNode = new helma.objectmodel.db.Node (name,id,prototype,nmgr.safe);
}
// now loop through all child elements and retrieve properties/subnodes for this node.
NodeList list = element.getChildNodes();
int len = list.getLength();
Hashtable propMap = new Hashtable();
List subnodes = new ExternalizableVector();
for ( int i=0; i<len; i++ ) {
/** for testing */
void debug(Object msg) {
for ( int i=0; i<offset; i++ ) {
System.out.print(" ");
}
System.out.println(msg.toString());
Element childElement;
try {
childElement = (Element)list.item(i);
} catch( ClassCastException e ) {
continue; // ignore CDATA, comments etc
}
if ( childElement.getTagName().equals("hop:child") ) {
// add a new NodeHandle, presume all IDs in this objectcache are unique,
// a prerequisite for a simple internal database.
subnodes.add (new NodeHandle (new DbKey(null,childElement.getAttribute("idref") ) ) );
continue;
}
if ( childElement.getTagName().equals("hop:parent") ) {
// add a NodeHandle to parent object
helmaNode.setParentHandle (new NodeHandle (new DbKey(null,childElement.getAttribute("idref") ) ) );
}
// if we come until here, childelement is a property value
Property prop = new Property (childElement.getTagName(), helmaNode);
if ( !"".equals(childElement.getAttribute("id")) || !"".equals(childElement.getAttribute("idref")) ) {
// we've got an object!
String idref = childElement.getAttribute("idref");
prop.setNodeHandle (new NodeHandle(new DbKey(null,idref)));
} else {
String type = childElement.getAttribute("type");
String content = XmlUtil.getTextContent(childElement);
if ( type.equals("boolean") ) {
if ( content.equals("true") ) {
prop.setBooleanValue(true);
} else {
prop.setBooleanValue(false);
}
} else if ( type.equals("date") ) {
SimpleDateFormat format = new SimpleDateFormat ( DATEFORMAT );
try {
Date date = format.parse(content);
prop.setDateValue (date);
} catch ( ParseException e ) {
prop.setStringValue (content);
}
} else if ( type.equals("float") ) {
prop.setFloatValue ((new Double(content)).doubleValue());
} else if ( type.equals("integer") ) {
prop.setIntegerValue ((new Long(content)).longValue());
} else {
prop.setStringValue (content);
}
}
propMap.put (childElement.getTagName(), prop);
}
if ( propMap.size()>0 )
helmaNode.setPropMap (propMap);
else
helmaNode.setPropMap (null);
if ( subnodes.size()>0 )
helmaNode.setSubnodes (subnodes);
else
helmaNode.setSubnodes (null);
return helmaNode;
}
}

View file

@ -10,6 +10,7 @@ import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
@ -54,6 +55,24 @@ public class XmlUtil {
}
}
/**
* get first "real" element (ie not the document-rootelement, but the next one
*/
public static Element getFirstElement (Document document) {
Element workelement = null;
if ( document.getDocumentElement()!=null ) {
org.w3c.dom.Node tmp = document.getDocumentElement().getFirstChild();
while( tmp!=null ) {
tmp = tmp.getNextSibling();
if ( tmp.getNodeType()==org.w3c.dom.Node.ELEMENT_NODE ) {
workelement = (Element) tmp;
break;
}
}
}
return workelement;
}
/**
* return the text content of an element
*/

View file

@ -1,11 +1,24 @@
package helma.objectmodel.dom;
import java.io.*;
import java.net.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import helma.objectmodel.*;
import helma.objectmodel.INode;
import helma.objectmodel.IProperty;
import helma.objectmodel.TransientNode;
import helma.objectmodel.db.Node;
import helma.objectmodel.db.DbMapping;
import helma.util.HtmlEncoder;
public class XmlWriter extends OutputStreamWriter implements XmlConstants {
@ -19,6 +32,9 @@ public class XmlWriter extends OutputStreamWriter implements XmlConstants {
private StringBuffer prefix = new StringBuffer();
private static int fileid;
private SimpleDateFormat format = new SimpleDateFormat ( DATEFORMAT );
private boolean dbmode = true;
/**
* create ids that can be used for temporary files.
@ -38,14 +54,26 @@ public class XmlWriter extends OutputStreamWriter implements XmlConstants {
super(out);
}
public XmlWriter (OutputStream out, String enc) throws UnsupportedEncodingException {
super(out, enc);
}
public XmlWriter (String desc) throws FileNotFoundException {
super (new FileOutputStream (desc));
}
public XmlWriter (String desc, String enc) throws FileNotFoundException, UnsupportedEncodingException {
super (new FileOutputStream (desc), enc);
}
public XmlWriter (File file) throws FileNotFoundException {
super (new FileOutputStream (file));
}
public XmlWriter (File file, String enc) throws FileNotFoundException, UnsupportedEncodingException {
super (new FileOutputStream (file), enc);
}
/**
* by default writing only descends 50 levels into the node tree to prevent
* infite loops. number can be changed here.
@ -54,6 +82,10 @@ public class XmlWriter extends OutputStreamWriter implements XmlConstants {
maxLevels = levels;
}
public void setDatabaseMode (boolean dbmode) {
this.dbmode = dbmode;
}
/**
* set the number of space chars
*/
@ -72,7 +104,8 @@ public class XmlWriter extends OutputStreamWriter implements XmlConstants {
*/
public boolean write( INode node ) throws IOException {
convertedNodes = new Vector();
writeln ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
String encoding = getEncoding();
writeln ("<?xml version=\"1.0\" encoding=\""+encoding+"\"?>");
writeln ("<!-- printed by helma object publisher -->");
writeln ("<!-- created " + (new Date()).toString() + " -->" );
write ("<xmlroot xmlns:hop=\"");
@ -86,17 +119,26 @@ public class XmlWriter extends OutputStreamWriter implements XmlConstants {
/**
* write a hopobject and print all its properties and children.
* if node has already been fully printed, just make a reference here.
* references are made here if a node already has been fully printed
* or if this is the last level that's going to be dumped
*/
public void write (INode node, String name, int level) throws IOException {
if ( ++level>maxLevels )
if (node==null)
return;
prefix.append(indent);
if ( ++level>maxLevels ) {
writeReferenceTag (node, name);
prefix = prefix.delete( prefix.length()-indent.length(), Integer.MAX_VALUE );
return;
}
if ( convertedNodes.contains(node) ) {
writeReferenceTag (node, name);
} else {
convertedNodes.addElement (node);
writeTagOpen (node,name);
if ( node.getParent()!=null ) {
writeReferenceTag (node.getParent(),"hop:parent");
}
writeProperties (node,level);
writeChildren (node,level);
writeTagClose (node,name);
@ -104,12 +146,24 @@ public class XmlWriter extends OutputStreamWriter implements XmlConstants {
prefix = prefix.delete( prefix.length()-indent.length(), Integer.MAX_VALUE );
}
/**
* loop through properties and print them with their property-name
* as elementname
*/
private void writeProperties (INode node, int level) throws IOException {
Enumeration e = node.properties();
Enumeration e = null;
if ( dbmode==true && node instanceof helma.objectmodel.db.Node ) {
// a newly constructed db.Node doesn't have a propMap,
// but returns an enumeration of all it's db-mapped properties
Hashtable props = ((Node)node).getPropMap();
if (props==null)
return;
e = props.keys();
} else {
e = node.properties();
}
while ( e.hasMoreElements() ) {
String key = (String)e.nextElement();
IProperty prop = node.get(key,false);
@ -129,7 +183,7 @@ public class XmlWriter extends OutputStreamWriter implements XmlConstants {
write (indent);
write ("<");
write (key);
write (" hop:type=\"null\"/>");
write (" type=\"null\"/>");
write (LINESEPARATOR);
}
@ -144,18 +198,17 @@ public class XmlWriter extends OutputStreamWriter implements XmlConstants {
write (property.getName());
switch (property.getType()) {
case IProperty.BOOLEAN:
write (" hop:type=\"boolean\"");
write (" type=\"boolean\"");
break;
case IProperty.FLOAT:
write (" hop:type=\"float\"");
write (" type=\"float\"");
break;
case IProperty.INTEGER:
write (" hop:type=\"integer\"");
write (" type=\"integer\"");
break;
}
if ( property.getType()==IProperty.DATE ) {
write (" hop:type=\"date\"");
SimpleDateFormat format = new SimpleDateFormat ( DATEFORMAT );
write (" type=\"date\"");
write (">");
write ( format.format (property.getDateValue()) );
} else {
@ -172,6 +225,12 @@ public class XmlWriter extends OutputStreamWriter implements XmlConstants {
* loop through the children-array and print them as <hop:child>
*/
private void writeChildren (INode node, int level) throws IOException {
if ( dbmode==true && node instanceof helma.objectmodel.db.Node ) {
Node dbNode = (Node)node;
DbMapping smap = dbNode.getDbMapping() == null ? null : dbNode.getDbMapping().getSubnodeMapping ();
if (smap != null && smap.isRelational ())
return;
}
Enumeration e = node.getSubnodes();
while (e.hasMoreElements()) {
INode nextNode = (INode)e.nextElement();
@ -187,12 +246,18 @@ public class XmlWriter extends OutputStreamWriter implements XmlConstants {
write (prefix.toString());
write ("<");
write ( (name==null)?"hopobject" : name);
write (" hop:id=\"");
write (" id=\"");
write (getNodeIdentifier(node));
write ("\" hop:prototype=\"");
write ("\" name=\"");
write (node.getName());
write ("\" prototype=\"");
write (getNodePrototype(node));
write ("\"");
write (">");
write ("\" created=\"");
write (Long.toString(node.created()));
write ("\" lastModified=\"");
write (Long.toString(node.lastModified()));
//FIXME: do we need anonymous-property?
write ("\">");
write (LINESEPARATOR);
}
@ -210,16 +275,16 @@ public class XmlWriter extends OutputStreamWriter implements XmlConstants {
/**
* write a tag holding a reference to an element that has
* been dumped before.
* e.g. <parent hop:idref="t35" hop:prototyperef="hopobject"/>
* been written out before.
* e.g. <parent idref="35" prototyperef="hopobject"/>
*/
public void writeReferenceTag (INode node, String name) throws IOException {
write (prefix.toString());
write ("<");
write ( (name==null)?"hopobject" : name);
write ( " hop:idref=\"");
write ( " idref=\"");
write (getNodeIdentifier(node));
write ("\" hop:prototyperef=\"");
write ("\" prototyperef=\"");
write (getNodePrototype(node));
write ("\"");
write ("/>");