Package org.apache.jackrabbit.oak.plugins.index.property

Source Code of org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditor

/*
* 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.plugins.index.property;

import static com.google.common.base.Predicates.in;
import static com.google.common.collect.Iterables.addAll;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Sets.newHashSet;
import static java.util.Collections.singleton;
import static org.apache.jackrabbit.JcrConstants.JCR_ISMIXIN;
import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
import static org.apache.jackrabbit.JcrConstants.JCR_SYSTEM;
import static org.apache.jackrabbit.oak.api.CommitFailedException.CONSTRAINT;
import static org.apache.jackrabbit.oak.commons.PathUtils.concat;
import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.DECLARING_NODE_TYPES;
import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_CONTENT_NODE_NAME;
import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.PROPERTY_NAMES;
import static org.apache.jackrabbit.oak.plugins.index.property.PropertyIndex.encode;
import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.JCR_NODE_TYPES;
import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.OAK_MIXIN_SUBTYPES;
import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.OAK_PRIMARY_SUBTYPES;

import java.util.Set;

import javax.jcr.PropertyType;

import org.apache.jackrabbit.oak.api.CommitFailedException;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
import org.apache.jackrabbit.oak.plugins.index.IndexEditor;
import org.apache.jackrabbit.oak.plugins.index.property.strategy.ContentMirrorStoreStrategy;
import org.apache.jackrabbit.oak.plugins.index.property.strategy.IndexStoreStrategy;
import org.apache.jackrabbit.oak.plugins.index.property.strategy.UniqueEntryStoreStrategy;
import org.apache.jackrabbit.oak.spi.commit.Editor;
import org.apache.jackrabbit.oak.spi.query.PropertyValues;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.jackrabbit.oak.spi.state.NodeState;

/**
* Index editor for keeping a property index up to date.
*
* @see PropertyIndex
* @see PropertyIndexLookup
*/
class PropertyIndexEditor implements IndexEditor {

    /** Index storage strategy */
    private static final IndexStoreStrategy MIRROR =
            new ContentMirrorStoreStrategy();

    /** Index storage strategy */
    private static final IndexStoreStrategy UNIQUE =
            new UniqueEntryStoreStrategy();

    /** Parent editor, or {@code null} if this is the root editor. */
    private final PropertyIndexEditor parent;

    /** Name of this node, or {@code null} for the root node. */
    private final String name;

    /** Path of this editor, built lazily in {@link #getPath()}. */
    private String path;

    /** Index definition node builder */
    private final NodeBuilder definition;

    private final Set<String> propertyNames;

    private final Set<String> primaryTypes;

    private final Set<String> mixinTypes;
   
    private final boolean unique;

    private final Set<String> keysToCheckForUniqueness;

    /**
     * Flag to indicate whether individual property changes should
     * be tracked for this node.
     */
    private boolean trackChanges;

    /**
     * Matching property value keys from the before state,
     * or {@code null} if this node is not indexed.
     */
    private Set<String> beforeKeys;

    /**
     * Matching property value keys from the after state,
     * or {@code null} if this node is not indexed.
     */
    private Set<String> afterKeys;

    public PropertyIndexEditor(NodeBuilder definition, NodeState root) {
        this.parent = null;
        this.name = null;
        this.path = "/";
        this.definition = definition;

        // get property names
        this.propertyNames = newHashSet(definition.getNames(PROPERTY_NAMES));

        // get declaring types, and all their subtypes
        // TODO: should we reindex when type definitions change?
        if (definition.hasProperty(DECLARING_NODE_TYPES)) {
            this.primaryTypes = newHashSet();
            this.mixinTypes = newHashSet();
            NodeState types =
                    root.getChildNode(JCR_SYSTEM).getChildNode(JCR_NODE_TYPES);
            for (String name : definition.getNames(DECLARING_NODE_TYPES)) {
                NodeState type = types.getChildNode(name);
                if (type.getBoolean(JCR_ISMIXIN)) {
                    mixinTypes.add(name);
                } else {
                    primaryTypes.add(name);
                }
                addAll(mixinTypes, type.getNames(OAK_MIXIN_SUBTYPES));
                addAll(primaryTypes, type.getNames(OAK_PRIMARY_SUBTYPES));
            }
        } else {
            this.primaryTypes = null;
            this.mixinTypes = null;
        }

        // keep track of modified keys for uniqueness checks
        if (definition.getBoolean(IndexConstants.UNIQUE_PROPERTY_NAME)) {
            unique = true;
            this.keysToCheckForUniqueness = newHashSet();
        } else {
            unique = false;
            this.keysToCheckForUniqueness = null;
        }
    }

    private PropertyIndexEditor(PropertyIndexEditor parent, String name) {
        this.parent = parent;
        this.name = name;
        this.path = null;
        this.definition = parent.definition;
        this.propertyNames = parent.propertyNames;
        this.primaryTypes = parent.primaryTypes;
        this.mixinTypes = parent.mixinTypes;
        this.unique = parent.unique;
        this.keysToCheckForUniqueness = parent.keysToCheckForUniqueness;
    }

    /**
     * Returns the path of this node, building it lazily when first requested.
     */
    private String getPath() {
        if (path == null) {
            path = concat(parent.getPath(), name);
        }
        return path;
    }

    private boolean isOfMatchingType(NodeState state) {
        // no type limitations
        return primaryTypes == null
                || primaryTypes.contains(state.getName(JCR_PRIMARYTYPE))
                || any(state.getNames(JCR_MIXINTYPES), in(mixinTypes));
    }

    private static void addValueKeys(Set<String> keys, PropertyState property) {
        if (property.getType().tag() != PropertyType.BINARY) {
            keys.addAll(encode(PropertyValues.create(property)));
        }
    }

    private static void addMatchingKeys(
            Set<String> keys, NodeState state, Iterable<String> propertyNames) {
        for (String propertyName : propertyNames) {
            PropertyState property = state.getProperty(propertyName);
            if (property != null) {
                addValueKeys(keys, property);
            }
        }
    }

    @Override
    public void enter(NodeState before, NodeState after)
            throws CommitFailedException {
        boolean beforeMatches = before.exists() && isOfMatchingType(before);
        boolean afterMatches  = after.exists()  && isOfMatchingType(after);

        if (beforeMatches || afterMatches) {
            beforeKeys = newHashSet();
            afterKeys = newHashSet();
        }
        if (beforeMatches && afterMatches) {
            trackChanges = true;
        } else if (beforeMatches) {
            // all matching values should be removed from the index
            addMatchingKeys(beforeKeys, before, propertyNames);
        } else if (afterMatches) {
            // all matching values should be added to the index
            addMatchingKeys(afterKeys, after, propertyNames);
        }
    }

    private static IndexStoreStrategy getStrategy(boolean unique) {
        return unique ? UNIQUE : MIRROR;
    }

    @Override
    public void leave(NodeState before, NodeState after)
            throws CommitFailedException {
        if (beforeKeys != null) {
            Set<String> sharedKeys = newHashSet(beforeKeys);
            sharedKeys.retainAll(afterKeys);

            beforeKeys.removeAll(sharedKeys);
            afterKeys.removeAll(sharedKeys);

            if (!beforeKeys.isEmpty() || !afterKeys.isEmpty()) {
                NodeBuilder index = definition.child(INDEX_CONTENT_NODE_NAME);

                getStrategy(unique).update(index, getPath(), beforeKeys, afterKeys);
                if (unique) {
                    keysToCheckForUniqueness.addAll(afterKeys);
                }
            }
        }

        if (parent == null) {
            // make sure that the index node exist, even with no content
            definition.child(INDEX_CONTENT_NODE_NAME);

            // check uniqueness constraints when leaving the root
            if (unique && !keysToCheckForUniqueness.isEmpty()) {
                NodeState indexMeta = definition.getNodeState();
                IndexStoreStrategy s = getStrategy(unique);
                for (String key : keysToCheckForUniqueness) {
                    if (s.count(indexMeta, singleton(key), 2) > 1) {
                        throw new CommitFailedException(
                                CONSTRAINT, 30,
                                "Uniqueness constraint violated for key " + key);
                    }
                }
            }
        }
    }

    @Override
    public void propertyAdded(PropertyState after) {
        if (trackChanges && propertyNames.contains(after.getName())) {
            addValueKeys(afterKeys, after);
        }
    }

    @Override
    public void propertyChanged(PropertyState before, PropertyState after) {
        if (trackChanges && propertyNames.contains(after.getName())) {
            addValueKeys(beforeKeys, before);
            addValueKeys(afterKeys, after);
        }
    }

    @Override
    public void propertyDeleted(PropertyState before) {
        if (trackChanges && propertyNames.contains(before.getName())) {
            addValueKeys(beforeKeys, before);
        }
    }

    @Override
    public Editor childNodeAdded(String name, NodeState after) {
        return new PropertyIndexEditor(this, name);
    }

    @Override
    public Editor childNodeChanged(
            String name, NodeState before, NodeState after) {
        return new PropertyIndexEditor(this, name);
    }

    @Override
    public Editor childNodeDeleted(String name, NodeState before) {
        return new PropertyIndexEditor(this, name);
    }

}
TOP

Related Classes of org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditor

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.