Package org.apache.jackrabbit.oak.kernel

Source Code of org.apache.jackrabbit.oak.kernel.KernelNodeState

/*
* 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.kernel.CoreValueMapper.fromJsopReader;
import static org.apache.jackrabbit.oak.kernel.CoreValueMapper.listFromJsopReader;

import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;

import javax.annotation.Nonnull;

import org.apache.jackrabbit.mk.api.MicroKernel;
import org.apache.jackrabbit.mk.api.MicroKernelException;
import org.apache.jackrabbit.mk.json.JsopReader;
import org.apache.jackrabbit.mk.json.JsopTokenizer;
import org.apache.jackrabbit.oak.api.CoreValue;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.plugins.memory.MultiPropertyState;
import org.apache.jackrabbit.oak.plugins.memory.SinglePropertyState;
import org.apache.jackrabbit.oak.spi.state.AbstractChildNodeEntry;
import org.apache.jackrabbit.oak.spi.state.AbstractNodeState;
import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;

import com.google.common.base.Function;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

/**
* Basic {@link NodeState} implementation based on the {@link MicroKernel}
* interface. This class makes an attempt to load data lazily.
*/
public final class KernelNodeState extends AbstractNodeState {

    /**
     * Maximum number of child nodes kept in memory.
     */
    static final int MAX_CHILD_NODE_NAMES = 1000;

    private final MicroKernel kernel;

    private final String path;

    private final String revision;

    private Map<String, PropertyState> properties;

    private long childNodeCount = -1;

    private String hash;

    private Map<String, String> childPaths;

    private final LoadingCache<String, KernelNodeState> cache;

    /**
     * Create a new instance of this class representing the node at the
     * given {@code path} and {@code revision}. It is an error if the
     * underlying Microkernel does not contain such a node.
     *
     * @param kernel the underlying MicroKernel
     * @param path the path of this KernelNodeState
     * @param revision the revision of the node to read from the kernel.
     * @param cache the KernelNodeState cache
     */
    public KernelNodeState(
            MicroKernel kernel, String path, String revision,
            LoadingCache<String, KernelNodeState> cache) {
        this.kernel = checkNotNull(kernel);
        this.path = checkNotNull(path);
        this.revision = checkNotNull(revision);
        this.cache = checkNotNull(cache);
    }

    private synchronized void init() {
        if (properties == null) {
            String json = kernel.getNodes(
                    path, revision, 0, 0, MAX_CHILD_NODE_NAMES,
                    "{\"properties\":[\"*\",\":hash\"]}");

            JsopReader reader = new JsopTokenizer(json);
            reader.read('{');
            properties = new LinkedHashMap<String, PropertyState>();
            childPaths = new LinkedHashMap<String, String>();
            do {
                String name = reader.readString();
                reader.read(':');
                if (":childNodeCount".equals(name)) {
                    childNodeCount =
                            Long.valueOf(reader.read(JsopReader.NUMBER));
                } else if (":hash".equals(name)) {
                    hash = reader.read(JsopReader.STRING);
                } else if (reader.matches('{')) {
                    reader.read('}');
                    String childPath = path + '/' + name;
                    if ("/".equals(path)) {
                        childPath = '/' + name;
                    }
                    childPaths.put(name, childPath);
                } else if (reader.matches('[')) {
                    List<CoreValue> values = listFromJsopReader(reader, kernel);
                    properties.put(name, new MultiPropertyState(name, values));
                } else {
                    CoreValue cv = fromJsopReader(reader, kernel);
                    properties.put(name, new SinglePropertyState(name, cv));
                }
            } while (reader.matches(','));
            reader.read('}');
            reader.read(JsopReader.END);
            // optimize for empty childNodes
            if (childPaths.isEmpty()) {
                childPaths = Collections.emptyMap();
            }
        }
    }

    @Override
    public long getPropertyCount() {
        init();
        return properties.size();
    }

    @Override
    public PropertyState getProperty(String name) {
        init();
        return properties.get(name);
    }

    @Override
    public Iterable<? extends PropertyState> getProperties() {
        init();
        return properties.values();
    }

    @Override
    public long getChildNodeCount() {
        init();
        return childNodeCount;
    }

    @Override
    public NodeState getChildNode(String name) {
        init();
        String childPath = childPaths.get(name);
        if (childPath == null && childNodeCount > MAX_CHILD_NODE_NAMES) {
            String path = getChildPath(name);
            if (kernel.nodeExists(path, revision)) {
                childPath = path;
            }
        }
        if (childPath == null) {
            return null;
        }
        try {
            return cache.get(revision + childPath);
        } catch (ExecutionException e) {
            throw new MicroKernelException(e);
        }
    }

    @Override
    public Iterable<? extends ChildNodeEntry> getChildNodeEntries() {
        init();
        Iterable<ChildNodeEntry> iterable = iterable(childPaths.entrySet());
        if (childNodeCount > childPaths.size()) {
            List<Iterable<ChildNodeEntry>> iterables = Lists.newArrayList();
            iterables.add(iterable);
            long offset = childPaths.size();
            while (offset < childNodeCount) {
                iterables.add(getChildNodeEntries(offset, MAX_CHILD_NODE_NAMES));
                offset += MAX_CHILD_NODE_NAMES;
            }
            iterable = Iterables.concat(iterables);
        }
        return iterable;
    }

    /**
     * Optimised comparison method that can avoid traversing all properties
     * and child nodes if both this and the given base node state come from
     * the same MicroKernel and either have the same content hash (when
     * available) or are located at the same path in different revisions.
     *
     * @see <a href="https://issues.apache.org/jira/browse/OAK-175">OAK-175</a>
     */
    @Override
    public void compareAgainstBaseState(NodeState base, NodeStateDiff diff) {
        if (this == base) {
            return; // no differences
        } else if (base instanceof KernelNodeState) {
            KernelNodeState kbase = (KernelNodeState) base;
            if (kernel.equals(kbase.kernel)) {
                if (revision.equals(kbase.revision) && path.equals(kbase.path)) {
                    return; // no differences
                } else {
                    init();
                    kbase.init();
                    if (hash != null && hash.equals(kbase.hash)) {
                        return; // no differences
                    } else if (path.equals(kbase.path)) {
                        // TODO: Parse the JSON diff returned by the kernel
                        // kernel.diff(kbase.revision, revision, path);
                    }
                }
            }
        }
        // fall back to the generic node state diff algorithm
        super.compareAgainstBaseState(base, diff);
    }

    //------------------------------------------------------------< Object >--

    /**
     * Optimised equality check that can avoid a full tree comparison if
     * both instances come from the same MicroKernel and have either
     * the same revision and path or the same content hash (when available).
     * Otherwise we fall back to the default tree comparison algorithm.
     *
     * @see <a href="https://issues.apache.org/jira/browse/OAK-172">OAK-172</a>
     */
    @Override
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        } else if (object instanceof KernelNodeState) {
            KernelNodeState that = (KernelNodeState) object;
            if (kernel.equals(that.kernel)) {
                if (revision.equals(that.revision) && path.equals(that.path)) {
                    return true;
                } else {
                    this.init();
                    that.init();
                    if (hash != null && that.hash != null) {
                        return hash.equals(that.hash);
                    }
                }
            }
        }
        // fall back to the generic tree equality comparison algorithm
        return super.equals(object);
    }

    //------------------------------------------------------------< internal >---

    @Nonnull
    String getRevision() {
        return revision;
    }

    @Nonnull
    String getPath() {
        return path;
    }

    //------------------------------------------------------------< private >---

    private Iterable<ChildNodeEntry> getChildNodeEntries(
            final long offset, final int count) {
        return new Iterable<ChildNodeEntry>() {
            @Override
            public Iterator<ChildNodeEntry> iterator() {
                List<ChildNodeEntry> entries =
                        Lists.newArrayListWithCapacity(count);
                String json = kernel.getNodes(
                        path, revision, 0, offset, count, null);
                JsopReader reader = new JsopTokenizer(json);
                reader.read('{');
                do {
                    String name = reader.readString();
                    reader.read(':');
                    if (reader.matches('{')) {
                        reader.read('}');
                        String childPath = getChildPath(name);
                        entries.add(new KernelChildNodeEntry(name, childPath));
                    } else if (reader.matches('[')) {
                        while (reader.read() != ']') {
                            // skip
                        }
                    } else {
                        reader.read();
                    }
                } while (reader.matches(','));
                reader.read('}');
                reader.read(JsopReader.END);
                return entries.iterator();
            }
        };
    }

    private String getChildPath(String name) {
        if ("/".equals(path)) {
            return '/' + name;
        } else {
            return path + '/' + name;
        }
    }

    private Iterable<ChildNodeEntry> iterable(
            Iterable<Entry<String, String>> set) {
        return Iterables.transform(
                set,
                new Function<Entry<String, String>, ChildNodeEntry>() {
                    @Override
                    public ChildNodeEntry apply(Entry<String, String> input) {
                        return new KernelChildNodeEntry(input);
                    }
                });
    }

    private class KernelChildNodeEntry extends AbstractChildNodeEntry {

        private final String name;

        private final String path;

        /**
         * Creates a child node entry with the given name and referenced
         * child node state.
         *
         * @param name child node name
         * @param path child node path
         */
        public KernelChildNodeEntry(String name, String path) {
            this.name = checkNotNull(name);
            this.path = checkNotNull(path);
        }

        /**
         * Utility constructor that copies the name and referenced
         * child node state from the given map entry.
         *
         * @param entry map entry
         */
        public KernelChildNodeEntry(Map.Entry<String, String> entry) {
            this(entry.getKey(), entry.getValue());
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public NodeState getNodeState() {
            try {
                return cache.get(revision + path);
            } catch (ExecutionException e) {
                throw new MicroKernelException(e);
            }
        }

    }

}
TOP

Related Classes of org.apache.jackrabbit.oak.kernel.KernelNodeState

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.