diff --git a/src/helma/framework/core/Application.java b/src/helma/framework/core/Application.java index a85a7cb0..87b87a5f 100644 --- a/src/helma/framework/core/Application.java +++ b/src/helma/framework/core/Application.java @@ -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); } diff --git a/src/helma/main/Server.java b/src/helma/main/Server.java index a1158d2d..5a86ad29 100644 --- a/src/helma/main/Server.java +++ b/src/helma/main/Server.java @@ -15,7 +15,6 @@ import helma.framework.*; import helma.framework.core.*; import helma.xmlrpc.*; import helma.util.*; -import com.sleepycat.db.*; /** diff --git a/src/helma/objectmodel/DatabaseException.java b/src/helma/objectmodel/DatabaseException.java new file mode 100644 index 00000000..dbd794a3 --- /dev/null +++ b/src/helma/objectmodel/DatabaseException.java @@ -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); + } + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/helma/objectmodel/IDatabase.java b/src/helma/objectmodel/IDatabase.java new file mode 100644 index 00000000..2618d621 --- /dev/null +++ b/src/helma/objectmodel/IDatabase.java @@ -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; + +} + + + diff --git a/src/helma/objectmodel/ITransaction.java b/src/helma/objectmodel/ITransaction.java new file mode 100644 index 00000000..1d617a9f --- /dev/null +++ b/src/helma/objectmodel/ITransaction.java @@ -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 { + +} + + + + diff --git a/src/helma/objectmodel/db/DbWrapper.java b/src/helma/objectmodel/db/DbWrapper.java deleted file mode 100644 index 38435894..00000000 --- a/src/helma/objectmodel/db/DbWrapper.java +++ /dev/null @@ -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(); - } - - -} diff --git a/src/helma/objectmodel/db/IDGenerator.java b/src/helma/objectmodel/db/IDGenerator.java index ca8ba96c..414b573e 100644 --- a/src/helma/objectmodel/db/IDGenerator.java +++ b/src/helma/objectmodel/db/IDGenerator.java @@ -58,7 +58,7 @@ public final class IDGenerator implements Serializable { /** * Get the current counter value */ - protected long getValue () { + public long getValue () { return counter; } diff --git a/src/helma/objectmodel/db/Node.java b/src/helma/objectmodel/db/Node.java index dfaaabc5..10bbcd5a 100644 --- a/src/helma/objectmodel/db/Node.java +++ b/src/helma/objectmodel/db/Node.java @@ -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); diff --git a/src/helma/objectmodel/db/NodeManager.java b/src/helma/objectmodel/db/NodeManager.java index 0021e732..f2a8d880 100644 --- a/src/helma/objectmodel/db/NodeManager.java +++ b/src/helma/objectmodel/db/NodeManager.java @@ -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; diff --git a/src/helma/objectmodel/db/Property.java b/src/helma/objectmodel/db/Property.java index b7da69c1..5a1f0dfd 100644 --- a/src/helma/objectmodel/db/Property.java +++ b/src/helma/objectmodel/db/Property.java @@ -202,14 +202,25 @@ public final class Property implements IProperty, Serializable, Cloneable { unregisterNode (); if (type == JAVAOBJECT) this.jvalue = null; - - // registerNode (value); + + // registerNode (value); type = NODE; - + nhandle = value.getHandle (); 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 (); diff --git a/src/helma/objectmodel/db/Transactor.java b/src/helma/objectmodel/db/Transactor.java index 66d2c3d9..a4ea136d 100644 --- a/src/helma/objectmodel/db/Transactor.java +++ b/src/helma/objectmodel/db/Transactor.java @@ -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; } diff --git a/src/helma/objectmodel/db/XmlDatabase.java b/src/helma/objectmodel/db/XmlDatabase.java new file mode 100644 index 00000000..a7cfec5c --- /dev/null +++ b/src/helma/objectmodel/db/XmlDatabase.java @@ -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; + } +} diff --git a/src/helma/objectmodel/dom/IDGenParser.java b/src/helma/objectmodel/dom/IDGenParser.java new file mode 100644 index 00000000..2f16ee7d --- /dev/null +++ b/src/helma/objectmodel/dom/IDGenParser.java @@ -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 ("\n"); + out.write ("\n"); + out.write ("\n" ); + out.write ("\n"); + out.write (" " + idgen.getValue() + "\n"); + out.write ("\n"); + out.close (); + return idgen; + } + +} + diff --git a/src/helma/objectmodel/dom/XmlConstants.java b/src/helma/objectmodel/dom/XmlConstants.java index 67e79535..2ab0709d 100644 --- a/src/helma/objectmodel/dom/XmlConstants.java +++ b/src/helma/objectmodel/dom/XmlConstants.java @@ -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"; } diff --git a/src/helma/objectmodel/dom/XmlConverter.java b/src/helma/objectmodel/dom/XmlConverter.java index 8eb48903..9e51d25a 100644 --- a/src/helma/objectmodel/dom/XmlConverter.java +++ b/src/helma/objectmodel/dom/XmlConverter.java @@ -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 ) { diff --git a/src/helma/objectmodel/dom/XmlReader.java b/src/helma/objectmodel/dom/XmlReader.java index 1ceb7ecb..1ed51888 100644 --- a/src/helma/objectmodel/dom/XmlReader.java +++ b/src/helma/objectmodel/dom/XmlReader.java @@ -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 XmlReader (NodeManager nmgr) { + this.nmgr = nmgr; } - public INode read( String desc ) { - return read(desc, new TransientNode() ); - } - - 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 ); - convertedNodes = null; - return convertedNode; + /** + * 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 { - 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()); + + /** + * 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 { + 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; i0 ) + helmaNode.setPropMap (propMap); + else + helmaNode.setPropMap (null); + if ( subnodes.size()>0 ) + helmaNode.setSubnodes (subnodes); + else + helmaNode.setSubnodes (null); + return helmaNode; } - } diff --git a/src/helma/objectmodel/dom/XmlUtil.java b/src/helma/objectmodel/dom/XmlUtil.java index dcabc81c..b28e8810 100644 --- a/src/helma/objectmodel/dom/XmlUtil.java +++ b/src/helma/objectmodel/dom/XmlUtil.java @@ -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 */ diff --git a/src/helma/objectmodel/dom/XmlWriter.java b/src/helma/objectmodel/dom/XmlWriter.java index b28b6fa0..ff7bfb6d 100644 --- a/src/helma/objectmodel/dom/XmlWriter.java +++ b/src/helma/objectmodel/dom/XmlWriter.java @@ -1,65 +1,97 @@ 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 { +public class XmlWriter extends OutputStreamWriter implements XmlConstants { private final static String LINESEPARATOR = System.getProperty("line.separator"); - + private Vector convertedNodes; private int maxLevels = 3; - + private String indent = " "; 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. */ - public static int generateID() { + public static int generateID() { return fileid++; } /** * empty constructor, will use System.out as outputstream. */ - public XmlWriter () { + public XmlWriter () { super(System.out); } - public XmlWriter (OutputStream out) { + public XmlWriter (OutputStream out) { super(out); } - public XmlWriter (String desc) throws FileNotFoundException { + public XmlWriter (OutputStream out, String enc) throws UnsupportedEncodingException { + super(out, enc); + } + + public XmlWriter (String desc) throws FileNotFoundException { super (new FileOutputStream (desc)); } - public XmlWriter (File file) throws FileNotFoundException { + 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. */ - public void setMaxLevels (int levels) { + public void setMaxLevels (int levels) { maxLevels = levels; } + public void setDatabaseMode (boolean dbmode) { + this.dbmode = dbmode; + } + /** * set the number of space chars */ - public void setIndent (int ct) { + public void setIndent (int ct) { StringBuffer tmp = new StringBuffer (); - for ( int i=0; i"); + String encoding = getEncoding(); + writeln (""); writeln (""); writeln ("" ); write ("maxLevels ) + public void write (INode node, String name, int level) throws IOException { + if (node==null) return; prefix.append(indent); - if ( convertedNodes.contains(node) ) { + if ( ++level>maxLevels ) { writeReferenceTag (node, name); - } else { + 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,61 +146,72 @@ 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(); - while ( e.hasMoreElements() ) { + private void writeProperties (INode node, int level) throws IOException { + 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); - if ( prop!=null ) { + if ( prop!=null ) { int type = prop.getType(); - if( type==IProperty.NODE ) { + if( type==IProperty.NODE ) { write (node.getNode(key,false), key, level); - } else { + } else { writeProperty (node.get(key,false)); } } } } - public void writeNullProperty (String key) throws IOException { + public void writeNullProperty (String key) throws IOException { write (prefix.toString()); write (indent); write ("<"); write (key); - write (" hop:type=\"null\"/>"); + write (" type=\"null\"/>"); write (LINESEPARATOR); - } + } /** - * write a single property, set attribute type according to type, + * write a single property, set attribute type according to type, * apply xml-encoding. */ - public void writeProperty (IProperty property) throws IOException { + public void writeProperty (IProperty property) throws IOException { write (prefix.toString()); write (indent); write ("<"); write (property.getName()); - switch (property.getType()) { + 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\""); + case IProperty.INTEGER: + write (" type=\"integer\""); break; } - if ( property.getType()==IProperty.DATE ) { - write (" hop:type=\"date\""); - SimpleDateFormat format = new SimpleDateFormat ( DATEFORMAT ); + if ( property.getType()==IProperty.DATE ) { + write (" type=\"date\""); write (">"); write ( format.format (property.getDateValue()) ); - } else { + } else { write (">"); write ( HtmlEncoder.encodeXml (property.getStringValue()) ); } @@ -171,9 +224,15 @@ public class XmlWriter extends OutputStreamWriter implements XmlConstants { /** * loop through the children-array and print them as */ - private void writeChildren (INode node, int level) throws IOException { + 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()) { + while (e.hasMoreElements()) { INode nextNode = (INode)e.nextElement(); write (nextNode, "hop:child", level); } @@ -183,16 +242,22 @@ public class XmlWriter extends OutputStreamWriter implements XmlConstants { * write an opening tag for a node. Include id and prototype, use a * name if parameter is non-empty. */ - public void writeTagOpen (INode node, String name) throws IOException { + public void writeTagOpen (INode node, String name) throws IOException { 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); } @@ -200,7 +265,7 @@ public class XmlWriter extends OutputStreamWriter implements XmlConstants { * write a closing tag for a node * e.g. */ - public void writeTagClose (INode node, String name) throws IOException { + public void writeTagClose (INode node, String name) throws IOException { write (prefix.toString()); write (" + * been written out before. + * e.g. */ - public void writeReferenceTag (INode node, String name) throws IOException { + 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 ("/>"); @@ -229,10 +294,10 @@ public class XmlWriter extends OutputStreamWriter implements XmlConstants { /** * retrieve prototype-string of a node, defaults to "hopobject" */ - private String getNodePrototype( INode node ) { - if ( node.getPrototype()==null || "".equals(node.getPrototype()) ) { + private String getNodePrototype( INode node ) { + if ( node.getPrototype()==null || "".equals(node.getPrototype()) ) { return "hopobject"; - } else { + } else { return node.getPrototype(); } } @@ -240,12 +305,12 @@ public class XmlWriter extends OutputStreamWriter implements XmlConstants { /** * TransientNode produces a different ID each time we call the getID()-method * this is a workaround and uses hashCode if INode stands for a TransientNode. - */ - private String getNodeIdentifier( INode node ) { - try { + */ + private String getNodeIdentifier( INode node ) { + try { TransientNode tmp = (TransientNode)node; return Integer.toString( tmp.hashCode() ); - } catch ( ClassCastException e ) { + } catch ( ClassCastException e ) { return node.getID(); } } @@ -253,7 +318,7 @@ public class XmlWriter extends OutputStreamWriter implements XmlConstants { public void writeln(String str) throws IOException { write (str); write (LINESEPARATOR); - } + } }