/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.version;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.node.MLPropertyInterceptor;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyScope;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.version.VersionServicePolicies.CalculateVersionLabelPolicy;
import org.alfresco.repo.version.common.AbstractVersionServiceImpl;
import org.alfresco.repo.version.common.VersionHistoryImpl;
import org.alfresco.repo.version.common.VersionImpl;
import org.alfresco.repo.version.common.VersionUtil;
import org.alfresco.repo.version.traitextender.VersionServiceExtension;
import org.alfresco.repo.version.traitextender.VersionServiceTrait;
import org.alfresco.service.cmr.dictionary.AspectDefinition;
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
import org.alfresco.service.cmr.dictionary.ClassDefinition;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.repository.AspectMissingException;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.version.ReservedVersionNameException;
import org.alfresco.service.cmr.version.Version;
import org.alfresco.service.cmr.version.VersionHistory;
import org.alfresco.service.cmr.version.VersionService;
import org.alfresco.service.cmr.version.VersionServiceException;
import org.alfresco.service.cmr.version.VersionType;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.traitextender.Extend;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.surf.util.ParameterCheck;

/**
 * Version1 Service - implements lightWeightVersionStore
 *
 * @author Roy Wetheral
 * 
 * NOTE: deprecated since 3.1 (migrate and use Version2 Service)
 */
@SuppressWarnings("deprecation")
public abstract class VersionServiceImpl extends AbstractVersionServiceImpl implements VersionService, VersionModel
{
    private static Log logger = LogFactory.getLog(VersionServiceImpl.class);
    
    /**
     * Error message I18N id's
     */
    protected static final String MSGID_ERR_NOT_FOUND = "version_service.err_not_found";
    protected static final String MSGID_ERR_NO_BRANCHES = "version_service.err_unsupported";
    protected static final String MSGID_ERR_RESTORE_EXISTS = "version_service.err_restore_exists";
    protected static final String MSGID_ERR_ONE_PRECEEDING = "version_service.err_one_preceeding";
    protected static final String MSGID_ERR_RESTORE_NO_VERSION = "version_service.err_restore_no_version";
    protected static final String MSGID_ERR_REVERT_MISMATCH = "version_service.err_revert_mismatch";

    /**
     * The db node service, used as the version store implementation
     */
    protected NodeService dbNodeService;

    /**
     * Policy behaviour filter
     */
    protected BehaviourFilter policyBehaviourFilter;

    /**
     * The repository searcher
     */
    protected SearchService searcher; // unused

    protected Comparator<Version> versionComparatorDesc;

    /**
     * Sets the db node service, used as the version store implementation
     *
     * @param nodeService  the node service
     */
    public void setDbNodeService(NodeService nodeService)
    {
        this.dbNodeService = nodeService;
    }

    /**
     * @param searcher  the searcher
     */
    public void setSearcher(SearchService searcher)
    {
        this.searcher = searcher;
    }
    
    /**
     * Set the policy behaviour filter
     *
     * @param policyBehaviourFilter     the policy behaviour filter
     */
    public void setPolicyBehaviourFilter(BehaviourFilter policyBehaviourFilter)
    {
        this.policyBehaviourFilter = policyBehaviourFilter;
    }

    /**
     * Sets an optional comparator to sort a versions in descending order (eg. 2.1, 2.0, 1.1, 1.0).
     * Really only needed in a 4.1.3 system (or above) that has been upgraded from an earlier system
     * that used NON ordered sequence numbers in a cluster. Not something we really support but was
     * the subject of MNT-226.
     * @param versionComparatorClass the name of a comparator. For example
     *        "org.alfresco.repo.version.common.VersionLabelComparator".
     */
    @SuppressWarnings("unchecked")
    public void setVersionComparatorClass(String versionComparatorClass)
    {
        if (versionComparatorClass != null && versionComparatorClass.trim().length() != 0)
        {
            try
            {
                versionComparatorDesc = (Comparator<Version>) getClass().getClassLoader().
                        loadClass(versionComparatorClass.trim()).newInstance();
            }
            catch (Exception e)
            {
                throw new AlfrescoRuntimeException(
                        "Failed to create a Comparator<Version> using the class name "+
                        versionComparatorClass, e);
            }
        }
    }
    
    /**
     * Register version label policy for the specified type
     * 
     * @param typeQName QName
     * @param policy CalculateVersionLabelPolicy
     */
    @Extend(extensionAPI=VersionServiceExtension.class,traitAPI=VersionServiceTrait.class)
    public void registerVersionLabelPolicy(QName typeQName, CalculateVersionLabelPolicy policy)
    {
        // Register the serial version label behaviour
        this.policyComponent.bindClassBehaviour(
                QName.createQName(NamespaceService.ALFRESCO_URI, "calculateVersionLabel"),
                typeQName,
                new JavaBehaviour(policy, "calculateVersionLabel"));
    }
    
    /**
     * Initialise method
     */
    @Override
    public void initialise()
    {
        super.initialise();     
    }
    
    /**
     * Gets the reference to the version store
     *
     * @return  reference to the version store
     */
    @Override
    public StoreRef getVersionStoreReference()
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("Run as user " + AuthenticationUtil.getRunAsUser());
            logger.debug("Fully authenticated " + AuthenticationUtil.getFullyAuthenticatedUser());
        }
        
        return new StoreRef(
                StoreRef.PROTOCOL_WORKSPACE,
                VersionModel.STORE_ID);
    }

    /**
     * @see VersionService#createVersion(NodeRef, Map)
     */
    public Version createVersion(
            NodeRef nodeRef,
            Map<String, Serializable> versionProperties)
            throws ReservedVersionNameException, AspectMissingException
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("Run as user " + AuthenticationUtil.getRunAsUser());
            logger.debug("Fully authenticated " + AuthenticationUtil.getFullyAuthenticatedUser());
        }
        
        long startTime = System.currentTimeMillis();
        
        int versionNumber = 0; // deprecated (unused)
        
        // Create the version
        Version version = createVersion(nodeRef, versionProperties, versionNumber);
        
        if (logger.isDebugEnabled())
        {
            logger.debug("created version (" + VersionUtil.convertNodeRef(version.getFrozenStateNodeRef()) + ") in " + (System.currentTimeMillis()-startTime) + " ms");
        }
        
        return version;
    }

    /**
     * The version's are created from the children upwards with the parent being created first.  This will
     * ensure that the child version references in the version node will point to the version history nodes
     * for the (possibly) newly created version histories.
     */
    @Extend(extensionAPI=VersionServiceExtension.class,traitAPI=VersionServiceTrait.class)
    public Collection<Version> createVersion(
            NodeRef nodeRef,
            Map<String, Serializable> versionProperties,
            boolean versionChildren)
            throws ReservedVersionNameException, AspectMissingException
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("Run as user " + AuthenticationUtil.getRunAsUser());
            logger.debug("Fully authenticated " + AuthenticationUtil.getFullyAuthenticatedUser());
        }
        
        long startTime = System.currentTimeMillis();
        
        int versionNumber = 0; // deprecated (unused)
        
        // Create the versions
        Collection<Version> versions = createVersion(nodeRef, versionProperties, versionChildren, versionNumber);
        
        if (logger.isDebugEnabled())
        {
            Version[] versionsArray = versions.toArray(new Version[0]);
            Version version = versionsArray[versionsArray.length -1]; // last item is the new parent version
            logger.debug("created version (" + VersionUtil.convertNodeRef(version.getFrozenStateNodeRef()) + ") in "+ (System.currentTimeMillis()-startTime) +" ms "+(versionChildren ? "(with " + (versions.size() - 1) + " children)" : ""));
        }
        
        return versions;
    }

    /**
     * Helper method used to create the version when the versionChildren flag is provided.  This method
     * ensures that all the children (if the falg is set to true) are created with the same version
     * number, this ensuring that the version stripe is correct.
     *
     * @param nodeRef                           the parent node reference
     * @param versionProperties                 the version properties
     * @param versionChildren                   indicates whether to version the children of the parent
     *                                          node
     * @param versionNumber                     the version number

     * @return                                  a collection of the created versions
     * @throws ReservedVersionNameException     thrown if there is a reserved version property name clash
     * @throws AspectMissingException    thrown if the version aspect is missing from a node
     */
    private Collection<Version> createVersion(
            NodeRef nodeRef,
            Map<String, Serializable> versionProperties,
            boolean versionChildren,
            int versionNumber)
            throws ReservedVersionNameException, AspectMissingException
    {

        Collection<Version> result = new ArrayList<Version>();

        if (versionChildren == true)
        {
            // Get the children of the node
            Collection<ChildAssociationRef> children = this.dbNodeService.getChildAssocs(nodeRef);
            for (ChildAssociationRef childAssoc : children)
            {
                // Recurse into this method to version all the children with the same version number
                Collection<Version> childVersions = createVersion(
                        childAssoc.getChildRef(),
                        versionProperties,
                        versionChildren,
                        versionNumber);
                result.addAll(childVersions);
            }
        }

        result.add(createVersion(nodeRef, versionProperties, versionNumber));

        return result;
    }

    /**
     * Note:  we can't control the order of the list, so if we have children and parents in the list and the
     * parents get versioned before the children and the children are not already versioned then the parents
     * child references will be pointing to the node ref, rather than the verison history.
     */
    public Collection<Version> createVersion(
            Collection<NodeRef> nodeRefs,
            Map<String, Serializable> versionProperties)
            throws ReservedVersionNameException, AspectMissingException
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("Run as user " + AuthenticationUtil.getRunAsUser());
            logger.debug("Fully authenticated " + AuthenticationUtil.getFullyAuthenticatedUser());
        }
        
        long startTime = System.currentTimeMillis();
        
        Collection<Version> result = new ArrayList<Version>(nodeRefs.size());
        
        int versionNumber = 0; // deprecated (unused)
        
        // Version each node in the list
        for (NodeRef nodeRef : nodeRefs)
        {
            result.add(createVersion(nodeRef, versionProperties, versionNumber));
        }

        if (logger.isDebugEnabled())
        {
            logger.debug("created version list (" + getVersionStoreReference() + ") in "+ (System.currentTimeMillis()-startTime) +" ms (with " + nodeRefs.size() + " nodes)");
        }
        
        return result;
    }

    /**
     * Creates a new version of the passed node assigning the version properties
     * accordingly.
     *
     * @param  nodeRef              a node reference
     * @param  origVersionProperties    the version properties
     * @param  versionNumber        the version number
     * @return                      the newly created version
     * @throws ReservedVersionNameException
     *                              thrown if there is a name clash in the version properties
     */
    protected Version createVersion(
            NodeRef nodeRef,
            Map<String, Serializable> origVersionProperties,
            int versionNumber)
            throws ReservedVersionNameException
    {
        long startTime = System.currentTimeMillis();

        // Copy the version properties (to prevent unexpected side effects to the caller)
        Map<String, Serializable> versionProperties = new HashMap<String, Serializable>();
        if (origVersionProperties != null)
        {
            versionProperties.putAll(origVersionProperties);
        }

        // If the version aspect is not there then add it
        if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == false)
        {
            this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, null);
        }

        // Call the policy behaviour
        invokeBeforeCreateVersion(nodeRef);

        // Check that the supplied additional version properties do not clash with the reserved ones
        VersionUtil.checkVersionPropertyNames(versionProperties.keySet());

        // Check the repository for the version history for this node
        NodeRef versionHistoryRef = getVersionHistoryNodeRef(nodeRef);
        NodeRef currentVersionRef = null;

        if (versionHistoryRef == null)
        {
            // Create the version history
            versionHistoryRef = createVersionHistory(nodeRef);
        }
        else
        {
            // Since we have an exisiting version history we should be able to lookup
            // the current version
            currentVersionRef = getCurrentVersionNodeRef(versionHistoryRef, nodeRef);

            if (currentVersionRef == null)
            {
                throw new VersionServiceException(MSGID_ERR_NOT_FOUND);
            }

            // Need to check that we are not about to create branch since this is not currently supported
            VersionHistory versionHistory = buildVersionHistory(versionHistoryRef, nodeRef);
            Version currentVersion = getVersion(currentVersionRef);
            if (versionHistory.getSuccessors(currentVersion).size() != 0)
            {
                throw new VersionServiceException(MSGID_ERR_NO_BRANCHES);
            }
        }

        // Create the node details
        QName classRef = this.nodeService.getType(nodeRef);
        PolicyScope nodeDetails = new PolicyScope(classRef);

        // Get the node details by calling the onVersionCreate policy behaviour
        invokeOnCreateVersion(nodeRef, versionProperties, nodeDetails);

        // Create the new version node (child of the version history)
        NodeRef newVersionRef = createNewVersion(
                nodeRef,
                versionHistoryRef,
                getStandardVersionProperties(versionProperties, nodeRef, currentVersionRef, versionNumber),
                versionProperties,
                nodeDetails);

        if (currentVersionRef == null)
        {
            // Set the new version to be the root version in the version history
            this.dbNodeService.createAssociation(
                    versionHistoryRef,
                    newVersionRef,
                    VersionServiceImpl.ASSOC_ROOT_VERSION);
        }
        else
        {
            // Relate the new version to the current version as its successor
            this.dbNodeService.createAssociation(
                    currentVersionRef,
                    newVersionRef,
                    VersionServiceImpl.ASSOC_SUCCESSOR);
        }

        // Create the version data object
        Version version = this.getVersion(newVersionRef);

        // Set the new version label on the versioned node
        this.nodeService.setProperty(
                nodeRef,
                ContentModel.PROP_VERSION_LABEL,
                version.getVersionLabel());

        // Freeze the version label property
        Map<QName, Serializable> versionLabelAsMap = new HashMap<QName, Serializable>(1);
        versionLabelAsMap.put(ContentModel.PROP_VERSION_LABEL, version.getVersionLabel());
        this.freezeProperties(newVersionRef, versionLabelAsMap);

        // Invoke the policy behaviour
        invokeAfterCreateVersion(nodeRef, version);

        if (logger.isTraceEnabled())
        {
            logger.trace("created Version (" + getVersionStoreReference() + ") " + nodeRef + " in " + (System.currentTimeMillis()-startTime) +" ms");
        }
        
        // Return the data object representing the newly created version
        return version;
    }

    /**
     * Creates a new version history node, applying the root version aspect is required
     *
     * @param nodeRef   the node ref
     * @return          the version history node reference
     */
    private NodeRef createVersionHistory(NodeRef nodeRef)
    {
        HashMap<QName, Serializable> props = new HashMap<QName, Serializable>();
        props.put(ContentModel.PROP_NAME, nodeRef.getId());
        props.put(PROP_QNAME_VERSIONED_NODE_ID, nodeRef.getId());

        // Create a new version history node
        ChildAssociationRef childAssocRef = this.dbNodeService.createNode(
                getRootNode(),
                CHILD_QNAME_VERSION_HISTORIES,
                QName.createQName(VersionModel.NAMESPACE_URI, nodeRef.getId()),
                TYPE_QNAME_VERSION_HISTORY,
                props);
        return childAssocRef.getChildRef();
    }

    /**
     * @see org.alfresco.service.cmr.version.VersionService#getVersionHistory(NodeRef)
     */
    public VersionHistory getVersionHistory(NodeRef nodeRef)
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("Run as user " + AuthenticationUtil.getRunAsUser());
            logger.debug("Fully authenticated " + AuthenticationUtil.getFullyAuthenticatedUser());
        }
        
        VersionHistory versionHistory = null;

        if (this.nodeService.exists(nodeRef) == true)
        {
            NodeRef versionHistoryRef = getVersionHistoryNodeRef(nodeRef);
            if (versionHistoryRef != null)
            {
                versionHistory = buildVersionHistory(versionHistoryRef, nodeRef);
            }
        }

        return versionHistory;
    }

    /**
     * @see VersionService#getCurrentVersion(NodeRef)
     */
    public Version getCurrentVersion(NodeRef nodeRef)
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("Run as user " + AuthenticationUtil.getRunAsUser());
            logger.debug("Fully authenticated " + AuthenticationUtil.getFullyAuthenticatedUser());
        }
        
        Version version = null;

        if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == true)
        {
            VersionHistory versionHistory = getVersionHistory(nodeRef);
            if (versionHistory != null)
            {
                String versionLabel = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL);
                version = versionHistory.getVersion(versionLabel);
            }
        }

        return version;
    }

    /**
     * Get a map containing the standard list of version properties populated.
     *
     * @param versionProperties     the version meta data properties
     * @param nodeRef               the node reference
     * @param preceedingNodeRef     the preceeding node reference
     * @param versionNumber         the version number
     * @return                      the standard version properties
     */
    private Map<QName, Serializable> getStandardVersionProperties(Map<String, Serializable> versionProperties, NodeRef nodeRef, NodeRef preceedingNodeRef, int versionNumber)
    {
        Map<QName, Serializable> result = new HashMap<QName, Serializable>(10);

        // deprecated (unused)
        //result.put(VersionModel.PROP_QNAME_VERSION_NUMBER, Integer.toString(versionNumber));

        // Set the versionable node id
        result.put(VersionModel.PROP_QNAME_FROZEN_NODE_ID, nodeRef.getId());

        // Set the versionable node store protocol
        result.put(VersionModel.PROP_QNAME_FROZEN_NODE_STORE_PROTOCOL, nodeRef.getStoreRef().getProtocol());

        // Set the versionable node store id
        result.put(VersionModel.PROP_QNAME_FROZEN_NODE_STORE_ID, nodeRef.getStoreRef().getIdentifier());

        // Store the current node type
        QName nodeType = this.nodeService.getType(nodeRef);
        result.put(VersionModel.PROP_QNAME_FROZEN_NODE_TYPE, nodeType);

        // Store the current aspects
        Set<QName> aspects = this.nodeService.getAspects(nodeRef);
        result.put(VersionModel.PROP_QNAME_FROZEN_ASPECTS, (Serializable)aspects);

        // Calculate the version label
        QName classRef = this.nodeService.getType(nodeRef);
        Version preceedingVersion = getVersion(preceedingNodeRef);
        String versionLabel = invokeCalculateVersionLabel(classRef, preceedingVersion, versionNumber, versionProperties);
        result.put(VersionModel.PROP_QNAME_VERSION_LABEL, versionLabel);

        return result;
    }

    /**
     * Creates a new version node, setting the properties both calculated and specified.
     *
     * @param versionableNodeRef  the reference to the node being versioned
     * @param versionHistoryRef   version history node reference
     * @param standardVersionProperties   version properties
     * @param versionProperties   version properties
     * @param nodeDetails          PolicyScope
     * @return                    the version node reference
     */
    private NodeRef createNewVersion(
            NodeRef versionableNodeRef,
            NodeRef versionHistoryRef,
            Map<QName, Serializable> standardVersionProperties,
            Map<String, Serializable> versionProperties,
            PolicyScope nodeDetails)
    {
        // Create the new version
        ChildAssociationRef childAssocRef = this.dbNodeService.createNode(
                versionHistoryRef,
                CHILD_QNAME_VERSIONS,
                CHILD_QNAME_VERSIONS,
                TYPE_QNAME_VERSION,
                standardVersionProperties);
        NodeRef versionNodeRef = childAssocRef.getChildRef();

        // Store the meta data
        storeVersionMetaData(versionNodeRef, versionProperties);

        // Freeze the various parts of the node
        freezeProperties(versionNodeRef, nodeDetails.getProperties());
        freezeChildAssociations(versionNodeRef, nodeDetails.getChildAssociations());
        freezeAssociations(versionNodeRef, nodeDetails.getAssociations());
        freezeAspects(nodeDetails, versionNodeRef, nodeDetails.getAspects());

        // Return the created node reference
        return versionNodeRef;
    }

    /**
     * Store the version meta data
     *
     * @param versionNodeRef        the version node reference
     * @param versionProperties     the version properties
     */
    private void storeVersionMetaData(NodeRef versionNodeRef, Map<String, Serializable> versionProperties)
    {
        for (Map.Entry<String, Serializable> entry : versionProperties.entrySet())
        {
            HashMap<QName, Serializable> properties = new HashMap<QName, Serializable>();

            properties.put(PROP_QNAME_META_DATA_NAME, entry.getKey());
            properties.put(PROP_QNAME_META_DATA_VALUE, entry.getValue());

            this.dbNodeService.createNode(
                    versionNodeRef,
                    CHILD_QNAME_VERSION_META_DATA,
                    CHILD_QNAME_VERSION_META_DATA,
                    TYPE_QNAME_VERSION_META_DATA_VALUE,
                    properties);
        }
    }
    
    protected Map<String, Serializable> getVersionMetaData(NodeRef versionNodeRef)
    {
        // Get the meta data
        List<ChildAssociationRef> metaData = this.dbNodeService.getChildAssocs(
                versionNodeRef,
                RegexQNamePattern.MATCH_ALL,
                CHILD_QNAME_VERSION_META_DATA);
        
        Map<String, Serializable> versionProperties = new HashMap<String, Serializable>(metaData.size());
        
        for (ChildAssociationRef ref : metaData)
        {
            NodeRef metaDataValue = (NodeRef)ref.getChildRef();
            String name = (String)this.dbNodeService.getProperty(metaDataValue, PROP_QNAME_META_DATA_NAME);
            Serializable value = this.dbNodeService.getProperty(metaDataValue, PROP_QNAME_META_DATA_VALUE);
            versionProperties.put(name, value);
        }
        
        return versionProperties;
    }

    /**
     * Freeze the aspects
     *
     * @param nodeDetails      the node details
     * @param versionNodeRef   the version node reference
     * @param aspects          the set of aspects
     */
    private void freezeAspects(PolicyScope nodeDetails, NodeRef versionNodeRef, Set<QName> aspects)
    {
        for (QName aspect : aspects)
        {
            // Freeze the details of the aspect
            freezeProperties(versionNodeRef, nodeDetails.getProperties(aspect));
            freezeChildAssociations(versionNodeRef, nodeDetails.getChildAssociations(aspect));
            freezeAssociations(versionNodeRef, nodeDetails.getAssociations(aspect));
        }
    }

    /**
     * Freeze associations
     *
     * @param versionNodeRef   the version node reference
     * @param associations     the list of associations
     */
    private void freezeAssociations(NodeRef versionNodeRef, List<AssociationRef> associations)
    {
        for (AssociationRef targetAssoc : associations)
        {
            HashMap<QName, Serializable> properties = new HashMap<QName, Serializable>();

            // Set the qname of the association
            properties.put(PROP_QNAME_ASSOC_TYPE_QNAME, targetAssoc.getTypeQName());

            // Set the reference property to point to the child node
            properties.put(ContentModel.PROP_REFERENCE, targetAssoc.getTargetRef());

            // Create child version reference
            this.dbNodeService.createNode(
                    versionNodeRef,
                    CHILD_QNAME_VERSIONED_ASSOCS,
                    CHILD_QNAME_VERSIONED_ASSOCS,
                    TYPE_QNAME_VERSIONED_ASSOC,
                    properties);
        }
    }

    /**
     * Freeze child associations
     *
     * @param versionNodeRef       the version node reference
     * @param childAssociations    the child associations
     */
    private void freezeChildAssociations(NodeRef versionNodeRef, List<ChildAssociationRef> childAssociations)
    {
        for (ChildAssociationRef childAssocRef : childAssociations)
        {
            HashMap<QName, Serializable> properties = new HashMap<QName, Serializable>();

            // Set the qname, isPrimary and nthSibling properties
            properties.put(PROP_QNAME_ASSOC_QNAME, childAssocRef.getQName());
            properties.put(PROP_QNAME_ASSOC_TYPE_QNAME, childAssocRef.getTypeQName());
            properties.put(PROP_QNAME_IS_PRIMARY, Boolean.valueOf(childAssocRef.isPrimary()));
            properties.put(PROP_QNAME_NTH_SIBLING, Integer.valueOf(childAssocRef.getNthSibling()));

            // Set the reference property to point to the child node
            properties.put(ContentModel.PROP_REFERENCE, childAssocRef.getChildRef());

            // Create child version reference
            this.dbNodeService.createNode(
                    versionNodeRef,
                    CHILD_QNAME_VERSIONED_CHILD_ASSOCS,
                    CHILD_QNAME_VERSIONED_CHILD_ASSOCS,
                    TYPE_QNAME_VERSIONED_CHILD_ASSOC,
                    properties);
        }
    }

    /**
     * Freeze properties
     *
     * @param versionNodeRef   the version node reference
     * @param properties       the properties
     */
    private void freezeProperties(NodeRef versionNodeRef, Map<QName, Serializable> properties)
    {
        // Copy the property values from the node onto the version node
        for (Map.Entry<QName, Serializable> entry : properties.entrySet())
        {
            // Get the property values
            HashMap<QName, Serializable> props = new HashMap<QName, Serializable>();
            props.put(PROP_QNAME_QNAME, entry.getKey());

            if (entry.getValue() instanceof Collection)
            {
                props.put(PROP_QNAME_MULTI_VALUE, entry.getValue());
                props.put(PROP_QNAME_IS_MULTI_VALUE, true);
            }
            else
            {
                props.put(PROP_QNAME_VALUE, entry.getValue());
                props.put(PROP_QNAME_IS_MULTI_VALUE, false);
            }

            // Create the node storing the frozen attribute details
            this.dbNodeService.createNode(
                    versionNodeRef,
                    CHILD_QNAME_VERSIONED_ATTRIBUTES,
                    CHILD_QNAME_VERSIONED_ATTRIBUTES,
                    TYPE_QNAME_VERSIONED_PROPERTY,
                    props);
        }
    }

    /**
     * Gets the version stores root node
     *
     * @return the node ref to the root node of the version store
     */
    protected NodeRef getRootNode()
    {
        // Get the version store root node reference
        return this.dbNodeService.getRootNode(getVersionStoreReference());
    }

    /**
     * Builds a version history object from the version history reference.
     * <p>
     * The node ref is passed to enable the version history to be scoped to the
     * appropriate branch in the version history.
     *
     * @param versionHistoryRef  the node ref for the version history
     * @param nodeRef            the node reference
     * @return                   a constructed version history object
     */
    protected VersionHistory buildVersionHistory(NodeRef versionHistoryRef, NodeRef nodeRef)
    {
        VersionHistory versionHistory = null;

        // List of versions with current one last and root one first.
        ArrayList<NodeRef> versionHistoryNodeRefs = new ArrayList<NodeRef>();
        
        NodeRef currentVersion;
        if (this.nodeService.exists(nodeRef))
        {
            currentVersion = getCurrentVersionNodeRef(versionHistoryRef, nodeRef);
        }
        else
        {
            currentVersion = VersionUtil.convertNodeRef(getLatestVersion(nodeRef).getFrozenStateNodeRef());
        }

        while (currentVersion != null)
        {
            AssociationRef preceedingVersion = null;

            versionHistoryNodeRefs.add(0, currentVersion);

            List<AssociationRef> preceedingVersions = this.dbNodeService.getSourceAssocs(
                                                                                currentVersion,
                                                                                VersionModel.ASSOC_SUCCESSOR);
            if (preceedingVersions.size() == 1)
            {
                preceedingVersion = (AssociationRef)preceedingVersions.toArray()[0];
                currentVersion = preceedingVersion.getSourceRef();
            }
            else if (preceedingVersions.size() > 1)
            {
                // Error since we only currently support one preceeding version
                throw new VersionServiceException(MSGID_ERR_ONE_PRECEEDING);
            }
            else
            {
                currentVersion = null;
            }
        }
        
        // Build the version history object
        boolean isRoot = true;
        Version preceeding = null;
        for (NodeRef versionRef : versionHistoryNodeRefs)
        {
            Version version = getVersion(versionRef);

            if (isRoot == true)
            {
                versionHistory = new VersionHistoryImpl(version, versionComparatorDesc);
                isRoot = false;
            }
            else
            {
                ((VersionHistoryImpl)versionHistory).addVersion(version, preceeding);
            }
            preceeding = version;
        }

        return versionHistory;
    }

    /**
     * Constructs the a version object to contain the version information from the version node ref.
     *
     * @param versionRef  the version reference
     * @return            object containing verison data
     */
    protected Version getVersion(NodeRef versionRef)
    {
        if (versionRef == null)
        {
            return null;
        }
        Map<String, Serializable> versionProperties = new HashMap<String, Serializable>();

        // Get the standard node details
        Map<QName, Serializable> nodeProperties = this.dbNodeService.getProperties(versionRef);
        for (QName key : nodeProperties.keySet())
        {
            Serializable value = nodeProperties.get(key);
            versionProperties.put(key.getLocalName(), value);
        }

        // Get the meta data
        Map<String, Serializable> versionMetaDataProperties = getVersionMetaData(versionRef);
        versionProperties.putAll(versionMetaDataProperties);
        
        // Create and return the version object
        NodeRef newNodeRef = new NodeRef(new StoreRef(STORE_PROTOCOL, STORE_ID), versionRef.getId());
        Version result = new VersionImpl(versionProperties, newNodeRef);
        // done
        return result;
    }

    /**
     * Gets a reference to the version history node for a given 'real' node.
     *
     * @param nodeRef  a node reference
     * @return         a reference to the version history node, null of none
     */
    protected NodeRef getVersionHistoryNodeRef(NodeRef nodeRef)
    {
        if (nodeService.exists(nodeRef))
        {
            return this.dbNodeService.getChildByName(getRootNode(), CHILD_QNAME_VERSION_HISTORIES, (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_UUID));
        }
        else
        {
            return this.dbNodeService.getChildByName(getRootNode(), CHILD_QNAME_VERSION_HISTORIES, nodeRef.getId());
        }
    }

    /**
     * Gets a reference to the node for the current version of the passed node ref.
     *
     * This uses the version label as a mechanism for looking up the version node in
     * the version history.
     *
     * @param nodeRef  a node reference
     * @return         a reference to a version reference
     */
    private NodeRef getCurrentVersionNodeRef(NodeRef versionHistory, NodeRef nodeRef)
    {
        NodeRef result = null;
        String versionLabel = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL);

        Collection<ChildAssociationRef> versions = this.dbNodeService.getChildAssocs(versionHistory);
        for (ChildAssociationRef version : versions)
        {
            String tempLabel = (String)this.dbNodeService.getProperty(version.getChildRef(), VersionModel.PROP_QNAME_VERSION_LABEL);
            if (tempLabel != null && tempLabel.equals(versionLabel) == true)
            {
                result = version.getChildRef();
                break;
            }
        }

        return result;
    }
    
    /**
     * @see VersionService#ensureVersioningEnabled(NodeRef,Map)
     */
    @Extend(extensionAPI=VersionServiceExtension.class,traitAPI=VersionServiceTrait.class)
    public void ensureVersioningEnabled(NodeRef nodeRef, Map<QName, Serializable> versionProperties)
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("Run as user " + AuthenticationUtil.getRunAsUser());
            logger.debug("Fully authenticated " + AuthenticationUtil.getFullyAuthenticatedUser());
        }
        
        // Don't alter the auditable aspect!
        boolean disableAuditable = policyBehaviourFilter.isEnabled(ContentModel.ASPECT_AUDITABLE);
        if(disableAuditable)
        {
            policyBehaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
        }
        
        // Do we need to apply the aspect?
        if (! nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE))
        {
            // Only apply new properties that are version ones
            AspectDefinition versionable =
                dictionaryService.getAspect(ContentModel.ASPECT_VERSIONABLE);
            Set<QName> versionAspectProperties = 
                versionable.getProperties().keySet();
            
            Map<QName,Serializable> props = new HashMap<QName, Serializable>();
            if(versionProperties != null &&! versionProperties.isEmpty())
            {
                for(QName prop : versionProperties.keySet())
                {
                    if(versionAspectProperties.contains(prop))
                    {
                        // This property is one from the versionable aspect
                        props.put(prop, versionProperties.get(prop));
                    }
                }
            }
            
            // Add the aspect
            nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, props);
        }
        
        // Do we need to create the initial version history entry? By convention this is always a major version.
        if(getVersionHistoryNodeRef(nodeRef) == null)
        {
            createVersion(nodeRef, Collections.<String,Serializable>singletonMap(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR));
        }
        
        // Put Auditable back
        if(disableAuditable)
        {
            policyBehaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
        }
    }

    /**
     * @see org.alfresco.service.cmr.version.VersionService#revert(NodeRef)
     */
    public void revert(NodeRef nodeRef)
    {
        revert(nodeRef, getCurrentVersion(nodeRef), true);
    }

    /**
     * @see org.alfresco.service.cmr.version.VersionService#revert(org.alfresco.service.cmr.repository.NodeRef, boolean)
     */
    public void revert(NodeRef nodeRef, boolean deep)
    {
        revert(nodeRef, getCurrentVersion(nodeRef), deep);
    }

    /**
     * @see org.alfresco.service.cmr.version.VersionService#revert(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.version.Version)
     */
    public void revert(NodeRef nodeRef, Version version)
    {
        revert(nodeRef, version, true);
    }

    /**
     * @see org.alfresco.service.cmr.version.VersionService#revert(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.version.Version, boolean)
     */
    public void revert(NodeRef nodeRef, Version version, boolean deep)
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("Run as user " + AuthenticationUtil.getRunAsUser());
            logger.debug("Fully authenticated " + AuthenticationUtil.getFullyAuthenticatedUser());
        }
        
        // Check the mandatory parameters
        ParameterCheck.mandatory("nodeRef", nodeRef);
        ParameterCheck.mandatory("version", version);

        // Cross check that the version provided relates to the node reference provided
        if (nodeRef.getId().equals(version.getVersionProperty(VersionModel.PROP_FROZEN_NODE_ID)) == false)
        {
            // Error since the version provided does not correspond to the node reference provided
            throw new VersionServiceException(MSGID_ERR_REVERT_MISMATCH);
        }

        // Turn off any auto-version policy behaviours
        this.policyBehaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE);
        try
        {
            // Store the current version label
            String currentVersionLabel = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL);

            // Get the node that represents the frozen state
            NodeRef versionNodeRef = version.getFrozenStateNodeRef();

            // Revert the property values
            this.nodeService.setProperties(nodeRef, this.nodeService.getProperties(versionNodeRef));

            // Apply/remove the aspects as required
            Set<QName> aspects = new HashSet<QName>(this.nodeService.getAspects(nodeRef));
            for (QName versionAspect : this.nodeService.getAspects(versionNodeRef))
            {
                if (aspects.contains(versionAspect) == false)
                {
                    this.nodeService.addAspect(nodeRef, versionAspect, null);
                }
                else
                {
                    aspects.remove(versionAspect);
                }
            }
            for (QName aspect : aspects)
            {
                this.nodeService.removeAspect(nodeRef, aspect);
            }

            // Re-add the versionable aspect to the reverted node
            if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == false)
            {
                this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, null);
            }

            // Re-set the version label property (since it should not be modified from the origional)
            this.nodeService.setProperty(nodeRef, ContentModel.PROP_VERSION_LABEL, currentVersionLabel);

            // Add/remove the child nodes
            List<ChildAssociationRef> children = new ArrayList<ChildAssociationRef>(this.nodeService.getChildAssocs(nodeRef));
            for (ChildAssociationRef versionedChild : this.nodeService.getChildAssocs(versionNodeRef))
            {
                if (children.contains(versionedChild) == false)
                {
                    if (this.nodeService.exists(versionedChild.getChildRef()) == true)
                    {
                        // The node was a primary child of the parent, but that is no longer the case.  Dispite this
                        // the node still exits so this means it has been moved.
                        // The best thing to do in this situation will be to re-add the node as a child, but it will not
                        // be a primary child.
                        this.nodeService.addChild(nodeRef, versionedChild.getChildRef(), versionedChild.getTypeQName(), versionedChild.getQName());
                    }
                    else
                    {
                        if (versionedChild.isPrimary() == true)
                        {
                            // Only try to resotre missing children if we are doing a deep revert
                            // Look and see if we have a version history for the child node
                            if (deep == true && getVersionHistoryNodeRef(versionedChild.getChildRef()) != null)
                            {
                                // We're going to try and restore the missing child node and recreate the assoc
                                restore(
                                   versionedChild.getChildRef(),
                                   nodeRef,
                                   versionedChild.getTypeQName(),
                                   versionedChild.getQName());
                            }
                            // else the deleted child did not have a version history so we can't restore the child
                            // and so we can't revert the association
                        }

                        // else
                        // Since this was never a primary assoc and the child has been deleted we won't recreate
                        // the missing node as it was never owned by the node and we wouldn't know where to put it.
                    }
                }
                else
                {
                    children.remove(versionedChild);
                }
            }
            for (ChildAssociationRef ref : children)
            {
                this.nodeService.removeChild(nodeRef, ref.getChildRef());
            }

            // Add/remove the target associations
            for (AssociationRef assocRef : this.nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL))
            {
                this.nodeService.removeAssociation(assocRef.getSourceRef(), assocRef.getTargetRef(), assocRef.getTypeQName());
            }
            for (AssociationRef versionedAssoc : this.nodeService.getTargetAssocs(versionNodeRef, RegexQNamePattern.MATCH_ALL))
            {
                if (this.nodeService.exists(versionedAssoc.getTargetRef()) == true)
                {
                    this.nodeService.createAssociation(nodeRef, versionedAssoc.getTargetRef(), versionedAssoc.getTypeQName());
                }

                // else
                // Since the tareget of the assoc no longer exists we can't recreate the assoc
            }
        }
        finally
        {
            // Turn auto-version policies back on
            this.policyBehaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE);
        }
    }

    /**
     * @see org.alfresco.service.cmr.version.VersionService#restore(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName)
     */
     public NodeRef restore(
                NodeRef nodeRef,
                NodeRef parentNodeRef,
                QName assocTypeQName,
                QName assocQName)
     {
         return restore(nodeRef, parentNodeRef, assocTypeQName, assocQName, true);
     }

    /**
     * @see org.alfresco.service.cmr.version.VersionService#restore(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName, boolean)
     */
     public NodeRef restore(
            NodeRef nodeRef,
            NodeRef parentNodeRef,
            QName assocTypeQName,
            QName assocQName,
            boolean deep)
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("Run as user " + AuthenticationUtil.getRunAsUser());
            logger.debug("Fully authenticated " + AuthenticationUtil.getFullyAuthenticatedUser());
        }
         
        NodeRef restoredNodeRef = null;

        // Check that the node does not exist
        if (this.nodeService.exists(nodeRef) == true)
        {
            // Error since you can not restore a node that already exists
            throw new VersionServiceException(MSGID_ERR_RESTORE_EXISTS, new Object[]{nodeRef.toString()});
        }

        // Try and get the version details that we want to restore to
        Version version = getHeadVersion(nodeRef);
        if (version == null)
        {
            // Error since there is no version information available to restore the node from
            throw new VersionServiceException(MSGID_ERR_RESTORE_NO_VERSION, new Object[]{nodeRef.toString()});
        }

        // Set the uuid of the new node
        Map<QName, Serializable> props = new HashMap<QName, Serializable>(1);
        props.put(ContentModel.PROP_NODE_UUID, version.getVersionProperty(VersionModel.PROP_FROZEN_NODE_ID));

        // Get the type of the node node
        QName type = (QName)version.getVersionProperty(VersionModel.PROP_FROZEN_NODE_TYPE);

        // Disable auto-version behaviour
        this.policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_VERSIONABLE);
        try
        {
            // Create the restored node
            restoredNodeRef = this.nodeService.createNode(
                    parentNodeRef,
                    assocTypeQName,
                    assocQName,
                    type,
                    props).getChildRef();
        }
        finally
        {
            // Enable auto-version behaviour
            this.policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_VERSIONABLE);
        }

        // Now we need to revert the newly restored node
        revert(restoredNodeRef, version, deep);

        return restoredNodeRef;
    }

    /**
     * Get the head version given a node reference
     *
     * @param nodeRef   the node reference
     * @return          the 'head' version
     */
    private Version getHeadVersion(NodeRef nodeRef)
    {
        Version version = null;
        StoreRef storeRef = nodeRef.getStoreRef();

        NodeRef versionHistoryNodeRef = getVersionHistoryNodeRef(nodeRef);
        if (versionHistoryNodeRef != null)
        {
            List<ChildAssociationRef> versionsAssoc = this.dbNodeService.getChildAssocs(versionHistoryNodeRef, RegexQNamePattern.MATCH_ALL, VersionModel.CHILD_QNAME_VERSIONS);
            for (ChildAssociationRef versionAssoc : versionsAssoc)
            {
                NodeRef versionNodeRef = versionAssoc.getChildRef();
                List<AssociationRef> successors = this.dbNodeService.getTargetAssocs(versionNodeRef, VersionModel.ASSOC_SUCCESSOR);
                if (successors.size() == 0)
                {
                    String storeProtocol = (String)this.dbNodeService.getProperty(
                            versionNodeRef,
                            QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_NODE_STORE_PROTOCOL));
                    String storeId = (String)this.dbNodeService.getProperty(
                            versionNodeRef,
                            QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_NODE_STORE_ID));
                    StoreRef versionStoreRef = new StoreRef(storeProtocol, storeId);
                    if (storeRef.equals(versionStoreRef) == true)
                    {
                        version = getVersion(versionNodeRef);
                    }
                }
            }
        }

        return version;
    }
    
    private Version getLatestVersion(NodeRef nodeRef)
    {
        Version version = null;
        StoreRef storeRef = nodeRef.getStoreRef();

        NodeRef versionHistoryNodeRef = getVersionHistoryNodeRef(nodeRef);
        if (versionHistoryNodeRef != null)
        {
            List<ChildAssociationRef> versionsAssoc = this.dbNodeService.getChildAssocs(versionHistoryNodeRef, RegexQNamePattern.MATCH_ALL, VersionModel.CHILD_QNAME_VERSIONS);
            for (ChildAssociationRef versionAssoc : versionsAssoc)
            {
                NodeRef versionNodeRef = versionAssoc.getChildRef();
                List<AssociationRef> predecessors = this.dbNodeService.getSourceAssocs(versionNodeRef, VersionModel.ASSOC_SUCCESSOR);
                if (predecessors.size() == 0)
                {
                    String storeProtocol = (String)this.dbNodeService.getProperty(
                            versionNodeRef,
                            QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_NODE_STORE_PROTOCOL));
                    String storeId = (String)this.dbNodeService.getProperty(
                            versionNodeRef,
                            QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_NODE_STORE_ID));
                    StoreRef versionStoreRef = new StoreRef(storeProtocol, storeId);
                    if (storeRef.equals(versionStoreRef) == true)
                    {
                        version = getVersion(versionNodeRef);
                    }
                }
            }
        }

        return version;
    }

    /**
     * @see VersionService#deleteVersionHistory(NodeRef)
     */
    public void deleteVersionHistory(NodeRef nodeRef)
        throws AspectMissingException
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("Run as user " + AuthenticationUtil.getRunAsUser());
            logger.debug("Fully authenticated " + AuthenticationUtil.getFullyAuthenticatedUser());
        }
        
        // Get the version history node for the node is question and delete it
        NodeRef versionHistoryNodeRef = getVersionHistoryNodeRef(nodeRef);

        if (versionHistoryNodeRef != null)
        {
            // Delete the version history node
            this.dbNodeService.deleteNode(versionHistoryNodeRef);

            if (this.nodeService.exists(nodeRef) == true && this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == true)
            {
                // Reset the version label property on the versionable node
                this.nodeService.setProperty(nodeRef, ContentModel.PROP_VERSION_LABEL, null);
            }
        }
    }
    
    public void deleteVersion(NodeRef nodeRef, Version version)
    {
        // This operation is not supported for a version1 store
        throw new UnsupportedOperationException("Delete version is unsupported by the old (deprecated) version store implementation");
    }
    
    @Override
    protected void defaultOnCreateVersion(
            QName classRef,
            NodeRef nodeRef, 
            Map<String, Serializable> versionProperties, 
            PolicyScope nodeDetails)
    {
        ClassDefinition classDefinition = this.dictionaryService.getClass(classRef);    
        if (classDefinition != null)
        {           
            boolean wasMLAware = MLPropertyInterceptor.setMLAware(true);
            try
            {
                // Copy the properties (along with their aspect)
                Map<QName,PropertyDefinition> propertyDefinitions = classDefinition.getProperties();
                for (QName propertyName : propertyDefinitions.keySet()) 
                {
                    Serializable propValue = this.nodeService.getProperty(nodeRef, propertyName);
                    nodeDetails.addProperty(classRef, propertyName, propValue);
                }
                // Also copy the aspect with no properties in its definition
                if (classDefinition.isAspect() && !nodeDetails.getAspects().contains(classRef))
                {
                    nodeDetails.addAspect(classRef);
                }
            }
            finally
            {
                MLPropertyInterceptor.setMLAware(wasMLAware);
            }
            
            // Version the associations (child and target)
            Map<QName, AssociationDefinition> assocDefs = classDefinition.getAssociations();

            // TODO: Need way of getting child assocs of a given type
            if (classDefinition.isContainer())
            {
                List<ChildAssociationRef> childAssocRefs = this.nodeService.getChildAssocs(nodeRef);
                for (ChildAssociationRef childAssocRef : childAssocRefs) 
                {
                    if (assocDefs.containsKey(childAssocRef.getTypeQName()))
                    {
                        nodeDetails.addChildAssociation(classDefinition.getName(), childAssocRef);
                    }
                }
            }
            
            // TODO: Need way of getting assocs of a given type
            List<AssociationRef> nodeAssocRefs = this.nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL);
            for (AssociationRef nodeAssocRef : nodeAssocRefs) 
            {
                if (assocDefs.containsKey(nodeAssocRef.getTypeQName()))
                {
                    nodeDetails.addAssociation(classDefinition.getName(), nodeAssocRef);
                }
            }
        }
    }

	@Override
    public boolean isAVersion(NodeRef nodeRef)
    {
		throw new UnsupportedOperationException();
    }
	
	@Override
    public boolean isVersioned(NodeRef nodeRef)
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("Run as user " + AuthenticationUtil.getRunAsUser());
            logger.debug("Fully authenticated " + AuthenticationUtil.getFullyAuthenticatedUser());
        }
	    
        return this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE);
    }
}
