getVersionAssocs(NodeRef versionHistoryRef, boolean preLoad)
    {
        // note: resultant list is ordered by (a) explicit index and (b) association creation time
        return dbNodeService.getChildAssocs(versionHistoryRef, Version2Model.CHILD_QNAME_VERSIONS, RegexQNamePattern.MATCH_ALL, preLoad);
    }
    
    /**
     * Builds a version history object from the version history reference.
     * 
     * 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)
    {
        if (useDeprecatedV1)
        {
            return super.buildVersionHistory(versionHistoryRef, nodeRef);
        }
        
        VersionHistory versionHistory = null;
        
        List versions = getAllVersions(versionHistoryRef);
        
        Collections.sort(versions, versionComparatorAsc);
        
        // Build the version history object
        boolean isRoot = true;
        Version preceeding = null;
        for (Version version : versions)
        {
            if (isRoot == true)
            {
                versionHistory = new VersionHistoryImpl(version);
                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 (useDeprecatedV1)
        {
            return super.getVersion(versionRef);
        }
        
        if (versionRef == null)
        {
            return null;
        }
        Map versionProperties = new HashMap();
        
        // Get the standard node details and get the meta data
        Map nodeProperties = this.dbNodeService.getProperties(versionRef);
        
        if (logger.isTraceEnabled())
        {
            logger.trace("getVersion: " + versionRef + " nodeProperties=\n" + nodeProperties.keySet());
        }
        
        // TODO consolidate with VersionUtil.convertFrozenToOriginalProps
        
        for (QName key : nodeProperties.keySet())
        {
            Serializable value = nodeProperties.get(key);
            
            String keyName = key.getLocalName();
            int idx = keyName.indexOf(Version2Model.PROP_METADATA_PREFIX);
            if (idx == 0)
            {
                // versioned metadata property - additional (optional) metadata, set during versioning
            	versionProperties.put(keyName.substring(Version2Model.PROP_METADATA_PREFIX.length()), value);
            }
            else
            {
               if (key.equals(Version2Model.PROP_QNAME_VERSION_DESCRIPTION))
               {
                   versionProperties.put(Version.PROP_DESCRIPTION, (String)value);
               }
               else if (key.equals(Version2Model.PROP_QNAME_VERSION_LABEL))
               {
                   versionProperties.put(VersionBaseModel.PROP_VERSION_LABEL, (String)value);
               }
               else if (key.equals(Version2Model.PROP_QNAME_VERSION_NUMBER))
               {
                   // deprecated (unused)
                   //versionProperties.put(VersionBaseModel.PROP_VERSION_NUMBER, (Integer)value);
               }
               else
               {
                   if (keyName.equals(Version.PROP_DESCRIPTION) || 
                       keyName.equals(VersionBaseModel.PROP_VERSION_LABEL) ||
                       keyName.equals(VersionBaseModel.PROP_VERSION_NUMBER))
                   {
                       // ignore reserved localname (including cm:description, cm:versionLabel)
                   }
                   else
                   {
                       // all other properties
                       versionProperties.put(keyName, value);
                   }
               }
            }
        }
        
        // Create and return the version object
        NodeRef newNodeRef = new NodeRef(new StoreRef(Version2Model.STORE_PROTOCOL, Version2Model.STORE_ID), versionRef.getId());
        Version result = new VersionImpl(versionProperties, newNodeRef);
        
        if (logger.isTraceEnabled())
        {
            logger.trace("getVersion: " + versionRef + " versionProperties=\n" + versionProperties.keySet());
        }
        
        // 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 (useDeprecatedV1)
        {
            return super.getVersionHistoryNodeRef(nodeRef);
        }
        
        // assume noderef is a 'live' node
        NodeRef vhNodeRef = this.dbNodeService.getChildByName(getRootNode(), Version2Model.CHILD_QNAME_VERSION_HISTORIES, nodeRef.getId());
        
        // DEPRECATED: for backwards compatibility, in case of a version node (eg. via getCurrentVersion) can lookup 'live' node via UUID
        if (vhNodeRef == null)
        {
        	if (nodeService.exists(nodeRef))
            {
        		vhNodeRef = this.dbNodeService.getChildByName(getRootNode(), Version2Model.CHILD_QNAME_VERSION_HISTORIES, (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_UUID));
            }
        }
        return vhNodeRef;
    }
    /**
     * Gets current version of the passed node ref
     *
     * This uses the version label as a mechanism for looking up the version node.
     */
    private Pair getCurrentVersionImpl(NodeRef versionHistoryRef, NodeRef nodeRef)
    {
        Pair result = null;
        
        String versionLabel = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL);
        
        // note: resultant list is ordered by (a) explicit index and (b) association creation time
        List versionAssocs = getVersionAssocs(versionHistoryRef, false);
        
        // Current version should be head version (since no branching)
        int cnt = versionAssocs.size();
        for (int i = cnt; i > 0; i--)
        {
            ChildAssociationRef versionAssoc = versionAssocs.get(i-1);
            
            String tempLabel = (String)this.dbNodeService.getProperty(versionAssoc.getChildRef(), Version2Model.PROP_QNAME_VERSION_LABEL);
            if (tempLabel != null && tempLabel.equals(versionLabel) == true)
            {
                boolean headVersion = (i == cnt);
                
                if (! headVersion)
                {
                    if (logger.isDebugEnabled())
                    {
                        logger.debug("Unexpected: current version does not appear to be 1st version in the list  ["+versionHistoryRef+", "+nodeRef+"]");
                    }
                }
                
                result = new Pair(headVersion, getVersion(versionAssoc.getChildRef()));
                break;
            }
        }
        
        return result;
    }
    
    /**
     * Check if versions are marked with invalid version label, if true > apply default serial version label (e.g. "1.0", "1.1") 
     * 
     * @param versionHistory a version histore node reference
     * @param nodeRef a node reference
     */
    private void checkForCorruptedVersions(NodeRef versionHistory, NodeRef nodeRef)
    {
        // get the current version label in live store
        String versionLabel = (String) this.nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL);
        if (versionLabel != null && versionLabel.equals("0"))
        {
            // need to correct version labels
            List versions = getAllVersions(versionHistory);
            // sort versions by node id
            Collections.sort(versions, new Comparator()
            {
                public int compare(Version v1, Version v2)
                {
                    int result = v1.getFrozenModifiedDate().compareTo(v2.getFrozenModifiedDate());
                    if (result == 0)
                    {
                        Long dbid1 = (Long)nodeService.getProperty(v1.getFrozenStateNodeRef(), ContentModel.PROP_NODE_DBID);
                        Long dbid2 = (Long)nodeService.getProperty(v2.getFrozenStateNodeRef(), ContentModel.PROP_NODE_DBID);
                        
                        if (dbid1 != null && dbid2 != null)
                        {
                            result = dbid1.compareTo(dbid2);
                        }
                        else
                        {
                            result = 0;
                            
                            if (logger.isWarnEnabled())
                            {
                                logger.warn("node-dbid property is missing for versions: " + v1.toString() + " or " + v2.toString());
                            }
                        }
                    }
                    return result;
                }
            });
            SerialVersionLabelPolicy serialVersionLabelPolicy = new SerialVersionLabelPolicy();
            QName classRef = this.nodeService.getType(nodeRef);
            Version preceedingVersion = null;
            for (Version version : versions)
            {
                // re-calculate version label
                versionLabel = serialVersionLabelPolicy.calculateVersionLabel(classRef, preceedingVersion, 0, version.getVersionProperties());
                // update version with new version label
                NodeRef versionNodeRef = new NodeRef(StoreRef.PROTOCOL_WORKSPACE, version.getFrozenStateNodeRef().getStoreRef().getIdentifier(), version.getFrozenStateNodeRef()
                        .getId());
                this.dbNodeService.setProperty(versionNodeRef, Version2Model.PROP_QNAME_VERSION_LABEL, versionLabel);
                
                version.getVersionProperties().put(VersionBaseModel.PROP_VERSION_LABEL, versionLabel);
                // remember preceding version
                preceedingVersion = version;
            }
            // update current version label in live store
            this.nodeService.setProperty(nodeRef, ContentModel.PROP_VERSION_LABEL, versionLabel);
        }
    }
    /**
     * @see org.alfresco.cms.version.VersionService#revert(NodeRef)
     */
    public void revert(NodeRef nodeRef)
    {
        if (useDeprecatedV1)
        {
            super.revert(nodeRef, getCurrentVersion(nodeRef), true);
        }
        else
        {
            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)
    {
        if (useDeprecatedV1)
        {
            super.revert(nodeRef, getCurrentVersion(nodeRef), deep);
        }
        else
        {
            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)
    {
        if (useDeprecatedV1)
        {
            super.revert(nodeRef, version, true);
        }
        else
        {
            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 (useDeprecatedV1)
        {
            super.revert(nodeRef, version, deep);
        }
        else
        {
            // 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(((NodeRef)version.getVersionProperty(Version2Model.PROP_FROZEN_NODE_REF)).getId()) == 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
                Map props = this.nodeService.getProperties(versionNodeRef);
                VersionUtil.convertFrozenToOriginalProps(props);
                
                this.nodeService.setProperties(nodeRef, props);
    
                // Apply/remove the aspects as required
                Set aspects = new HashSet(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 original)
                this.nodeService.setProperty(nodeRef, ContentModel.PROP_VERSION_LABEL, currentVersionLabel);
    
                // Add/remove the child nodes
                List children = new ArrayList(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.  Despite 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 restore 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 target 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);
            }
        }
    }
    /* (non-Javadoc)
     * @see org.alfresco.repo.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)
     {
         if (useDeprecatedV1)
         {
             return super.restore(nodeRef, parentNodeRef, assocTypeQName, assocQName, true);
         }
         return restore(nodeRef, parentNodeRef, assocTypeQName, assocQName, true);
     }
    /* (non-Javadoc)
     * @see org.alfresco.repo.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 (useDeprecatedV1)
        {
            return super.restore(nodeRef, parentNodeRef, assocTypeQName, assocQName, deep);
        }
         
        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 props = new HashMap(1);
        props.put(ContentModel.PROP_NODE_UUID, ((NodeRef)version.getVersionProperty(Version2Model.PROP_FROZEN_NODE_REF)).getId());
        // Get the type of the frozen node
        QName type = (QName)dbNodeService.getType(VersionUtil.convertNodeRef(version.getFrozenStateNodeRef()));
        // 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)
    {
        NodeRef versionHistoryNodeRef = getVersionHistoryNodeRef(nodeRef);
        
        Version headVersion = null;
        if (versionHistoryNodeRef != null)
        {
            VersionHistory versionHistory = buildVersionHistory(versionHistoryNodeRef, nodeRef);
            if (versionHistory != null)
            {
                headVersion = versionHistory.getHeadVersion();
            }
        }
        return headVersion;
    }
    /* (non-Javadoc)
     * @see org.alfresco.repo.service.cmr.version.VersionService#deleteVersionHistory(org.alfresco.service.cmr.repository.NodeRef)
     */
    public void deleteVersionHistory(NodeRef nodeRef)
                                     throws AspectMissingException
    {
        if (useDeprecatedV1)
        {
            super.deleteVersionHistory(nodeRef);
        }
        else
        {
            // Get the version history node for the node is question and delete it
            NodeRef versionHistoryNodeRef = getVersionHistoryNodeRef(nodeRef);
            
            if (versionHistoryNodeRef != null)
            {
                try
                {
                    // Disable auto-version behaviour
                    this.policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_VERSIONABLE);
                    
                    // 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);
                    }
                    
                }
                finally
                {
                    this.policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_VERSIONABLE);
                }
            }
        }
    }
    
    /* (non-Javadoc)
     * @see org.alfresco.service.cmr.version.VersionService#deleteVersion(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.version.Version)
     */
    public void deleteVersion(NodeRef nodeRef, Version version)
    {
        if (useDeprecatedV1)
        {
            super.deleteVersion(nodeRef, version); // throws UnsupportedOperationException
        }
        else
        {
            // Check the mandatory parameters
            ParameterCheck.mandatory("nodeRef", nodeRef);
            ParameterCheck.mandatory("version", version);
            
            Version currentVersion = getCurrentVersion(nodeRef);
            
            // Delete the version node
            this.dbNodeService.deleteNode(VersionUtil.convertNodeRef(version.getFrozenStateNodeRef()));
            
            if (currentVersion.getVersionLabel().equals(version.getVersionLabel()))
            {
                Version headVersion = getHeadVersion(nodeRef);
                if (headVersion != null)
                {
                    // Reset the version label property on the versionable node to new head version
                    // Disable the VersionableAspect for this change though, we don't want
                    //  to have this create a new version for the property change!
                    policyBehaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE);
                    this.nodeService.setProperty(nodeRef, ContentModel.PROP_VERSION_LABEL, headVersion.getVersionLabel());
                }
                else
                {
                    deleteVersionHistory(nodeRef);
                }
            }
        }
    }
}