/*
 * $Header: /home/cvspublic/jakarta-slide/src/stores/org/apache/slide/store/txfile/AbstractXMLResourceDescriptor.java,v 1.7 2005/02/28 11:56:17 luetzkendorf Exp $
 * $Revision: 1.7 $
 * $Date: 2005/02/28 11:56:17 $
 *
 * ====================================================================
 *
 * 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.txfile;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.apache.slide.common.*;
import org.apache.slide.lock.LockTokenNotFoundException;
import org.apache.slide.lock.NodeLock;
import org.apache.slide.security.NodePermission;
import org.apache.slide.structure.*;
import org.apache.slide.content.*;

import org.jdom.*;
import org.jdom.input.*;
import org.jdom.output.*;

/**
 * Abstract class for encode all meta info of a resource into XML. 
 * 
 * Takes over very much code from UriProperties and AbstractUriProperties, that's why Marc is listed as author as well.
 * 
 * @see FileResourceManager
 * @see TxXMLFileDescriptorsStore
 */
public abstract class AbstractXMLResourceDescriptor {

    protected final Format outputFormat;
    protected Object txId;
    protected String uri;
    protected SimpleDateFormat dateFormat;
    protected boolean registeredForSaving = false;

    /** Stored object.*/
    protected ObjectNode object;

    /** Permissions vector. */
    protected Vector permissions;

    /** Locks vector.*/
    protected Vector locks;

    /** Revision descriptors.*/
    protected NodeRevisionDescriptors revisionDescriptors;

    /** Revision descriptor hashtable.*/
    protected Hashtable descriptor;


    protected static String booleanToString(boolean aBoolean) {
        return aBoolean ? "true" : "false";
    }

    protected static Element createBindings(String aParent, String aChild, Enumeration aEnum) {
        Element aElement = new Element(aParent);
        Element childNode;
        ObjectNode.Binding binding;
        while (aEnum.hasMoreElements()) {
            binding = (ObjectNode.Binding) aEnum.nextElement();
            childNode = new Element(aChild);
            childNode.setAttribute(new Attribute("name", binding.getName()));
            childNode.setAttribute(new Attribute("uuri", binding.getUuri()));
            aElement.addContent(childNode);
        }
        return aElement;
    }

    protected static Element createElements(String aParent, String aChild, Enumeration aEnum) {
        Element aElement = new Element(aParent);
        while (aEnum.hasMoreElements()) {
            Object aObject = aEnum.nextElement();
            Element aItem = new Element(aChild);
            aItem.setAttribute("val", aObject.toString());
            aElement.addContent(aItem);
        }
        return aElement;
    }

    protected static NodePermission decodePermission(Element aElement, String aUri) {
        String aRevisionNumber = aElement.getAttributeValue("revisionNumber");
        String aSubject = aElement.getAttributeValue("subjectUri");
        String aAction = aElement.getAttributeValue("actionUri");
        boolean aInheritable = new Boolean(aElement.getAttributeValue("inheritable")).booleanValue();
        boolean aNegative = new Boolean(aElement.getAttributeValue("negative")).booleanValue();
        return new NodePermission(aUri, aRevisionNumber, aSubject, aAction, aInheritable, aNegative);

    }

    protected static Element encodeNodePermission(NodePermission aPermission) {
        Element aElementPermission = new Element("permission");
        NodeRevisionNumber aRevisionNumber = aPermission.getRevisionNumber();
        if (aRevisionNumber != null) {
            aElementPermission.setAttribute("revisionNumber", encodeRevisionNumber(aRevisionNumber));
        }
        aElementPermission.setAttribute("subjectUri", aPermission.getSubjectUri());
        aElementPermission.setAttribute("actionUri", aPermission.getActionUri());
        aElementPermission.setAttribute("inheritable", booleanToString(aPermission.isInheritable()));
        aElementPermission.setAttribute("negative", booleanToString(aPermission.isNegative()));
        return aElementPermission;
    }

    protected static Element encodeRevisionDescriptor(NodeRevisionDescriptor aDescriptor) {
        Element aRevisions = new Element("revisions");
        aRevisions.setAttribute("branchName", aDescriptor.getBranchName());
        aRevisions.setAttribute("number", encodeRevisionNumber(aDescriptor.getRevisionNumber()));
        aRevisions.addContent(createElements("labels", "label", aDescriptor.enumerateLabels()));
        Element aProperties = new Element("properties");

        for (Enumeration aEnum = aDescriptor.enumerateProperties(); aEnum.hasMoreElements();) {
            Object aObject = aEnum.nextElement();
            // System.out.println("---------- encodeRevisionDescriptor aObject="+aObject+" "+aObject.getClass().getName());
            NodeProperty aProp = (NodeProperty) aObject;
            aProperties.addContent(encodeNodeProperty(aProp));
        }
        aRevisions.addContent(aProperties);
        return aRevisions;
    }

    protected static Element encodeNodeProperty(NodeProperty aProp) {
        Element aElement = new Element("property");
        aElement.setAttribute("name", aProp.getName());
        aElement.setAttribute("namespace", aProp.getNamespace());
        aElement.setAttribute("value", aProp.getValue().toString());
        aElement.setAttribute("type", aProp.getType());
        aElement.setAttribute("protected", booleanToString(aProp.isProtected()));
        
        
        Enumeration aEnum = aProp.enumeratePermissions();
        if (aEnum.hasMoreElements()) {
            Element aPermissions = new Element("permissions");
            for (; aEnum.hasMoreElements();) {
                NodePermission aPermission = (NodePermission) aEnum.nextElement();
                aPermissions.addContent(encodeNodePermission(aPermission));
            }
            aElement.addContent(aPermissions);
        }
        return aElement;
    }

    protected static Vector createVector(Element aElement, String aParentName, String aChildName) {
        Element aParent = aElement.getChild(aParentName);
        Vector aRet = new Vector();
        // System.out.println("--------- createVector  aParentName="+aParentName+" aChildName="+aChildName);
        List aList = aParent.getChildren(aChildName);
        // System.out.println("--------- createVector  aList="+aList);
        for (int i = 0; i < aList.size(); i++) {
            Element aChild = (Element) aList.get(i);
            aRet.addElement(aChild.getAttributeValue("val"));
        }
        return aRet;
    }

    protected static Vector createBindingVector(
        Element aElement,
        String aParentName,
        String aChildName,
        boolean parentBindings) {
        Element aParent = aElement.getChild(aParentName);
        Vector aRet = new Vector();
        // System.out.println("--------- createVector  aParentName="+aParentName+" aChildName="+aChildName);
        Iterator it = aParent.getChildren().iterator();
        while (it.hasNext()) {
            Element aChild = (Element) it.next();
            String name = aChild.getAttributeValue("name");
            String uuri = aChild.getAttributeValue("uuri");
            if (parentBindings) {
                aRet.add(new ObjectNode.ParentBinding(name, uuri));
            } else {
                aRet.add(new ObjectNode.Binding(name, uuri));
            }
        }
        return aRet;
    }

    protected static String encodeRevisionNumber(NodeRevisionNumber aRevisionNumber) {
        return aRevisionNumber.getMajor() + "." + aRevisionNumber.getMinor();
    }

    protected static Object createObject(String aNomClasse, Class aTypes[], Object aArgs[])
        throws UnknownObjectClassException {
        Class aClasse = null;
        try {
            // First, load the object's class
            aClasse = Class.forName(aNomClasse);
            Constructor aConstructor = aClasse.getConstructor(aTypes);
            if (aConstructor == null)
                aConstructor = aClasse.getSuperclass().getConstructor(aTypes);
            return aConstructor.newInstance(aArgs);

        } catch (Exception e) {
            throw new UnknownObjectClassException(aNomClasse);
        }
    }

    protected static NodeRevisionNumber decodeRevisionNumber(Element aElement) {
        Element aElementRevision = aElement.getChild("revision");
        return new NodeRevisionNumber(
            Integer.parseInt(aElementRevision.getAttributeValue("major")),
            Integer.parseInt(aElementRevision.getAttributeValue("minor")));
    }

    protected static NodeRevisionNumber decodeRevisionNumber(String aStr) {
        return (aStr == null ? null : new NodeRevisionNumber(aStr));
    }

    protected static NodeProperty decodeNodeProperty(Element aElement, String aUri) {
        String aName = aElement.getAttributeValue("name");
        String aValue = aElement.getAttributeValue("value");
        String aNamespace = aElement.getAttributeValue("namespace");
        String aType = aElement.getAttributeValue("type");
        boolean aProtected = new Boolean(aElement.getAttributeValue("protected")).booleanValue();

        NodeProperty property = new NodeProperty(aName, aValue, aNamespace, aType, aProtected);
        
        Element aPermisionsElement = aElement.getChild("permissions");
        if (aPermisionsElement != null) {
            List aList = aPermisionsElement.getChildren();
            for (int i = 0; i < aList.size(); i++) {
                Element aChild = (Element) aList.get(i);
                property.addPermission(decodePermission(aChild, aUri));
            }
        }
        
        return property;
    }

    /**
     * Creates an XML descriptor resource.
     * 
     * @param uri uri of the resource
     * @param txId identifier for the transaction in which the descriptor is to be managed
     * @param characterEncoding charcter enconding used to store this descriptor in XML
     * @throws ServiceAccessException if anything goes wrong at system level
     */
    public AbstractXMLResourceDescriptor(
        Uri uri,
        Object txId,
        String characterEncoding)
        throws ServiceAccessException {

        outputFormat = Format.getCompactFormat();
        outputFormat.setEncoding(characterEncoding);

        dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss z");

        this.txId = txId;

        if (uri == null) {
            throw new ServiceAccessException(null, "Trying to initialize XMLResourceDescriptor with null URI");
        }
        this.uri = uri.toString();
    }

    // -------------- PART TAKE OVER FROM AbstractUriProperties START -------------- 

    /**
     * Retrive an object from the Descriptors Store.
     *
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception ObjectNotFoundException The object to retrieve was not found
     */
    public ObjectNode retrieveObject() throws ServiceAccessException, ObjectNotFoundException {
        if (object == null) {
            throw new ObjectNotFoundException(uri);
        }
        return object.cloneObject();
    }

    /**
    * Store an object in the Descriptors Store.
    *
    * @param object Object to update
    * @exception ServiceAccessException Error accessing the Descriptors Store
    * @exception ObjectNotFoundException The object to update was not found
    */
    public void storeObject(ObjectNode aObject) throws ServiceAccessException, ObjectNotFoundException {
        object = aObject.cloneObject();
    }

    /**
    * Remove an object from the Descriptors Store.
    *
    * @param object Object to remove
    * @exception ServiceAccessException Error accessing the Descriptors Store
    * @exception ObjectNotFoundException The object to remove was not found
    */
    public void removeObject(ObjectNode aObject) throws ServiceAccessException, ObjectNotFoundException {
        object = null;
    }

    /**
    * Store an object permissions in the Descriptors Store.
    *
    * @param permission Permission we want to create
    * @exception ServiceAccessException Error accessing the Descriptors Store
    */
    public void grantPermission(NodePermission permission) throws ObjectNotFoundException, ServiceAccessException {
        if (permissions == null)
            permissions = new Vector();
        permissions.addElement(permission.cloneObject());
    }

    /**
    * Store an object permissions in the Descriptors Store.
    *
    * @param permission Permission we want to create
    * @exception ServiceAccessException Error accessing the Descriptors Store
    */
    public void revokePermission(NodePermission permission) throws ObjectNotFoundException, ServiceAccessException {
        if (permissions != null)
            permissions.removeElement(permission);
    }

    /**
    * Revoke all the permissions on the object .
    *
    * @param permission Permission we want to create
    * @exception ServiceAccessException Error accessing the Descriptors Store
    */
    public void revokePermissions() throws ObjectNotFoundException, ServiceAccessException {
        if (permissions != null)
            permissions.removeAllElements();
    }

    /**
    * Store an object permissions in the Descriptors Store.
    *
    * @param permission Permission we want to create
    * @exception ServiceAccessException Error accessing the Descriptors Store
    */
    public Enumeration enumeratePermissions() throws ServiceAccessException {
        if (permissions == null)
            permissions = new Vector();
        return permissions.elements();
    }

    /**
    * Puts a lock on a subject.
    *
    * @param lock Lock token
    * @exception ServiceAccessException Service access error
    */
    public void putLock(NodeLock lock) throws ObjectNotFoundException, ServiceAccessException {
        if (locks == null)
            locks = new Vector();
        locks.addElement(lock.cloneObject());
    }

    /**
    * Renews a lock.
    *
    * @param lock Token to renew
    * @exception ServiceAccessException Service access error
    * @exception LockTokenNotFoundException Lock token was not found
    */
    public void renewLock(NodeLock lock) throws LockTokenNotFoundException, ObjectNotFoundException, ServiceAccessException {
        if (locks == null)
            locks = new Vector();
        boolean wasPresent = locks.removeElement(lock);
        if (!wasPresent) {
            throw new LockTokenNotFoundException(lock);
        }
        locks.addElement(lock.cloneObject());
    }

    /**
    * Removes (cancels) a lock.
    *
    * @param lock Token to remove
    * @exception ServiceAccessException Service access error
    * @exception LockTokenNotFoundException Lock token was not found
    */
    public void removeLock(NodeLock lock) throws LockTokenNotFoundException, ObjectNotFoundException, ServiceAccessException {

        if (locks == null) {
            throw new LockTokenNotFoundException(lock);
        }
        boolean wasPresent = locks.removeElement(lock);
        if (!wasPresent) {
            throw new LockTokenNotFoundException(lock);
        }
    }

    /**
    * Returns the list of locks put on a subject.
    *
    * @param subject Subject
    * @return Enumeration List of locks which have been put on the subject
    * @exception ServiceAccessException Service access error
    */
    public Enumeration enumerateLocks() throws ServiceAccessException {
        if (locks == null)
            locks = new Vector();
        return locks.elements();
    }

    /**
    * Retrieve a revision descriptors.
    *
    * @exception ServiceAccessException Service access error
    * @exception RevisionDescriptorNotFoundException Revision descriptor
    * was not found
    */
    public NodeRevisionDescriptors retrieveRevisionDescriptors()
        throws ServiceAccessException, RevisionDescriptorNotFoundException {
        if (revisionDescriptors == null) {
            throw new RevisionDescriptorNotFoundException(uri.toString());
        }
        return revisionDescriptors.cloneObject();
    }

    /**
    * Create new revision descriptors.
    *
    * @param revisionDescriptors Node revision descriptors
    * @exception ServiceAccessException Service access error
    */
    public void createRevisionDescriptors(NodeRevisionDescriptors aRevisionDescriptors)
        throws ObjectNotFoundException, ServiceAccessException {
        revisionDescriptors = aRevisionDescriptors.cloneObject();
    }

    /**
    * Update revision descriptors.
    *
    * @param revisionDescriptors Node revision descriptors
    * @exception ServiceAccessException Service access error
    * @exception RevisionDescriptorNotFoundException Revision descriptor
    * was not found
    */
    public void storeRevisionDescriptors(NodeRevisionDescriptors aRevisionDescriptors)
        throws RevisionDescriptorNotFoundException, ObjectNotFoundException, ServiceAccessException {
        if (!revisionDescriptors.getUri().equals(uri.toString())) {
            throw new RevisionDescriptorNotFoundException(uri.toString());
        }
        revisionDescriptors = aRevisionDescriptors.cloneObject();
    }

    /**
    * Remove revision descriptors.
    *
    * @exception ServiceAccessException Service access error
    */
    public void removeRevisionDescriptors() throws ObjectNotFoundException, ServiceAccessException {
        revisionDescriptors = null;
    }

    /**
    * Retrieve revision descriptor.
    *
    * @param revisionNumber Node revision number
    */
    public NodeRevisionDescriptor retrieveRevisionDescriptor(NodeRevisionNumber revisionNumber)
        throws ServiceAccessException, RevisionDescriptorNotFoundException {
        Object result = null;

        if (descriptor != null && revisionNumber != null)
            result = descriptor.get(revisionNumber.toString());

        if (result == null) {
            throw new RevisionDescriptorNotFoundException(uri.toString());
        }
        return ((NodeRevisionDescriptor) result).cloneObject();
    }

    /**
    * Create new revision descriptor.
    *
    * @param revisionDescriptor Node revision descriptor
    * @exception ServiceAccessException Service access error
    */
    public void createRevisionDescriptor(NodeRevisionDescriptor aRevisionDescriptor)
        throws ObjectNotFoundException, ServiceAccessException {
        if (descriptor == null)
            descriptor = new Hashtable();

        descriptor.put(aRevisionDescriptor.getRevisionNumber().toString(), aRevisionDescriptor.cloneObject());
    }

    /**
    * Update revision descriptor.
    *
    * @param revisionDescriptors Node revision descriptor
    * @exception ServiceAccessException Service access error
    * @exception RevisionDescriptorNotFoundException Revision descriptor
    * was not found
    */
    public void storeRevisionDescriptor(NodeRevisionDescriptor aRevisionDescriptor)
        throws RevisionDescriptorNotFoundException, ObjectNotFoundException, ServiceAccessException {
        String key = aRevisionDescriptor.getRevisionNumber().toString();
        if (descriptor == null || !descriptor.containsKey(key)) {
            throw new RevisionDescriptorNotFoundException(uri.toString());
        }
        descriptor.put(key, aRevisionDescriptor.cloneObject());
    }

    /**
     * Remove revision descriptor.
     *
     * @param revisionNumber Revision number
     * @exception ServiceAccessException Service access error
     */
    public void removeRevisionDescriptor(NodeRevisionNumber number) throws ObjectNotFoundException, ServiceAccessException {
        if (descriptor == null)
            return;

        descriptor.remove(number.toString());
    }

    // -------------- PART TAKE OVER FROM AbstractUriProperties END -------------- 

    public void registerForSaving() {
        registeredForSaving = true;
    }

    public boolean isRegisteredForSaving() {
        return registeredForSaving;
    }

    /**
     * Stores this descriptor to the resource manager.
     * 
     * @throws ServiceAccessException if anything goes wrong at system level
     * @throws ObjectNotFoundException if the descriptor has not been created before 
     */
    public abstract void save() throws ServiceAccessException, ObjectNotFoundException;

    /**
     * Loads this descriptor from the resource manager.
     * 
     * @throws ServiceAccessException if anything goes wrong at system level
     * @throws ObjectNotFoundException if the descriptor does not exist 
     */
    public abstract void load() throws ServiceAccessException, ObjectNotFoundException;

    /**
     * Creates this descriptor in the resource manager.
     * 
     * @throws ServiceAccessException if anything goes wrong at system level
     * @throws ObjectAlreadyExistsException if the descriptor already exists 
     */
    public abstract void create() throws ServiceAccessException, ObjectAlreadyExistsException;

    /**
     * Deletes this descriptor from the resource manager.
     * 
     * @throws ServiceAccessException if anything goes wrong at system level
     * @throws ObjectNotFoundException if the descriptor does not exist 
     */
    public abstract void delete() throws ServiceAccessException, ObjectNotFoundException;

    /**
     * Gets the URI of this descriptor.
     * 
     * @return the URI
     */
    public String getUri() {
        return uri;
    }

    /**
     * Gets the transaction this descriptor lives in.
     * 
     * @return the transaction identifier
     */
    public Object getTxId() {
        return txId;
    }

    /**
     * Checks if the specified object is a descriptor with the same URI in the same transaction.
     * 
     * 
     * @param o object to compare this descriptor to
     * @return <code>true</code> if object is equal as described above 
     */
    public boolean equals(Object o) {
        return (
            this == o
                || (o != null
                    && o instanceof XMLResourceDescriptor
                    && ((XMLResourceDescriptor) o).uri.equals(uri)
                    && ((XMLResourceDescriptor) o).txId.equals(txId)));
    }

    public String toString() {
        return txId + ": " + uri;
    }

    protected void save(OutputStream os) throws ServiceAccessException, IOException {
        Element aRoot = encode();
        Document aDocument = new Document(aRoot);

        XMLOutputter aOutputter = new XMLOutputter(outputFormat);
        aOutputter.output(aDocument, os);
        os.flush();
    }

    protected void load(InputStream is) throws ServiceAccessException, JDOMException, IOException {
        SAXBuilder aBuilder = new SAXBuilder();
        Document aDocument = aBuilder.build(is);
        decode(aDocument.getRootElement());
    }

    protected void init() throws ServiceAccessException {
        // need to set this null, as AbstractUriProperties.retrieveObject relies on it
        object = null;
        permissions = new Vector();
        locks = new Vector();
        revisionDescriptors =
            new NodeRevisionDescriptors(uri, null, new Hashtable(), new Hashtable(), new Hashtable(), false);

        descriptor = new Hashtable();
    }

    protected Element encode() throws ServiceAccessException {
        Element aRoot = new Element("data");
        aRoot.addContent(encodeObject());
        aRoot.addContent(encodePermissions());
        aRoot.addContent(encodeLocks());
        aRoot.addContent(encodeRevisionDescriptors());
        aRoot.addContent(encodeRevisionDescriptor());
        return aRoot;
    }

    protected Element encodeObject() {
        Element aElementObjectNode = new Element("objectnode");
        if (object != null) {
            aElementObjectNode.setAttribute("classname", object.getClass().getName());
            aElementObjectNode.setAttribute("uri", object.getUri());
            if (object instanceof LinkNode) {
                aElementObjectNode.setAttribute("linkTo", ((LinkNode) object).getLinkedUri());
            }
            aElementObjectNode.addContent(createBindings("children", "child", object.enumerateBindings()));
            aElementObjectNode.addContent(createBindings("parents", "parent", object.enumerateParentBindings()));
            aElementObjectNode.addContent(createElements("links", "link", object.enumerateLinks()));
        } else {
            // for null locks
            aElementObjectNode.setAttribute("classname", "null");
            aElementObjectNode.setAttribute("uri", uri.toString());
        }
        return aElementObjectNode;
    }

    protected Element encodePermissions() {
        Element aPermissions = new Element("permissions");
        if (permissions == null)
            return aPermissions;

        for (int aSize = permissions.size(), i = 0; i < aSize; i++) {
            NodePermission aPermission = (NodePermission) permissions.elementAt(i);
            aPermissions.addContent(encodeNodePermission(aPermission));
        }
        return aPermissions;
    }

    protected Element encodeLocks() {
        Element aElementLocks = new Element("locks");
        if (locks == null)
            return aElementLocks;

        for (int aSize = locks.size(), i = 0; i < aSize; i++) {
            NodeLock aLock = (NodeLock) locks.elementAt(i);
            Element aElementLock = new Element("lock");
            aElementLock.setAttribute("subjectUri", aLock.getSubjectUri());
            aElementLock.setAttribute("typeUri", aLock.getTypeUri());
            aElementLock.setAttribute("date", dateFormat.format(aLock.getExpirationDate()));
            aElementLock.setAttribute("inheritance", booleanToString(aLock.isInheritable()));
            aElementLock.setAttribute("exclusive", booleanToString(aLock.isExclusive()));
            aElementLock.setAttribute("lockId", aLock.getLockId());
            aElementLock.setAttribute("owner", 
                  aLock.getOwnerInfo() == null ? "" : aLock.getOwnerInfo());
            aElementLocks.addContent(aElementLock);
        }
        return aElementLocks;
    }

    protected Element encodeRevisionDescriptors() {

        Element aRevisionsHistory = new Element("revisionsHistory");
        if (revisionDescriptors == null)
            return aRevisionsHistory;

        aRevisionsHistory.setAttribute(
            "initialRevision",
            encodeRevisionNumber(revisionDescriptors.getInitialRevision()));
        aRevisionsHistory.setAttribute("useVersioning", booleanToString(revisionDescriptors.isVersioned()));

        // System.out.println("---------- encodeRevisionDescriptors getLatestRevision="+
        //  revisionDescriptors.getLatestRevision());

        Element aBranchesElement = new Element("branches");
        Enumeration aBranches = revisionDescriptors.enumerateBranchNames();
        while (aBranches.hasMoreElements()) {
            String aBranchName = (String) aBranches.nextElement();
            Element aElementBranch = new Element("branch");
            aElementBranch.setAttribute("name", aBranchName);
            NodeRevisionNumber aRevisionNumber = revisionDescriptors.getLatestRevision(aBranchName);
            aElementBranch.setAttribute("lastestRevision", encodeRevisionNumber(aRevisionNumber));
            aBranchesElement.addContent(aElementBranch);
        }
        aRevisionsHistory.addContent(aBranchesElement);

        Element aRevisionsElement = new Element("revisions");
        Enumeration aRevisions = revisionDescriptors.enumerateRevisionNumbers();
        while (aRevisions.hasMoreElements()) {
            NodeRevisionNumber aRevisionNumber = (NodeRevisionNumber) aRevisions.nextElement();
            Element aRevisionElement = new Element("branch");
            aRevisionElement.setAttribute("start", encodeRevisionNumber(aRevisionNumber));

            Enumeration aSuccessors = revisionDescriptors.getSuccessors(aRevisionNumber);
            while (aSuccessors.hasMoreElements()) {
                NodeRevisionNumber aSuccessorRevisionNumber = (NodeRevisionNumber) aSuccessors.nextElement();
                Element aSuccessorRevisionElement = new Element("revision");
                aSuccessorRevisionElement.setAttribute("number", encodeRevisionNumber(aSuccessorRevisionNumber));
                aRevisionElement.addContent(aSuccessorRevisionElement);
            }
            aRevisionsElement.addContent(aRevisionElement);
        }
        aRevisionsHistory.addContent(aRevisionsElement);

        return aRevisionsHistory;
    }

    protected Element encodeRevisionDescriptor() {
        Element aRet = new Element("descriptor");
        if (descriptor == null)
            return aRet;

        for (Enumeration aEnum = descriptor.elements(); aEnum.hasMoreElements();) {
            NodeRevisionDescriptor aRevisionDescriptor = (NodeRevisionDescriptor) aEnum.nextElement();
            aRet.addContent(encodeRevisionDescriptor(aRevisionDescriptor));
        }
        return aRet;
    }

    protected void decode(Element aRoot) throws ServiceAccessException {
        decodeObject(aRoot);
        decodePermissions(aRoot);
        decodeLocks(aRoot);
        decodeRevisionDescriptors(aRoot);
        decodeRevisionDescriptor(aRoot);
    }

    protected void decodeObject(Element aElement) throws ServiceAccessException {
        Element aElementObjectNode = aElement.getChild("objectnode");
        String aClasseName = aElementObjectNode.getAttributeValue("classname");
        if (!"null".equals(aClasseName)) {
            try {
                String aUri = aElementObjectNode.getAttributeValue("uri");
                Vector aChilds = createBindingVector(aElementObjectNode, "children", "child", false);
                Vector aParents = createBindingVector(aElementObjectNode, "parents", "parent", true);
                Vector aLinks = createVector(aElementObjectNode, "links", "link");
                // System.out.println("--------- decodeObject  aChilds="+aChilds);
                // System.out.println("--------- decodeObject  aLinks="+aLinks);
                Class aTypes[] = null;
                Object aArgs[] = null;

                if (aClasseName.equals(LinkNode.class.getName())) {
                    String aLinkTo = aElementObjectNode.getAttributeValue("linkTo");
                    aTypes = new Class[] { String.class, Vector.class, Vector.class, String.class };
                    aArgs = new Object[] { aUri, aChilds, aLinks, aLinkTo };
                } else {
                    aTypes = new Class[] { String.class, Vector.class, Vector.class, Vector.class };
                    aArgs = new Object[] { aUri, aChilds, aParents, aLinks };
                }
                object = (ObjectNode) createObject(aClasseName, aTypes, aArgs);
                object.setUri(object.getUuri());
            } catch (Exception e) {
                e.printStackTrace();
                throw new ServiceAccessException(null, e);
            }
            uri = object.getUri();
        } else {
            object = null;
            uri = aElementObjectNode.getAttributeValue("uri");
        }
    }

    protected void decodePermissions(Element aElement) {
        permissions = new Vector();
        Element aPermissions = aElement.getChild("permissions");
        List aList = aPermissions.getChildren();
        for (int i = 0; i < aList.size(); i++) {
            Element aChild = (Element) aList.get(i);
            permissions.addElement(decodePermission(aChild, uri));
        }
    }

    protected void decodeLocks(Element aElement) throws ServiceAccessException {
        try {
            locks = new Vector();
            Element aElementLocks = aElement.getChild("locks");
            List aList = aElementLocks.getChildren();

            for (int i = 0; i < aList.size(); i++) {
                Element aChild = (Element) aList.get(i);
                String aSubject = aChild.getAttributeValue("subjectUri");
                String aType = aChild.getAttributeValue("typeUri");
                Date aDateExpiration = dateFormat.parse(aChild.getAttributeValue("date"));
                boolean aInheritable = new Boolean(aChild.getAttributeValue("inheritance")).booleanValue();
                boolean aNegative = new Boolean(aChild.getAttributeValue("exclusive")).booleanValue();
                String aLockId = aChild.getAttributeValue("lockId");
                String ownerInfo = aChild.getAttributeValue("owner");

                locks.addElement(
                    new NodeLock(aLockId, uri, aSubject, aType, aDateExpiration, aInheritable, aNegative, ownerInfo));
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceAccessException(null, e);
        }

    }

    protected void decodeRevisionDescriptors(Element aElement) {
        Element aRevisionsHistory = aElement.getChild("revisionsHistory");

        NodeRevisionNumber aInitialRevision =
            decodeRevisionNumber(aRevisionsHistory.getAttributeValue("initialRevision"));
        boolean aUseVersionning = new Boolean(aRevisionsHistory.getAttributeValue("useVersioning")).booleanValue();

        Element aBranchesElement = aRevisionsHistory.getChild("branches");
        if (aBranchesElement == null) {
            revisionDescriptors =
                new NodeRevisionDescriptors(
                    uri,
                    aInitialRevision,
                    new Hashtable(),
                    new Hashtable(),
                    new Hashtable(),
                    aUseVersionning);
            return;
        }

        List aList = aBranchesElement.getChildren();
        Hashtable aLastestRevisions = new Hashtable();
        for (int i = 0; i < aList.size(); i++) {
            Element aChild = (Element) aList.get(i);
            String aName = aChild.getAttributeValue("name");
            NodeRevisionNumber aRevisionNumber = decodeRevisionNumber(aChild.getAttributeValue("lastestRevision"));
            aLastestRevisions.put(aName, aRevisionNumber);
        }
        Hashtable aBranches = new Hashtable();
        Element aRevisionsElement = aRevisionsHistory.getChild("revisions");
        aList = aRevisionsElement.getChildren();
        for (int i = 0; i < aList.size(); i++) {
            Element aChild = (Element) aList.get(i);
            NodeRevisionNumber aStartNumber = decodeRevisionNumber(aChild.getAttributeValue("start"));
            List aSuccessors = aChild.getChildren();
            Vector aSuccessorsNumbers = new Vector();
            for (int k = 0; k < aSuccessors.size(); k++) {
                Element aSuccessor = (Element) aSuccessors.get(k);
                NodeRevisionNumber aRevisionNumber = decodeRevisionNumber(aSuccessor.getAttributeValue("number"));
                aSuccessorsNumbers.addElement(aRevisionNumber);
            }
            aBranches.put(aStartNumber, aSuccessorsNumbers);
        }
        revisionDescriptors =
            new NodeRevisionDescriptors(
                uri,
                aInitialRevision,
                new Hashtable(),
                aLastestRevisions,
                aBranches,
                aUseVersionning);
    }

    protected void decodeRevisionDescriptor(Element aParent) {
        descriptor = new Hashtable();

        Element aElement = aParent.getChild("descriptor");
        if (aElement == null)
            return;

        List aList = aElement.getChildren();

        for (int i = 0; i < aList.size(); i++) {
            Element aChild = (Element) aList.get(i);
            String aBranchName = aChild.getAttributeValue("branchName");
            NodeRevisionNumber aRevisionNumber = decodeRevisionNumber(aChild.getAttributeValue("number"));

            Vector aLabels = new Vector();
            Element aLabelsElement = (Element) aChild.getChild("labels");
            List aLabelList = aLabelsElement.getChildren();
            for (int k = 0; k < aLabelList.size(); k++) {
                Element aLabel = (Element) aLabelList.get(k);
                aLabels.addElement(aLabel.getAttributeValue("val"));
            }

            NodeRevisionDescriptor aNode =
                new NodeRevisionDescriptor(aRevisionNumber, aBranchName, aLabels, 
                        Collections.EMPTY_LIST);

            Element aPropertiesElement = (Element) aChild.getChild("properties");
            List aPropertiesList = aPropertiesElement.getChildren();
            for (int k = 0; k < aPropertiesList.size(); k++) {
                Element aProperty = (Element) aPropertiesList.get(k);
                aNode.setProperty(decodeNodeProperty(aProperty, uri));
            }
            descriptor.put(aRevisionNumber.toString(), aNode);
        }
    }

}
