/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/jdomobjects/XDescriptors.java,v 1.4 2005/01/12 13:12:05 pnever Exp $
 * $Revision: 1.4 $
 * $Date: 2005/01/12 13:12:05 $
 *
 * ====================================================================
 *
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.slide.store.tamino.jdomobjects;

import com.softwareag.common.instrumentation.logging.Level;
import com.softwareag.common.instrumentation.logging.Logger;
import com.softwareag.common.instrumentation.logging.LoggerFactory;
import com.softwareag.common.instrumentation.logging.LoggerUtil;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Vector;
import org.apache.slide.content.NodeProperty;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionDescriptors;
import org.apache.slide.content.NodeRevisionNumber;
import org.apache.slide.lock.NodeLock;
import org.apache.slide.store.tamino.common.IDescriptors;
import org.apache.slide.store.tamino.common.IDescriptorsDocument;
import org.apache.slide.store.tamino.common.XGlobals;
import org.apache.slide.store.tamino.datastore.XUtilDbSession;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.util.ClassName;
import org.apache.slide.util.JDom;
import org.apache.slide.util.XAssertionFailed;
import org.apache.slide.util.XUri;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;

/**
 * Describes the XDescriptorsStore's view to a Descriptors object.
 *
 * If an XDescriptors is created in purpose to be written to Tamino, be sure
 * to set the tsdLanguage before creating the document.
 *
 * @author martin.wallmer@softwareag.com
 *
 * @version $Revision: 1.4 $
 */
public class XDescriptors
    implements IDescriptors, IDescriptorsDocument, XGlobals, XJdom
{
    private static final String LOGNAME = LoggerUtil.getThisClassName();
    private static final String CLASSNAME = new ClassName(LOGNAME).getPlainName();
    private static final Logger logger = LoggerFactory.getLogger(LOGNAME);
    
    
    //--
    
    /** ino:id; null if not yet defined */
    private String descriptorsId;
    
    private final String contentCollection;
    
    private final Namespace contentNamespace;
    
    /** keep track of own state */
    private final XStateMachine stateMachine;
    
    /** indicates, if this resource is currently in readOnly mode */
    private boolean readOnly = true;
    
    /** may be null */
    private ObjectNode objectNode;
    
    /** may be null */
    private NodeRevisionDescriptors revisionDescriptors;
    
    /**
     ** There are at most 2 descriptors in the list: 1.0 (main), 0.0 (backup)
     ** If the resource is not under version control, there is exactly one.
     **/
    private final List descriptorList;
    
    /** list of NodeLock objects (with objectNode == null if loaded from xml) */
    private final List locks;
    
    /** the universal uri this Descriptors object is bound to */
    private final String uuri;
    
    private String tsdLanguage = XGlobals.TSD4_SCHEMA_VERSION;
    
    /**
     * needed to know which kind of XML to be created (knownProperties).
     *
     * @param    tsdLanguage         a  String
     *
     */
    public void setTsdLanguage (String tsdLanguage) {
        this.tsdLanguage = tsdLanguage;
    }
    
    public XDescriptors(String contentCollection, Namespace contentNamespace, String uuri) {
        this(null, contentCollection, contentNamespace, null, null, new ArrayList(),
             new ArrayList(), uuri, new XStateMachine());
    }
    
    /**
     * Creates a descriptors object from a JDOM document.
     * When called after object is read from datastore, state is NO_STATE, when
     * called from getClone (), take whatever state is.
     * <p>
     * Is protected to give test tools the chance to overwrite and manipulate the state.
     *
     * @pre        (descriptors != null)
     */
    public XDescriptors (String descriptorsId,
                         String contentCollection, Namespace contentNamespace,
                         ObjectNode objectNode, NodeRevisionDescriptors revisionDescriptors,
                         List descriptorList, List locks,
                         String uuri, XStateMachine stateMachine) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "<init>", new Object[] {objectNode, uuri, stateMachine} );
        
        this.descriptorsId = descriptorsId;
        this.contentCollection = contentCollection;
        this.contentNamespace = contentNamespace;
        this.objectNode = objectNode;
        this.revisionDescriptors = revisionDescriptors;
        this.descriptorList = descriptorList;
        this.locks = locks;
        this.uuri = uuri;
        this.stateMachine = stateMachine;
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "<init>" );
    }
    
    // --------------- implementations for IDesc ------------------------
    /**
     * retrieves the current state of this Descriptors object
     *
     * @return     the current state of this Descriptors object
     */
    public int getState () {
        return stateMachine.state();
    }
    
    // --------------- implementations for IDescriptors -------------------
    /**
     * retrieves the uuri of this resource.
     *
     * @return   the uuri of this resource
     *
     */
    public String getUuri () {
        return uuri;
    }
    
    
    
    /**
     * Sets the structure aspect of this resource. Caution: the objectNode's uuri overwritten
     * with the value of this.getUuri().
     *
     * @param      newObjectNode   the structure aspect of this resource
     */
    public void setObjectNode (ObjectNode newObjectNode) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "setObjectNode", new Object[] {objectNode} );
        
        if (objectNode != null) {
            throw new IllegalStateException(newObjectNode.getUuri() + " " + objectNode.getUuri());
        }
        objectNode = newObjectNode;
        
        // Note that the objectNode's uuri is silently overwritten by this descriptor's uuri,
        // even if the objectNode's uuri is not null. This is necessary because - e.g. -
        // webdav's COPY method uses objectNode.cloneObject() to create a new objectNode
        // - and cloneObject copies the uuri.
        objectNode.setUuri(uuri);
        stateMachine.set();
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "setObjectNode" );
    }
    
    /**
     * retrieves the structure aspect of this resource.
     *
     * @pre
     * @post
     *
     * @return     the structure aspect of this resource
     */
    public ObjectNode getUuriObjectNode () {
        return objectNode == null? null : objectNode.cloneObject();
    }
    
    public ObjectNode getObjectNode (String uri) {
        ObjectNode result;
        
        if (objectNode == null) {
            return null;
        }
        result = objectNode.cloneObject();
        result.setUri(uri);
        return result;
    }
    
    /**
     * removes the structure aspect of this resource.
     *
     * @pre        object != null
     * @post
     *
     * @return     true
     */
    public boolean removeObjectNode () {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "removeObjectNode");
        
        objectNode = null;
        stateMachine.remove();
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "removeObjectNode");
        
        return true;
    }
    
    /**
     * sets the revisions aspect of this resource.
     *
     * @pre        newRevisionDescriptors != null
     *
     * @param      newRevisionDescriptors   the revisions aspect of this resource
     */
    public void setRevisionDescriptors
        (NodeRevisionDescriptors newRevisionDescriptors)
    {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "setRevisionDescriptors", new Object [] {revisionDescriptors});
        
        prepare();
        revisionDescriptors = newRevisionDescriptors;
        stateMachine.set();
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "setRevisionDescriptors");
    }
    
    public NodeRevisionDescriptors getUuriRevisionDescriptors () {
        return revisionDescriptors == null? null : revisionDescriptors.cloneObject();
    }
    
    /**
     * retrieves the revisions aspect of this resource.
     *
     * @pre        object != null
     * @post
     *
     * @return     the revisions aspect of this resource
     */
    public NodeRevisionDescriptors getRevisionDescriptors ( String uri) {
        NodeRevisionDescriptors result;
        
        if (revisionDescriptors == null) {
            return null;
        }
        result = revisionDescriptors.cloneObject();
        result.setUri(uri);
        return result;
    }
    
    /**
     * removes the revisions aspect of this resource.
     *
     * @pre        uuri.equals (revisionDescriptors.getUuri())
     * @post
     *
     * @param      toBeRemoved   the revisions aspect of this resource. TODO: not used!
     *
     * @return     false if already deleted
     */
    public boolean removeRevisionDescriptors (NodeRevisionDescriptors toBeRemoved){
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "removeRevisionDescriptors", new Object [] {toBeRemoved});
        
        boolean result = (revisionDescriptors != null);
        
        if (result) {
            revisionDescriptors = null;
            stateMachine.remove();
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "removeRevisionDescriptors", new Boolean (result));
        
        return result;
    }
    
    /**
     * sets a specific revision aspect within this resource.
     *
     * @pre        object != null
     * @post
     *
     * @param      revisionDescriptor   the revision aspect of this resource
     */
    public void setRevisionDescriptor (NodeRevisionDescriptor revisionDescriptor) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "setRevisionDescriptor", new Object [] {revisionDescriptor});
        
        prepare();
        
        // TODO:
        // move this call into the XDescriptorsStore. Might be possible because modCount is gone ...
        XNodeRevisionDescriptor.removeSpecialProperties(revisionDescriptor);
        
        descriptorList.add(revisionDescriptor);
        if (descriptorList.size() > 2) {
            throw new XAssertionFailed();
        }
        
        stateMachine.set();
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "setRevisionDescriptor");
    }
    
    /**
     * retrieves a specific revision aspect within this resource.
     *
     * @pre        revisionNumber != null
     * @post
     *
     * @param      revisionNumber identifies a specific revision within
     *             current resource
     *
     * @return     the revision identified by revisionNumber
     */
    public NodeRevisionDescriptor getRevisionDescriptor
        (NodeRevisionNumber revisionNumber)
    {
        if (revisionNumber == null) {
            // TODO: throw new XAssertionFailed();
            return null;
        }
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "getRevisionDescriptor", new Object [] {revisionNumber});
        
        NodeRevisionDescriptor result;
        
        result = XNodeRevisionDescriptor.findElement (descriptorList, revisionNumber);
        if (result != null) {
            result = result.cloneObject();
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "getRevisionDescriptor", result);
        
        return result;
    }
    
    /**
     * Get enumeration of all descriptor aspects
     *
     * @return      Enumeration containing all descriptor aspects
     */
    public Enumeration getRevisionDescriptorList () {
        Vector result;
        Iterator iter;
        
        result = new Vector();
        iter = descriptorList.iterator();
        while (iter.hasNext()) {
            result.add(((NodeRevisionDescriptor) iter.next()).cloneObject());
        }
        return result.elements();
    }
    
    /**
     * removes a specific revision within this resource.
     *
     * @pre        revisionDescriptor != null
     * @post
     *
     * @param      revisionDescriptor   the revision aspect to be removed
     *
     * @return     true if the revisionDescriptor was found and removed
     */
    public boolean removeRevisionDescriptor
        (NodeRevisionDescriptor revisionDescriptor)
    {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "removeRevisionDescriptor", new Object [] {revisionDescriptor});
        
        boolean result;
        NodeRevisionDescriptor toBeDeleted;
        
        toBeDeleted = XNodeRevisionDescriptor.findElement (descriptorList, revisionDescriptor.getRevisionNumber());
        // should never happen
        result = (toBeDeleted != null);
        
        descriptorList.remove(toBeDeleted);
        stateMachine.remove();
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "getRevisionDescriptor", new Boolean (result));
        
        return result;
    }
    
    /**
     * sets a lock for this resource
     *
     * @pre        lock != null
     * @post
     *
     * @param      lock   the lock to be set
     */
    public void setNodeLock (NodeLock lock) {
        
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "setNodeLock", new Object [] {lock});
        
        prepare();
        locks.add(lock);
        stateMachine.set();
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "setNodeLock");
    }
    
    /**
     * removes a lock for this resource
     *
     * @pre        lock != null
     * @post
     *
     * @param   lock the lock to be removed
     *
     * @return  true, if found and deleted
     */
    public boolean removeLock (NodeLock lock) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "removeLock", new Object [] {lock});
        
        prepare();
        
        boolean result = false;
        String lockIdToRemove = lock.getLockId();
        Iterator it = locks.iterator();
        while (it.hasNext()) {
            NodeLock current = (NodeLock)it.next();
            if (current.getLockId().equals (lockIdToRemove)) {
                locks.remove(current);
                result = true;
                stateMachine.remove();
                break;
            }
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "removeLock", new Boolean(result));
        
        return result;
    }
    
    /**
     * get all locks for this resource
     *
     * @return      Enumeration containing all locks
     */
    public Enumeration getLocks ( String uri ) {
        Vector result;
        Iterator iter;
        NodeLock tmp;
        
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "getLocks");
        
        result = new Vector();
        iter = locks.iterator();
        while (iter.hasNext()) {
            tmp = ((NodeLock) iter.next()).cloneObject();
            tmp.setObjectUri(uri);
            result.add(tmp);
        }
        Enumeration en = result.elements();
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "removeLock", en);
        
        return en;
    }
    
    
    private void prepare () {
        if (readOnly)
            throw new IllegalStateException();
        // check locking
        // check readOnly
    }
    
    
    // --------------- implementations for IDescriptorsDocument ----------
    /**
     * retrieves the jdom document representing this Descriptors object.
     *
     * @return     the jdom document representing this Descriptors object
     */
    public Document toXml () {
        return toXml (tsdLanguage);
    }
    
    /**
     ** @return a newly created Document
     **/
    public Document toXml (String schemaLanguage) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "toXml");
        
        Element root;
        Document doc;
        
        root = new Element (contentCollection, contentNamespace);
        
        root.setAttribute (new Attribute (A_URI, uuri));
        root.setAttribute (A_DEPTH, Integer.toString(getDepth (uuri)));
        root.setAttribute (A_DTD_VERSION, SCHEMA_VERSION);
        root.setAttribute (A_ASPECTS, new Integer (stateMachine.getActiveAspects()).toString());
        
        // TODO: only in noBinding case
        String lastPathSegment = getLastPathSegment();
        root.setAttribute (A_LAST_PATHSEGMENT, lastPathSegment);
        
        if (objectNode != null) {
            root.addContent(XObjectNode.toXml(objectNode));
        }
        if (revisionDescriptors != null) {
            root.addContent(XNodeRevisionDescriptors.toXml(revisionDescriptors));
        }
        root.addContent (XNodeRevisionDescriptor.toXmlList (descriptorList, schemaLanguage));
        root.addContent (XNodeLock.toXmlList(locks));
        XUtilDbSession.setInoId(root, getDescriptorsId());
        doc = new Document(root);
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "toXml", doc);
        
        return doc;
    }
    
    public void toStream (XmlStream xs) {
        toStream (xs, tsdLanguage);
    }

    public void toStream(XmlStream xs, String schemaLanguage) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "toStream");
        
        xs.addStartElement(contentCollection);
        xs.addAttribute(A_URI, uuri); // use escaping: the store name is part of the uuri - and it might contain arbitrary characters
        xs.addAttributeUnescaped(A_DEPTH, Integer.toString(getDepth (uuri)));
        xs.addAttributeUnescaped(A_DTD_VERSION, SCHEMA_VERSION);
        xs.addAttributeUnescaped(A_ASPECTS, new Integer (stateMachine.getActiveAspects()).toString());
        if (getDescriptorsId()!=null)
            xs.addAttributeUnescaped("ino:id",getDescriptorsId());
        
        // todo: only in noBinding case
        String lastPathSegment = getLastPathSegment();
        xs.addAttribute(A_LAST_PATHSEGMENT, lastPathSegment);
        xs.addEndElement();
        
        if (objectNode != null) {
            XObjectNode.toStream(xs, objectNode);
        }
        if (revisionDescriptors != null) {
            XNodeRevisionDescriptors.toStream(xs, revisionDescriptors);
        }
        XNodeRevisionDescriptor.toStreamList (xs, descriptorList, schemaLanguage);
        XNodeLock.toStreamList(xs, locks);
        
        xs.addCloseElement(contentCollection);

        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "toStream", xs);
    }

    // TODO: user XUri class
    private String getLastPathSegment() {
        String lastPathSegment = uuri;
        if (uuri.endsWith("/"))
            lastPathSegment = lastPathSegment.substring (0, lastPathSegment.length() -1);
        int pos = uuri.lastIndexOf('/');
        
        if (pos >= 0)
            lastPathSegment = uuri.substring (pos + 1);
        return lastPathSegment;
    }
    
    // TODO: user XUri class
    private static int getDepth (String uri) {
        // TODO: expensive
        StringTokenizer ut = new StringTokenizer (uri, XUri.SEP);
        int ntok = ut.countTokens();
        
        return ntok;
    }
    
    //
    // sets the jdom document representing this Descriptors object. Called
    // when object is read from database.
    //
    // @param     the current state of this Descriptors object
    //
    //    public void setDocument (Document document) {
    //      throw new UnsupportedOperationException();
    //    }
    
    /**
     * retrieves the descriptorsId. The descriptorsId is a string representing
     * physical location of this object within database (schema and ino:id).
     * If a Descriptors object is in cache and must accessed in datastore (update
     * or delete), access can be faster.
     *
     * @return     a string representing physical location
     */
    public String getDescriptorsId () {
        return descriptorsId;
    }
    
    /**
     * sets the descriptorsId.
     *
     * @param   descriptorsId  the current state of this Descriptors object
     */
    public void setDescriptorsId (String descriptorsId) {
        this.descriptorsId = descriptorsId;
    }
    
    /**
     * Returns the string repesentation of this object.
     * @return the string repesentation of this object
     */
    public String toString () {
        return JDom.outputter().outputString (toXml());
    }
    
    public String details() {
        StringBuffer b = new StringBuffer();
        b.append(String.valueOf(stateMachine));
        b.append(" uuri=").append(uuri);
        Iterator iter = descriptorList.iterator();
        while (iter.hasNext()) {
            NodeRevisionDescriptor nrd = (NodeRevisionDescriptor)iter.next();
            NodeProperty p = nrd.getProperty("xdavContentId", TAMINO_NAMESPACE_URI);
            String v = "";
            if (p != null) {
                v = String.valueOf(p.getValue());
            }
            b.append(" "+nrd.getBranchName()+"_"+nrd.getRevisionNumber()+"["+v+"]");
        }
        return b.toString();
    }
    
    /**
     * sets the readOnly flag of this resource
     *
     * @param  readOnly    true or false
     */
    public void  setReadOnly (boolean readOnly) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "setReadOnly", new Object [] {new Boolean (readOnly)});
        
        this.readOnly = readOnly;
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "setReadOnly");
    }
    
    /**
     * retrieves the readOnly flag of this resource
     *
     * @return      true if this resource is readOnly else false
     */
    public boolean  isReadOnly () {
        return readOnly;
    }
    
    /**
     * Returns true if the state of this resource is deleted
     *
     * @return      true   if this resource's state is either DELETED or
     *                     CREATED_DELETED
     */
    public boolean  isDeleted() {
        return stateMachine.state() == DELETED ||
            stateMachine.state() == CREATED_DELETED;
    }
    
    /**
     * Creates a deep clone of this IDescriptors object.
     *
     * @return     a deep copy of this IDescriptors object
     */
    public IDescriptors getClone (String newUuri) {
        XDescriptors result;
        
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "getClone");
        
        result = new XDescriptors(descriptorsId, contentCollection, contentNamespace,
                                  getUuriObjectNode(), getUuriRevisionDescriptors(),
                                  getList(getRevisionDescriptorList()),
                                  getList(getLocks(null)),
                                  newUuri, new XStateMachine(stateMachine));
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "getClone", result);
        
        return result;
    }
    
    private static List getList(Enumeration en) {
        List result;
        
        result = new ArrayList();
        while (en.hasMoreElements()) {
            result.add(en.nextElement());
        }
        return result;
    }
    
    /**
     * call this to tell this IDescriptors object that it has been successfully
     * committed.
     */
    public void commitEvent () {
        stateMachine.commit();
    }
    
    /**
     * Get the value of the given property for the given NRN of this descriptors object.
     * Default namespace is used.
     *
     * @pre   nrn != null
     * @param nrn the node revision number
     * @param pname the property name
     * @return the property value (null, if property not found)
     */
    public Object getPropertyValue( NodeRevisionNumber nrn, String pname ) {
        return getPropertyValue (nrn, pname, null);
    }
    
    /**
     * Get the value of the given property for the given NRN of this descriptors object.
     * If namespace == null, the default namespace ise used
     *
     * @param    nrn                 a  NodeRevisionNumber
     * @param    pname               a  String
     * @param    namespace           a  String
     *
     * @return   an Object
     *
     */
    public Object getPropertyValue( NodeRevisionNumber nrn, String pname, String namespace ) {
        Object result = null;
        NodeRevisionDescriptor nrd = getRevisionDescriptor( nrn );
        if( nrd == null ) {
            if( logger.isLoggable(Level.FINE) )
                logger.fine( CLASSNAME, "getPropertyValue",
                            "Revision descriptor "+nrn+" for "+uuri+" not found" );
            return result;
        }
        
        NodeProperty p = null;
        if (namespace == null)
            p = nrd.getProperty( pname );
        else
            p = nrd.getProperty (pname, namespace);
        
        if( p == null )
            return result;
        
        return p.getValue();
    }
    
    /**
     * Set the value of the given property for the given NRN of this descriptors object.
     * @pre   nrn != null
     * @param nrn the node revision number
     * @param pname the property name
     * @param pvalue the property value
     */
    private void setPropertyValue( NodeRevisionNumber nrn, String pname, String pnamespace, Object pvalue ) {
        NodeRevisionDescriptor nrd = getRevisionDescriptor( nrn );
        if( nrd == null ) {
            if( logger.isLoggable(Level.FINE) )
                logger.fine( CLASSNAME, "setPropertyValue",
                            "Revision descriptor "+nrn+" for "+uuri+" not found" );
            return;
        }
        
        //the nrd must be replaces to update the jdom state of this descriptors object
        prepare();
        removeRevisionDescriptor( nrd );
        nrd.setProperty( pname, pnamespace, pvalue );
        setRevisionDescriptor( nrd );
    }
    
    /**
     * Get the value of the given property for the latest revision of this
     * descriptors object. Use the given namespace. If namespace is null,
     * use the default namespace
     *
     * @param    pname               a  String
     * @param    namespace           a  String
     *
     * @return   an Object
     *
     */
    public Object getPropertyValue( String pname, String namespace ) {
        Object result = null;
        NodeRevisionDescriptors nrds = getUuriRevisionDescriptors();
        if( nrds != null ) {
            NodeRevisionNumber nrn = nrds.getLatestRevision();
            if (namespace == null)
                result = getPropertyValue( nrn, pname );
            else
                result = getPropertyValue( nrn, pname, namespace );
        }
        else {
            if( logger.isLoggable(Level.FINE) )
                logger.fine( CLASSNAME, "getPropertyValue",
                            "Revision descriptors for "+uuri+" not found" );
        }
        return result;
    }
    
    /**
     * Set the value of the given property for the latest revision of this descriptors object.
     * @param pname the property name
     * @param pvalue the property value
     */
    public void setPropertyValue( String pname, String pnamespace, Object pvalue ) {
        NodeRevisionDescriptors nrds = getUuriRevisionDescriptors();
        if( nrds != null ) {
            NodeRevisionNumber nrn = nrds.getLatestRevision();
            setPropertyValue( nrn, pname, pnamespace, pvalue );
        }
        else {
            if( logger.isLoggable(Level.FINE) )
                logger.fine( CLASSNAME, "setPropertyValue",
                            "Revision descriptors for "+uuri+" not found" );
        }
    }
    
    /**
     * Checks the validy of this document. Uses by the DbSession to check incomming and outgoing
     * descriptors.
     */
    public void validate() {
        try {
            doValidate();
        } catch (XAssertionFailed e) {
            System.out.println(toString());
            throw new XAssertionFailed ("XDescriptors.validate() failed: " + e.getMessage()
                                            + "\ndescriptor: " + toString(), e);
        }
    }
    
    public void doValidate () {
        if (stateMachine == null) {
            throw new XAssertionFailed("no state machine");
        }
        if (descriptorList == null) {
            throw new XAssertionFailed("no descriptorList");
        }
        if (locks == null) {
            throw new XAssertionFailed("no locks");
        }
        stateMachine.checkState();
        
        if (isDeleted()) {
            if (stateMachine.getActiveAspects() != 0) {
                throw new XAssertionFailed("active aspects: " + stateMachine.getActiveAspects());
            }
            if (objectNode != null) {
                throw new XAssertionFailed("objectNode not null");
            }
            if (revisionDescriptors != null) {
                throw new XAssertionFailed("revisionDescriptors not null");
            }
            if (descriptorList.size() != 0) {
                throw new XAssertionFailed("descriptorList not empty");
            }
            if (locks.size() != 0) {
                throw new XAssertionFailed("locks not empty");
            }
        } else {
            if (objectNode == null) {
                throw new XAssertionFailed("objectNode missing");
            }
            // TODO: version (VR) URIs dont have NRDS
            //            if (revisionDescriptors == null) {
            //                throw new XAssertionFailed("revisionDescriptors missing");
            //            }
            if (descriptorList.size() != 1 && descriptorList.size() != 2) {
                throw new XAssertionFailed("1 or 2 descriptorList elements expected: " + descriptorList.size());
            }
        }
    }
}


