<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.jackrabbit.core.version;

import EDU.oswego.cs.dl.util.concurrent.ReadWriteLock;
import EDU.oswego.cs.dl.util.concurrent.ReentrantWriterPreferenceReadWriteLock;
import org.apache.jackrabbit.core.NodeId;
import org.apache.jackrabbit.core.NodeImpl;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.state.ItemStateException;
import org.apache.jackrabbit.core.state.LocalItemStateManager;
import org.apache.jackrabbit.core.state.NodeState;
import org.apache.jackrabbit.name.QName;
import org.apache.jackrabbit.uuid.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.version.VersionException;
import javax.jcr.version.VersionHistory;
import java.util.List;

/**
 * Base implementation of the {@link VersionManager} interface.
 * <p/>
 * All read operations must aquire the read lock before reading, all write
 * operations must aquire the write lock.
 */
abstract class AbstractVersionManager implements VersionManager {

    /**
     * Logger instance.
     */
    private static Logger log = LoggerFactory.getLogger(AbstractVersionManager.class);

    /**
     * State manager for the version storage.
     */
    protected LocalItemStateManager stateMgr;

    /**
     * Persistent root node of the version histories.
     */
    protected NodeStateEx historyRoot;

    /**
     * the lock on this version manager
     */
    private final ReadWriteLock rwLock =
            new ReentrantWriterPreferenceReadWriteLock() {
                /**
                 * Allow reader when there is no active writer, or current
                 * thread owns the write lock (reentrant).
                 */
                protected boolean allowReader() {
                    return activeWriter_ == null
                        || activeWriter_ == Thread.currentThread();
                }
            };

    //-------------------------------------------------------< VersionManager >

    /**
     * {@inheritDoc}
     */
    public InternalVersion getVersion(NodeId id) throws RepositoryException {
        // lock handling via getItem()
        InternalVersion v = (InternalVersion) getItem(id);
        if (v == null) {
            log.warn("Versioning item not found: " + id);
        }
        return v;
    }

    /**
     * {@inheritDoc}
     */
    public InternalVersionHistory getVersionHistory(NodeId id)
            throws RepositoryException {
        // lock handling via getItem()
        return (InternalVersionHistory) getItem(id);
    }

    /**
     * {@inheritDoc}
     */
    public boolean hasVersionHistory(NodeId id) {
        // lock handling via hasItem()
        return hasItem(id);
    }

    /**
     * {@inheritDoc}
     */
    public boolean hasVersion(NodeId id) {
        // lock handling via hasItem()
        return hasItem(id);
    }

    //-------------------------------------------------------< implementation >

    /**
     * aquires the write lock on this version manager.
     */
    protected void aquireWriteLock() {
        while (true) {
            try {
                rwLock.writeLock().acquire();
                return;
            } catch (InterruptedException e) {
                // ignore
            }
        }
    }

    /**
     * releases the write lock on this version manager.
     */
    protected void releaseWriteLock() {
        rwLock.writeLock().release();
    }

    /**
     * aquires the read lock on this version manager.
     */
    protected void aquireReadLock() {
        while (true) {
            try {
                rwLock.readLock().acquire();
                return;
            } catch (InterruptedException e) {
                // ignore
            }
        }
    }

    /**
     * releases the read lock on this version manager.
     */
    protected void releaseReadLock() {
        rwLock.readLock().release();
    }

    /**
     * {@inheritDoc}
     */
    public VersionHistory getVersionHistory(Session session, NodeState node)
            throws RepositoryException {
        aquireReadLock();
        try {
            NodeId vhId = getVersionHistoryId(node);
            if (vhId == null) {
                return null;
            }
            return (VersionHistory) ((SessionImpl) session).getNodeById(vhId);
        } finally {
            releaseReadLock();
        }
    }

    /**
     * Returns the item with the given persistent id. Subclass responsibility.
     * <p/>
     * Please note, that the overridden method must aquire the readlock before
     * reading the state manager.
     *
     * @param id the id of the item
     * @return version item
     * @throws RepositoryException if an error occurs
     */
    protected abstract InternalVersionItem getItem(NodeId id)
            throws RepositoryException;

    /**
     * Return a flag indicating if the item specified exists.
     * Subclass responsibility.
     * @param id the id of the item
     * @return <code>true</code> if the item exists;
     *         <code>false</code> otherwise
     */
    protected abstract boolean hasItem(NodeId id);

    /**
     * Returns the item references that reference the given version item.
     * Subclass responsiblity.
     * <p/>
     * Please note, that the overridden method must aquire the readlock before
     * reading the state manager.
     *
     * @param item version item
     * @return list of item references, may be empty.
     */
    protected abstract List getItemReferences(InternalVersionItem item);

    /**
     * Creates a new Version History.
     *
     * @param node the node for which the version history is to be initialized
     * @return the newly created version history.
     * @throws javax.jcr.RepositoryException
     */
    InternalVersionHistory createVersionHistory(NodeState node)
            throws RepositoryException {

        aquireWriteLock();
        try {
            stateMgr.edit();
        } catch (IllegalStateException e) {
            releaseWriteLock();
            throw new RepositoryException("Unable to start edit operation", e);
        }

        boolean succeeded = false;

        try {
            // create deep path
            String uuid = node.getNodeId().getUUID().toString();
            NodeStateEx root = historyRoot;
            for (int i = 0; i < 3; i++) {
                QName name = new QName(QName.NS_DEFAULT_URI, uuid.substring(i * 2, i * 2 + 2));
                if (!root.hasNode(name)) {
                    root.addNode(name, QName.REP_VERSIONSTORAGE, null, false);
                    root.store();
                }
                root = root.getNode(name, 1);
            }
            QName historyNodeName = new QName(QName.NS_DEFAULT_URI, uuid);
            if (root.hasNode(historyNodeName)) {
                // already exists
                return null;
            }

            // create new history node in the persistent state
            InternalVersionHistoryImpl hist = InternalVersionHistoryImpl.create(
                    this, root, new NodeId(UUID.randomUUID()), historyNodeName, node);

            // end update
            stateMgr.update();
            succeeded = true;

            log.info("Created new version history " + hist.getId() + " for " + node + ".");
            return hist;

        } catch (ItemStateException e) {
            throw new RepositoryException(e);
        } finally {
            if (!succeeded) {
                // update operation failed, cancel all modifications
                stateMgr.cancel();
            }
            releaseWriteLock();
        }
    }

    /**
     * Returns the id of the version history associated with the given node
     * or <code>null</code> if that node doesn't have a version history.
     *
     * @param node the node whose version history's id is to be returned.
     * @return the the id of the version history associated with the given node
     *         or <code>null</code> if that node doesn't have a version history.
     * @throws javax.jcr.RepositoryException if an error occurs
     */
    private NodeId getVersionHistoryId(NodeState node)
            throws RepositoryException {
        // build and traverse path
        String uuid = node.getNodeId().getUUID().toString();
        NodeStateEx n = historyRoot;
        for (int i = 0; i < 3; i++) {
            QName name = new QName(QName.NS_DEFAULT_URI, uuid.substring(i * 2, i * 2 + 2));
            if (!n.hasNode(name)) {
                return null;
            }
            n = n.getNode(name, 1);
        }
        QName historyNodeName = new QName(QName.NS_DEFAULT_URI, uuid);
        if (!n.hasNode(historyNodeName)) {
            return null;
        }
        return n.getNode(historyNodeName, 1).getNodeId();
    }

    /**
     * Checks in a node
     *
     * @param node node to checkin
     * @return internal version
     * @throws javax.jcr.RepositoryException if an error occurs
     * @see javax.jcr.Node#checkin()
     */
    protected InternalVersion checkin(InternalVersionHistoryImpl history, NodeImpl node)
            throws RepositoryException {

        aquireReadLock();
        String versionName;
        try {
            // 1. search a predecessor, suitable for generating the new name
            Value[] values = node.getProperty(QName.JCR_PREDECESSORS).getValues();
            InternalVersion best = null;
            for (int i = 0; i < values.length; i++) {
                InternalVersion pred = history.getVersion(NodeId.valueOf(values[i].getString()));
                if (best == null || pred.getSuccessors().length < best.getSuccessors().length) {
                    best = pred;
                }
            }

            // 2. generate version name (assume no namespaces in version names)
            versionName = best.getName().getLocalName();
            int pos = versionName.lastIndexOf('.');
            if (pos > 0) {
                versionName = versionName.substring(0, pos + 1)
                    + (Integer.parseInt(versionName.substring(pos + 1)) + 1);
            } else {
                versionName = String.valueOf(best.getSuccessors().length + 1) + ".0";
            }

            // 3. check for colliding names
            while (history.hasVersion(new QName("", versionName))) {
                versionName += ".1";
            }

            stateMgr.edit();
        } catch (IllegalStateException e) {
            throw new RepositoryException("Unable to start edit operation.");
        } finally {
            releaseReadLock();
        }

        boolean succeeded = false;

        try {
            aquireWriteLock();
            releaseReadLock();

            InternalVersionImpl v = history.checkin(new QName("", versionName), node);
            stateMgr.update();<commit_msg>JCR-632: Reverted revision 475683.<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.jackrabbit.core.version;

import EDU.oswego.cs.dl.util.concurrent.ReadWriteLock;
import EDU.oswego.cs.dl.util.concurrent.ReentrantWriterPreferenceReadWriteLock;
import org.apache.jackrabbit.core.NodeId;
import org.apache.jackrabbit.core.NodeImpl;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.state.ItemStateException;
import org.apache.jackrabbit.core.state.LocalItemStateManager;
import org.apache.jackrabbit.core.state.NodeState;
import org.apache.jackrabbit.name.QName;
import org.apache.jackrabbit.uuid.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.version.VersionException;
import javax.jcr.version.VersionHistory;
import java.util.List;

/**
 * Base implementation of the {@link VersionManager} interface.
 * <p/>
 * All read operations must aquire the read lock before reading, all write
 * operations must aquire the write lock.
 */
abstract class AbstractVersionManager implements VersionManager {

    /**
     * Logger instance.
     */
    private static Logger log = LoggerFactory.getLogger(AbstractVersionManager.class);

    /**
     * State manager for the version storage.
     */
    protected LocalItemStateManager stateMgr;

    /**
     * Persistent root node of the version histories.
     */
    protected NodeStateEx historyRoot;

    /**
     * the lock on this version manager
     */
    private final ReadWriteLock rwLock =
            new ReentrantWriterPreferenceReadWriteLock() {
                /**
                 * Allow reader when there is no active writer, or current
                 * thread owns the write lock (reentrant).
                 */
                protected boolean allowReader() {
                    return activeWriter_ == null
                        || activeWriter_ == Thread.currentThread();
                }
            };

    //-------------------------------------------------------< VersionManager >

    /**
     * {@inheritDoc}
     */
    public InternalVersion getVersion(NodeId id) throws RepositoryException {
        // lock handling via getItem()
        InternalVersion v = (InternalVersion) getItem(id);
        if (v == null) {
            log.warn("Versioning item not found: " + id);
        }
        return v;
    }

    /**
     * {@inheritDoc}
     */
    public InternalVersionHistory getVersionHistory(NodeId id)
            throws RepositoryException {
        // lock handling via getItem()
        return (InternalVersionHistory) getItem(id);
    }

    /**
     * {@inheritDoc}
     */
    public boolean hasVersionHistory(NodeId id) {
        // lock handling via hasItem()
        return hasItem(id);
    }

    /**
     * {@inheritDoc}
     */
    public boolean hasVersion(NodeId id) {
        // lock handling via hasItem()
        return hasItem(id);
    }

    //-------------------------------------------------------< implementation >

    /**
     * aquires the write lock on this version manager.
     */
    protected void aquireWriteLock() {
        while (true) {
            try {
                rwLock.writeLock().acquire();
                return;
            } catch (InterruptedException e) {
                // ignore
            }
        }
    }

    /**
     * releases the write lock on this version manager.
     */
    protected void releaseWriteLock() {
        rwLock.writeLock().release();
    }

    /**
     * aquires the read lock on this version manager.
     */
    protected void aquireReadLock() {
        while (true) {
            try {
                rwLock.readLock().acquire();
                return;
            } catch (InterruptedException e) {
                // ignore
            }
        }
    }

    /**
     * releases the read lock on this version manager.
     */
    protected void releaseReadLock() {
        rwLock.readLock().release();
    }

    /**
     * {@inheritDoc}
     */
    public VersionHistory getVersionHistory(Session session, NodeState node)
            throws RepositoryException {
        aquireReadLock();
        try {
            NodeId vhId = getVersionHistoryId(node);
            if (vhId == null) {
                return null;
            }
            return (VersionHistory) ((SessionImpl) session).getNodeById(vhId);
        } finally {
            releaseReadLock();
        }
    }

    /**
     * Returns the item with the given persistent id. Subclass responsibility.
     * <p/>
     * Please note, that the overridden method must aquire the readlock before
     * reading the state manager.
     *
     * @param id the id of the item
     * @return version item
     * @throws RepositoryException if an error occurs
     */
    protected abstract InternalVersionItem getItem(NodeId id)
            throws RepositoryException;

    /**
     * Return a flag indicating if the item specified exists.
     * Subclass responsibility.
     * @param id the id of the item
     * @return <code>true</code> if the item exists;
     *         <code>false</code> otherwise
     */
    protected abstract boolean hasItem(NodeId id);

    /**
     * Returns the item references that reference the given version item.
     * Subclass responsiblity.
     * <p/>
     * Please note, that the overridden method must aquire the readlock before
     * reading the state manager.
     *
     * @param item version item
     * @return list of item references, may be empty.
     */
    protected abstract List getItemReferences(InternalVersionItem item);

    /**
     * Creates a new Version History.
     *
     * @param node the node for which the version history is to be initialized
     * @return the newly created version history.
     * @throws javax.jcr.RepositoryException
     */
    InternalVersionHistory createVersionHistory(NodeState node)
            throws RepositoryException {

        aquireWriteLock();
        try {
            stateMgr.edit();
        } catch (IllegalStateException e) {
            releaseWriteLock();
            throw new RepositoryException("Unable to start edit operation", e);
        }

        boolean succeeded = false;

        try {
            // create deep path
            String uuid = node.getNodeId().getUUID().toString();
            NodeStateEx root = historyRoot;
            for (int i = 0; i < 3; i++) {
                QName name = new QName(QName.NS_DEFAULT_URI, uuid.substring(i * 2, i * 2 + 2));
                if (!root.hasNode(name)) {
                    root.addNode(name, QName.REP_VERSIONSTORAGE, null, false);
                    root.store();
                }
                root = root.getNode(name, 1);
            }
            QName historyNodeName = new QName(QName.NS_DEFAULT_URI, uuid);
            if (root.hasNode(historyNodeName)) {
                // already exists
                return null;
            }

            // create new history node in the persistent state
            InternalVersionHistoryImpl hist = InternalVersionHistoryImpl.create(
                    this, root, new NodeId(UUID.randomUUID()), historyNodeName, node);

            // end update
            stateMgr.update();
            succeeded = true;

            log.info("Created new version history " + hist.getId() + " for " + node + ".");
            return hist;

        } catch (ItemStateException e) {
            throw new RepositoryException(e);
        } finally {
            if (!succeeded) {
                // update operation failed, cancel all modifications
                stateMgr.cancel();
            }
            releaseWriteLock();
        }
    }

    /**
     * Returns the id of the version history associated with the given node
     * or <code>null</code> if that node doesn't have a version history.
     *
     * @param node the node whose version history's id is to be returned.
     * @return the the id of the version history associated with the given node
     *         or <code>null</code> if that node doesn't have a version history.
     * @throws javax.jcr.RepositoryException if an error occurs
     */
    private NodeId getVersionHistoryId(NodeState node)
            throws RepositoryException {
        // build and traverse path
        String uuid = node.getNodeId().getUUID().toString();
        NodeStateEx n = historyRoot;
        for (int i = 0; i < 3; i++) {
            QName name = new QName(QName.NS_DEFAULT_URI, uuid.substring(i * 2, i * 2 + 2));
            if (!n.hasNode(name)) {
                return null;
            }
            n = n.getNode(name, 1);
        }
        QName historyNodeName = new QName(QName.NS_DEFAULT_URI, uuid);
        if (!n.hasNode(historyNodeName)) {
            return null;
        }
        return n.getNode(historyNodeName, 1).getNodeId();
    }

    /**
     * Checks in a node
     *
     * @param node node to checkin
     * @return internal version
     * @throws javax.jcr.RepositoryException if an error occurs
     * @see javax.jcr.Node#checkin()
     */
    protected InternalVersion checkin(InternalVersionHistoryImpl history, NodeImpl node)
            throws RepositoryException {

        aquireReadLock();
        String versionName;
        try {
            // 1. search a predecessor, suitable for generating the new name
            Value[] values = node.getProperty(QName.JCR_PREDECESSORS).getValues();
            InternalVersion best = null;
            for (int i = 0; i < values.length; i++) {
                InternalVersion pred = history.getVersion(NodeId.valueOf(values[i].getString()));
                if (best == null || pred.getSuccessors().length < best.getSuccessors().length) {
                    best = pred;
                }
            }

            // 2. generate version name (assume no namespaces in version names)
            versionName = best.getName().getLocalName();
            int pos = versionName.lastIndexOf('.');
            if (pos > 0) {
                versionName = versionName.substring(0, pos + 1)
                    + (Integer.parseInt(versionName.substring(pos + 1)) + 1);
            } else {
                versionName = String.valueOf(best.getSuccessors().length + 1) + ".0";
            }

            // 3. check for colliding names
            while (history.hasVersion(new QName("", versionName))) {
                versionName += ".1";
            }

            stateMgr.edit();
        } catch (IllegalStateException e) {
            releaseReadLock();
            throw new RepositoryException("Unable to start edit operation.");
        }

        boolean succeeded = false;

        try {
            aquireWriteLock();
            releaseReadLock();

            InternalVersionImpl v = history.checkin(new QName("", versionName), node);
            stateMgr.update();
{'fraction_non_alphanumeric': 0.10150874043680813, 'fraction_numerical': 0.0030345770825319484, 'mean_word_length': 1.9192763568309419, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '263512', 'n_tokens_mistral': 6052, 'n_tokens_neox': 5877, 'n_words': 1823}