// You can redistribute this software and/or modify it under the terms of
// the Ozone Library License version 1 published by ozone-db.org.
//
// The original code and portions created by SMB are
// Copyright (C) 1997-@year@ by SMB GmbH. All rights reserved.
//
// $Id: XMLContainer.java,v 1.1 2001/12/18 11:03:24 per_nyfelt Exp $
package org.ozoneDB.xml.util;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.ObjectInput;
import java.io.Externalizable;
import org.ozoneDB.*;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.ContentHandler;
import org.infozone.tools.xml.queries.XObject;
import org.infozone.tools.xml.queries.XPathQuery;
import org.infozone.tools.xml.queries.XUpdateQuery;
import org.ozoneDB.xml.dom.DocumentProxy;
/**
* <p>This class is the central part of the ozone/XML API. Basically it provides a
* persistent container for a XML document, which is stored in an ozone database.</p>
*
* <p><dl><dt>IMPORTANT:</dt><dd>Before calling one of the store or extract methods the
* thread <b>must</b> have joined a transaction.</dd></dl>
* </p>
*
* Usage of the SAX methods is recomended, because of the better performance.
*
* @version $Revision: 1.1 $ $Date: 2001/12/18 11:03:24 $
* @author <a href="http://www.smb-tec.com">SMB</a>
*/
public class XMLContainer implements Externalizable, SAXChunkProducerDelegate {
// Class data
public final static boolean debug = false;
// Data
private XMLContainerHelper helper;
private transient OzoneInterface db;
private transient Document doc;
/** True if this object runs outside an ozone server. */
private transient boolean runsExternal;
// Class methods
/**
* Creates a new container with the given name.
* @param _db The database where the container will be stored.
* @param _docName The name of the container (and the document).
* @return the new container.
*/
public static XMLContainer newContainer( OzoneInterface _db, String _docName ) throws Exception {
XMLContainerHelper helper = (XMLContainerHelper)_db.createObject(
"org.ozoneDB.xml.util.XMLContainerHelperImpl",
OzoneInterface.Public,
_docName );
return new XMLContainer( _db, helper );
}
/**
* Returns the XMLContainer representing the document with the given name.
* @param _db The database where the container is stored.
* @param _docName The name under which the container has been stored.
* @return the container for the given name or null if the container
* does not exist.
*/
public static XMLContainer forName( OzoneInterface _db, String _docName ) throws Exception {
XMLContainerHelper helper = (XMLContainerHelper)_db.objectForName( _docName );
return helper != null ? new XMLContainer( _db, helper ) : null;
}
/**
* Returns the XMLContainer representing the document the given node
* belongs to.
* @param _db The database where the container is stored.
* @param _pNode A node of the document the container represents.
* @return the container for the given node or null if the node has not
* been stored using the XMLContainer class.
*/
public static XMLContainer forNode( OzoneInterface _db, Node _pNode ) throws Exception {
if (!(_pNode instanceof OzoneProxy)) {
throw new IllegalArgumentException("Not a persistent DOM node: " + _pNode.getClass().getName());
}
DocumentProxy pDoc = _pNode instanceof Document
? (DocumentProxy)_pNode
: (DocumentProxy)_pNode.getOwnerDocument();
XMLContainerHelper helper = (XMLContainerHelper)pDoc.getContainer();
return helper != null ? new XMLContainer( _db, helper ) : null;
}
// Constructors
protected XMLContainer( OzoneInterface _db, XMLContainerHelper _helper ) {
db = _db;
helper = _helper;
runsExternal = (db instanceof ExternalDatabase);
}
// Methods
/**
* Changes the name of this container.
* @param _name The new name of this container or null to remove the
* current name.
*/
public void setName( String _name ) throws Exception {
if (helper == null) {
throw new IllegalStateException( "Document has already been deleted." );
}
db.nameObject( helper, _name );
}
/**
* Deletes the container (and the associated document) from the database.
*/
public synchronized void delete() throws Exception {
if (helper == null) {
throw new IllegalStateException("Document has already been deleted.");
}
db.deleteObject(helper);
helper = null;
doc = null;
}
/**
* Get the underlying persistent document that this container is working on.
* @return The persistent document.
*/
public Document getPDocument() throws Exception {
if (helper == null) {
throw new IllegalStateException( "Document has already been deleted." );
}
// not synchronized because it wouldn't make a difference here
if (doc == null) {
doc = helper.getDocument();
}
return doc;
}
/**
* Stores a transient DOM tree database. The newly created Nodes are
* appended to the Document Node of this container.
*
* @see #storeDOM(Node, Node)
*/
public void storeDOM( Document _tNode ) throws Exception {
if (helper == null)
throw new IllegalStateException("Document has already been deleted.");
storeDOM( null, _tNode );
}
/**
* Stores a transient node into the database.
*
* <p>Before calling this method the current thread <b>must</b> have joined an
* {@link org.ozoneDB.ExternalTransaction explicit Transaction}. This is an
* exception to the normal case, where an implicit transaction (handled by
* Ozone) is more appropriate.</p>
* <p><dl><dt>Note:</dt><dd>If ever possible the corresponding
* {@link #storeSAX(Node) SAX method} should be used, because the performance
* of SAX storage is much better.</dd></dl></p>
*
* @param _pNode The persistent node where the stored node will be appended to.
* Null replaces the current document.
* @param _tnode The transient node to be stored.
*
* @throws IllegalStateException if the underlying document has already been deleted.
* @throws IllegalArgumentException if _tnode was null.
* @throws IllegalStateException if the current thread has not joined a
* {@link org.ozoneDB.ExternalTransaction transaction}.
* @see org.ozoneDB.ExternalTransaction
* @see org.ozoneDB.ExternalDatabase#newTransaction()
*/
public void storeDOM(Node _pNode, Node _tNode ) throws Exception {
long time = 0;
long starttime = 0;
if (helper == null) {
throw new IllegalStateException( "Document has already been deleted." );
}
if (_tNode == null) {
throw new IllegalArgumentException( "tNode == null." );
}
// thread must have joined a transaction
if (runsExternal && ((ExternalDatabase)db).currentTransaction() == null) {
throw new IllegalStateException( "Thread must have joined a transaction!" );
}
SAXChunkConsumer consumer = helper.beginInputSequence(_pNode);
ModifiableNodeList mnl = new ModifiableNodeList(1);
mnl.addNode(_tNode);
SAXChunkProducer producer = new SAXChunkProducer(mnl);
ChunkOutputStream cos = producer.chunkStream();
do {
cos.reset();
producer.createNextChunk();
if (XMLContainer.debug) {
starttime = System.currentTimeMillis();
}
consumer = helper.putChunk(cos.toByteArray(), consumer);
if (XMLContainer.debug) {
time += (System.currentTimeMillis() - starttime);
}
} while (!cos.getEndFlag());
if (XMLContainer.debug) {
System.out.println("DOM store: store in db time: " + time + " ms");
}
}
/**
* Stores a DOM tree represented by SAX events in the datasabase. The
* newly created Nodes are appended to the Document Node of this container.
*
* @see #storeSAX(Node)
*/
public ContentHandler storeSAX() throws Exception {
if (helper == null) {
throw new IllegalStateException( "Document has already been deleted." );
}
return storeSAX( null );
}
/**
* Stores XML represented by SAX events into the database.
* <p>The entire storage process <b>must</b> be enclosed by an
* {@link org.ozoneDB.ExternalTransaction explicit Transaction}. This is an
* exception to the normal case, where an implicit transaction (handled by
* Ozone) is more appropriate. The storage process starts with the call to this
* method and ends with the last event send to the content handler. The SAX
* events must be properly terminated, i.e. there must be an end event for
* for every start event, otherwise the correct storage can't be guaranteed.</p>
*
* @param _pNode The persistent node where the stored data will be appended to.
*
* @return the content handler which stores all XML data it recieves into the
* database.
*
* @throws IllegalStateException if the underlying document has already been deleted.
* @throws IllegalStateException if the current thread has not joined a
* {@link org.ozoneDB.ExternalTransaction transaction}.
* @see org.ozoneDB.ExternalTransaction
* @see org.ozoneDB.ExternalDatabase#newTransaction()
*/
public ContentHandler storeSAX(Node _pNode) throws Exception {
if (helper == null) {
throw new IllegalStateException( "Document has already been deleted." );
}
// thread must have joined a transaction
if (runsExternal && ((ExternalDatabase)db).currentTransaction() == null) {
throw new IllegalStateException( "Thread must have joined a transaction!" );
}
// this eventually blocks if there is another thread storing something already
SAXChunkConsumer consumer = helper.beginInputSequence(_pNode);
SAXChunkProducer producer = new SAXChunkProducer(this);
producer.dbConsumer = consumer;
return producer;
}
/**
* This method is for internal use only. <b>Don't call it directly.</b>
*/
public void processChunk(SAXChunkProducer _producer) throws Exception {
if (XMLContainer.debug) {
System.out.print("XMLContainer.processChunk()... ");
}
ChunkOutputStream cos = _producer.chunkStream();
if (XMLContainer.debug) {
System.out.print("putChunk(" + cos.count + ")... ");
}
_producer.dbConsumer = helper.putChunk(cos.toByteArray(), _producer.dbConsumer);
}
/**
* @see #extractDOM(Document, Node, Node , int)
*/
public Document extractDOM( Document _domFactory ) throws Exception {
return (Document) extractDOM( _domFactory, (Node)null, null, -1 );
}
/**
* @see #extractDOM(Document, Node, Node , int)
*/
public Node extractDOM( Document _domFactory, Node _pNode, Node _appendTo) throws Exception {
return extractDOM( _domFactory, _pNode, _appendTo, -1 );
}
/**
* Extracts a given DOM node and all its descendants.
*
* <p>Before calling this method the current thread <b>must</b> have joined an
* {@link org.ozoneDB.ExternalTransaction explicit Transaction}. This is an
* exception to the normal case, where an implicit transaction (handled by
* Ozone) is more appropriate.</p>
*
* <p><dl><dt>Note:</dt><dd>If possible, the corresponding
* {@link #extractSAX(ContentHandler) SAX method} should be used, because the
* performance of SAX retrieval is much better.</dd></dl></p>
*
* @param _domFactory The DOM Document that is used to create the extracted DOM nodes.
* @param _pNode The persistent DOM node
* @param _appendTo The transient DOM node where the extracted content will
* will be appended to.
* @param _depth The number of hierarchy steps that should be extracted or
* null if the entire hierarchy should be extracted.
*/
public Node extractDOM ( Document _domFactory, Node _pNode, Node _appendTo, int _depth) throws Exception {
ModifiableNodeList mnl = null;
if (_pNode != null) {
mnl = new ModifiableNodeList(1);
mnl.addNode(_pNode);
}
NodeList resultList = extractDOM (_domFactory, mnl, _appendTo, _depth);
return resultList.item(0);
/* if (helper == null) {
throw new IllegalStateException ("Document has already been deleted.");
}
// thread must have joined a transaction
if (runsExternal && ((ExternalDatabase)db).currentTransaction() == null) {
throw new IllegalStateException( "Thread must have joined a transaction!" );
}
SAXChunkProducer producer = helper.beginOutputSequence( _pNode, _depth );
SAXChunkConsumer consumer = new SAXChunkConsumer(_domFactory, _appendTo);
ChunkOutputStream cos;
do {
producer = helper.createNextChunk( producer );
cos = producer.chunkStream();
consumer.processChunk( cos.toByteArray() );
cos.reset();
} while (!cos.getEndFlag());
return consumer.getResultNode();*/
}
public NodeList extractDOM (Document _domFactory, NodeList _pNodes, Node _appendTo, int _depth) throws Exception {
if (helper == null) {
throw new IllegalStateException ("Document has already been deleted.");
}
// thread must have joined a transaction
if (runsExternal && ((ExternalDatabase)db).currentTransaction() == null) {
throw new IllegalStateException( "Thread must have joined a transaction!" );
}
SAXChunkProducer producer = helper.beginOutputSequence( _pNodes, _depth );
SAXChunkConsumer consumer = new SAXChunkConsumer(_domFactory, _appendTo);
ChunkOutputStream cos;
do {
producer = helper.createNextChunk( producer );
cos = producer.chunkStream();
consumer.processChunk( cos.toByteArray() );
cos.reset();
} while (!cos.getEndFlag());
return consumer.getResultNodeList();
}
/**
* @see #extractSAX(ContentHandler, Node, int)
*/
public void extractSAX( ContentHandler _contentHandler ) throws Exception {
extractSAX( _contentHandler, null, -1 );
}
/**
* @see #extractSAX(ContentHandler, Node , int )
*/
public void extractSAX( ContentHandler _contentHandler, Node _pNode ) throws Exception {
extractSAX( _contentHandler, _pNode, -1 );
}
/**
* Extracts a given DOM node and all its descendants.
*
* <p>Before calling this method the current thread <b>must</b> have joined an
* {@link org.ozoneDB.ExternalTransaction explicit Transaction}. This is an
* exception to the normal case, where an implicit transaction (handled by
* Ozone) is more appropriate.</p>
*
* @param _contentHandler The ContentHandler that will receive the generated
* SAX events.
* @param _pNode The persistent DOM node that is the starting point, or null
* if the entire document should be extracted.
* @param _depth The number of hierarchy steps that should be extracted or
* null if the entire hierarchy should be extracted.
*/
public void extractSAX( ContentHandler _contentHandler, Node _pNode, int _depth ) throws Exception {
if (helper == null) {
throw new IllegalStateException( "Document has already been deleted." );
}
// thread must have joined a transaction
if (runsExternal && ((ExternalDatabase)db).currentTransaction() == null) {
throw new IllegalStateException( "Thread must have joined a transaction!" );
}
ModifiableNodeList mnl = null;
if (_pNode != null) {
mnl = new ModifiableNodeList(1);
mnl.addNode(_pNode);
}
SAXChunkProducer producer = helper.beginOutputSequence(mnl, _depth );
SAXChunkConsumer consumer = new SAXChunkConsumer( _contentHandler );
ChunkOutputStream cos;
do {
producer = helper.createNextChunk( producer );
cos = producer.chunkStream();
consumer.processChunk( cos.toByteArray() );
cos.reset();
} while (!cos.getEndFlag());
}
/**
* Create a new XUpdate query. XUpdate is a descriptive XML update
* language. XUpdate is the recommended way to change a document in the
* database. See <a href=http://www.xmldb.org>www.xmldb.org</a> for more
* information and some XUpdate documentation.
*
* @see OzoneXUpdateQuery
*/
public OzoneXUpdateQuery newXUpdateQuery() {
return new OzoneXUpdateQuery( this );
}
/**
* Create a new XPath query.
*
* @see OzoneXPathQuery
*/
public OzoneXPathQuery newXPathQuery() {
return new OzoneXPathQuery( this );
}
protected void executeXUpdate( OzoneXUpdateQuery _query ) throws Exception {
if (_query == null) {
throw new IllegalArgumentException( "_query == null." );
}
helper.executeXUpdate( _query );
}
protected XObject executeXPath( OzoneXPathQuery _query ) throws Exception {
if (_query == null) {
throw new IllegalArgumentException( "_query == null." );
}
return helper.executeXPath( _query );
}
/**
* Determines the absolute XPath for the given node.
*
* @param node The W3C DOM node whose XPath is to determine.
* @return The string representing the absolute XPath for this node.
*/
public String xpathForNode( Node _pnode ) {
if (helper == null) {
throw new IllegalStateException( "Document has already been deleted." );
}
return helper.xpathForNode( _pnode );
}
public void writeExternal( ObjectOutput _out ) throws IOException {
if (runsExternal) {
throw new IllegalStateException( "XMLContainer cannot be serialized outside an ozone server." );
}
_out.writeObject( helper );
}
public void readExternal( ObjectInput _in ) throws IOException, ClassNotFoundException {
if (runsExternal) {
throw new IllegalStateException( "XMLContainer cannot be deserialized outside an ozone server." );
}
helper = (XMLContainerHelper)_in.readObject();
db = org.ozoneDB.core.Env.currentEnv().database;
}
}