Package org.apache.jackrabbit.oak.kernel

Source Code of org.apache.jackrabbit.oak.kernel.KernelNodeStoreBranch$Merged

/*
* 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.oak.kernel;

import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.jackrabbit.oak.commons.PathUtils.elements;
import static org.apache.jackrabbit.oak.commons.PathUtils.getName;
import static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath;

import java.util.concurrent.locks.Lock;

import javax.annotation.Nonnull;

import org.apache.jackrabbit.mk.api.MicroKernel;
import org.apache.jackrabbit.mk.api.MicroKernelException;
import org.apache.jackrabbit.oak.api.CommitFailedException;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.spi.commit.CommitHook;
import org.apache.jackrabbit.oak.spi.commit.PostCommitHook;
import org.apache.jackrabbit.oak.spi.state.ConflictAnnotatingRebaseDiff;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.apache.jackrabbit.oak.spi.state.NodeStoreBranch;

/**
* {@code NodeStoreBranch} based on {@link MicroKernel} branching and merging.
* This implementation keeps changes in memory up to a certain limit and writes
* them back to the Microkernel branch when the limit is exceeded.
*/
class KernelNodeStoreBranch implements NodeStoreBranch {

    /** The underlying store to which this branch belongs */
    private final KernelNodeStore store;

    /** Lock for coordinating concurrent merge operations */
    private final Lock mergeLock;

    /**
     * State of the this branch. Either {@link Unmodified}, {@link InMemory}, {@link Persisted}
     * or {@link Merged}.
     * @see BranchState
     */
    private BranchState branchState;

    public KernelNodeStoreBranch(KernelNodeStore kernelNodeStore, Lock mergeLock,
            KernelNodeState base) {

        this.store = checkNotNull(kernelNodeStore);
        this.mergeLock = checkNotNull(mergeLock);
        branchState = new Unmodified(checkNotNull(base));
    }

    @Override
    public String toString() {
        return branchState.toString();
    }

    @Nonnull
    @Override
    public NodeState getBase() {
        return branchState.getBase();
    }

    @Nonnull
    @Override
    public NodeState getHead() {
        return branchState.getHead();
    }

    @Override
    public void setRoot(NodeState newRoot) {
        branchState.setRoot(checkNotNull(newRoot));
    }

    @Override
    public boolean move(String source, String target) {
        if (PathUtils.isAncestor(checkNotNull(source), checkNotNull(target))) {
            return false;
        } else if (source.equals(target)) {
            return true;
        }

        if (!getNode(source).exists()) {
            // source does not exist
            return false;
        }
        NodeState destParent = getNode(getParentPath(target));
        if (!destParent.exists()) {
            // parent of destination does not exist
            return false;
        }
        if (destParent.getChildNode(getName(target)).exists()) {
            // destination exists already
            return false;
        }
        branchState.persist().commit(">\"" + source + "\":\"" + target + '"');
        return true;
    }

    @Override
    public boolean copy(String source, String target) {
        if (!getNode(checkNotNull(source)).exists()) {
            // source does not exist
            return false;
        }
        NodeState destParent = getNode(getParentPath(checkNotNull(target)));
        if (!destParent.exists()) {
            // parent of destination does not exist
            return false;
        }
        if (destParent.getChildNode(getName(target)).exists()) {
            // destination exists already
            return false;
        }
        branchState.persist().commit("*\"" + source + "\":\"" + target + '"');
        return true;
    }

    @Nonnull
    @Override
    public NodeState merge(@Nonnull CommitHook hook, PostCommitHook committed) throws CommitFailedException {
        return branchState.merge(checkNotNull(hook), checkNotNull(committed));
    }

    @Override
    public void rebase() {
        branchState.rebase();
    }

    private NodeState getNode(String path) {
        NodeState node = getHead();
        for (String name : elements(path)) {
            node = node.getChildNode(name);
        }
        return node;
    }

    /**
     * Sub classes of this class represent a state a branch can be in. See the individual
     * sub classes for permissible state transitions.
     */
    private abstract class BranchState {
        /** Root state of the base revision of this branch */
        protected KernelNodeState base;

        protected BranchState(KernelNodeState base) {
            this.base = base;
        }

        /**
         * Persist this branch to an underlying branch in the {@code MicroKernel}.
         */
        Persisted persist() {
            branchState = new Persisted(base, getHead());
            return (Persisted) branchState;
        }

        KernelNodeState getBase(){
            return base;
        }

        @Nonnull
        abstract NodeState getHead();

        abstract void setRoot(NodeState root);

        abstract void rebase();

        @Nonnull
        abstract NodeState merge(@Nonnull CommitHook hook, PostCommitHook committed) throws CommitFailedException;
    }

    /**
     * Instances of this class represent a branch whose base and head are the same.
     * <p>
     * Transitions to:
     * <ul>
     *     <li>{@link InMemory} on {@link #setRoot(NodeState)} if the new root differs
     *         from the current base</li>.
     *     <li>{@link Merged} on {@link #merge(CommitHook, PostCommitHook)}</li>
     * </ul>
     */
    private class Unmodified extends BranchState {
        Unmodified(KernelNodeState base) {
            super(base);
        }

        @Override
        public String toString() {
            return "Unmodified[" + base + ']';
        }

        @Override
        NodeState getHead() {
            return base;
        }

        @Override
        void setRoot(NodeState root) {
            if (!base.equals(root)) {
                branchState = new InMemory(base, root);
            }
        }

        @Override
        void rebase() {
            base = store.getRoot();
        }

        @Override
        NodeState merge(CommitHook hook, PostCommitHook committed) throws CommitFailedException {
            branchState = new Merged(base);
            return base;
        }
    }

    /**
     * Instances of this class represent a branch whose base and head differ.
     * All changes are kept in memory.
     * <p>
     * Transitions to:
     * <ul>
     *     <li>{@link Unmodified} on {@link #setRoot(NodeState)} if the new root is the same
     *         as the base of this branch or
     *     <li>{@link Persisted} otherwise.
     *     <li>{@link Merged} on {@link #merge(CommitHook, PostCommitHook)}</li>
     * </ul>
     */
    private class InMemory extends BranchState {
        /** Root state of the transient head. */
        private NodeState head;

        @Override
        public String toString() {
            return "InMemory[" + base + ", " + head + ']';
        }

        InMemory(KernelNodeState base, NodeState head) {
            super(base);
            this.head = head;
        }

        @Override
        NodeState getHead() {
            return head;
        }

        @Override
        void setRoot(NodeState root) {
            if (base.equals(root)) {
                branchState = new Unmodified(base);
            } else if (!head.equals(root)) {
                head = root;
                persist();
            }
        }

        @Override
        void rebase() {
            KernelNodeState root = store.getRoot();
            NodeBuilder builder = root.builder();
            head.compareAgainstBaseState(base, new ConflictAnnotatingRebaseDiff(builder));
            head = builder.getNodeState();
            base = root;
        }

        @Override
        NodeState merge(CommitHook hook, PostCommitHook committed) throws CommitFailedException {
            mergeLock.lock();
            try {
                rebase();
                NodeState toCommit = checkNotNull(hook).processCommit(base, head);
                JsopDiff diff = new JsopDiff(store);
                toCommit.compareAgainstBaseState(base, diff);
                NodeState newHead = store.commit(diff.toString(), base);
                committed.contentChanged(base, newHead);
                branchState = new Merged(base);
                return newHead;
            } catch (MicroKernelException e) {
                throw new CommitFailedException(
                        "Kernel", 1,
                        "Failed to merge changes to the underlying MicroKernel", e);
            } finally {
                mergeLock.unlock();
            }
        }
    }

    /**
     * Instances of this class represent a branch whose base and head differ.
     * All changes are persisted to an underlying branch in the {@code MicroKernel}.
     * <p>
     * Transitions to:
     * <ul>
     *     <li>{@link Unmodified} on {@link #setRoot(NodeState)} if the new root is the same
     *         as the base of this branch.
     *     <li>{@link Merged} on {@link #merge(CommitHook, PostCommitHook)}</li>
     * </ul>
     */
    private class Persisted extends BranchState {
        /** Root state of the transient head, top of persisted branch. */
        private KernelNodeState head;

        @Override
        public String toString() {
            return "Persisted[" + base + ", " + head + ']';
        }

        Persisted(KernelNodeState base, NodeState head) {
            super(base);
            this.head = store.branch(base);
            persistTransientHead(head);
        }

        void commit(String jsop) {
            if (!jsop.isEmpty()) {
                head = store.commit(jsop, head);
            }
        }

        @Override
        NodeState getHead() {
            return head;
        }

        @Override
        void setRoot(NodeState root) {
            if (base.equals(root)) {
                branchState = new Unmodified(base);
            } else if (!head.equals(root)) {
                persistTransientHead(root);
            }
        }

        @Override
        void rebase() {
            KernelNodeState root = store.getRoot();
            if (head.equals(root)) {
                // Nothing was written to this branch: set new base revision
                head = root;
                base = root;
            } else {
                // perform rebase in kernel
                head = store.rebase(head, root);
                base = root;
            }
        }

        @Override
        NodeState merge(CommitHook hook, PostCommitHook committed) throws CommitFailedException {
            mergeLock.lock();
            try {
                rebase();
                NodeState toCommit = checkNotNull(hook).processCommit(base, head);
                if (toCommit.equals(base)) {
                    committed.contentChanged(base, base);
                    branchState = new Merged(base);
                    return base;
                } else {
                    JsopDiff diff = new JsopDiff(store);
                    toCommit.compareAgainstBaseState(head, diff);
                    commit(diff.toString());
                    NodeState newRoot = store.merge(head);
                    committed.contentChanged(base, newRoot);
                    branchState = new Merged(base);
                    return newRoot;
                }
            } catch (MicroKernelException e) {
                throw new CommitFailedException(
                        "Kernel", 1,
                        "Failed to merge changes to the underlying MicroKernel", e);
            } finally {
                mergeLock.unlock();
            }
        }

        private void persistTransientHead(NodeState newHead) {
            if (!newHead.equals(head)) {
                JsopDiff diff = new JsopDiff(store);
                newHead.compareAgainstBaseState(head, diff);
                head = store.commit(diff.toString(), head);
            }
        }
    }

    /**
     * Instances of this class represent a branch that has already been merged.
     * All methods throw an {@code IllegalStateException}.
     * <p>
     * Transitions to: none.
     */
    private class Merged extends BranchState {
        protected Merged(KernelNodeState base) {
            super(base);
        }

        @Override
        public String toString() {
            return "Merged[" + base + ']';
        }

        @Override
        NodeState getHead() {
            throw new IllegalStateException("Branch has already been merged");
        }

        @Override
        void setRoot(NodeState root) {
            throw new IllegalStateException("Branch has already been merged");
        }

        @Override
        void rebase() {
            throw new IllegalStateException("Branch has already been merged");
        }

        @Override
        NodeState merge(CommitHook hook, PostCommitHook committed) throws CommitFailedException {
            throw new IllegalStateException("Branch has already been merged");
        }
    }
}
TOP

Related Classes of org.apache.jackrabbit.oak.kernel.KernelNodeStoreBranch$Merged

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.