All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditor Maven / Gradle / Ivy

There is a newer version: 1.64.0
Show newest version
/*
 * 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.collect.Sets.newHashSet;
import static java.util.Collections.singleton;
import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
import static org.apache.jackrabbit.oak.api.CommitFailedException.CONSTRAINT;
import static org.apache.jackrabbit.oak.api.Type.NAME;
import static org.apache.jackrabbit.oak.api.Type.NAMES;
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 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.plugins.nodetype.TypePredicate;
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;

import com.google.common.base.Predicate;

/**
 * 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 propertyNames;

    /** Type predicate, or {@code null} if there are no type restrictions */
    private final Predicate typePredicate;

    /**
     * Keys to check for uniqueness, or {@code null} for no uniqueness checks.
     */
    private final Set keysToCheckForUniqueness;

    /**
     * Flag to indicate whether the type of this node may have changed.
     */
    private boolean typeChanged;

    /**
     * Matching property value keys from the before state. Lazily initialized.
     */
    private Set beforeKeys;

    /**
     * Matching property value keys from the after state. Lazily initialized.
     */
    private Set afterKeys;

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

        // get property names
        PropertyState names = definition.getProperty(PROPERTY_NAMES);
        if (names.count() == 1) { // OAK-1273: optimize for the common case
            this.propertyNames = singleton(names.getValue(NAME, 0));
        } else {
            this.propertyNames = newHashSet(names.getValue(NAMES));
        }

        // get declaring types, and all their subtypes
        // TODO: should we reindex when type definitions change?
        if (definition.hasProperty(DECLARING_NODE_TYPES)) {
            this.typePredicate = new TypePredicate(
                    root, definition.getNames(DECLARING_NODE_TYPES));
        } else {
            this.typePredicate = null;
        }

        // keep track of modified keys for uniqueness checks
        if (definition.getBoolean(IndexConstants.UNIQUE_PROPERTY_NAME)) {
            this.keysToCheckForUniqueness = newHashSet();
        } else {
            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.typePredicate = parent.typePredicate;
        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;
    }

    /**
     * Adds the encoded values of the given property to the given set.
     * If the given set is uninitialized, i.e. {@code null}, then a new
     * set is created for any values to be added. The set, possibly newly
     * initialized, is returned.
     *
     * @param keys set of encoded values, or {@code null}
     * @param property property whose values are to be added to the set
     * @return set of encoded values, possibly initialized
     */
    private static Set addValueKeys(
            Set keys, PropertyState property) {
        if (property.getType().tag() != PropertyType.BINARY
                && property.count() > 0) {
            if (keys == null) {
                keys = newHashSet();
            }
            keys.addAll(encode(PropertyValues.create(property)));
        }
        return keys;
    }

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

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

    @Override
    public void enter(NodeState before, NodeState after) {
        typeChanged = (typePredicate == null); // disables property name checks
        beforeKeys = null;
        afterKeys = null;
    }

    @Override
    public void leave(NodeState before, NodeState after)
            throws CommitFailedException {
        // apply the type restrictions
        if (typePredicate != null) {
            if (typeChanged) {
                // possible type change, so ignore diff results and
                // just load all matching values from both states
                beforeKeys = getMatchingKeys(before, propertyNames);
                afterKeys = getMatchingKeys(after, propertyNames);
            }
            if (beforeKeys != null && !typePredicate.apply(before)) {
                // the before state doesn't match the type, so clear its values
                beforeKeys = null;
            }
            if (afterKeys != null && !typePredicate.apply(after)) {
                // the after state doesn't match the type, so clear its values
                afterKeys = null;
            }
        }

        // if any changes were detected, update the index accordingly
        if (beforeKeys != null || afterKeys != null) {
            // first make sure that both the before and after sets are non-null
            if (beforeKeys == null
                    || (typePredicate != null && !typePredicate.apply(before))) {
                beforeKeys = newHashSet();
            } else if (afterKeys == null) {
                afterKeys = newHashSet();
            } else {
                // both before and after matches found, remove duplicates
                Set 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(keysToCheckForUniqueness != null).update(
                        index, getPath(), beforeKeys, afterKeys);
                if (keysToCheckForUniqueness != null) {
                    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 (keysToCheckForUniqueness != null
                    && !keysToCheckForUniqueness.isEmpty()) {
                NodeState indexMeta = definition.getNodeState();
                IndexStoreStrategy s = getStrategy(true);
                for (String key : keysToCheckForUniqueness) {
                    if (s.count(indexMeta, singleton(key), 2) > 1) {
                        throw new CommitFailedException(
                                CONSTRAINT, 30,
                                "Uniqueness constraint violated for key " + key);
                    }
                }
            }
        }
    }

    private boolean isTypeProperty(String name) {
        return JCR_PRIMARYTYPE.equals(name) || JCR_MIXINTYPES.equals(name);
    }

    @Override
    public void propertyAdded(PropertyState after) {
        String name = after.getName();
        typeChanged = typeChanged || isTypeProperty(name);
        if (propertyNames.contains(name)) {
            afterKeys = addValueKeys(afterKeys, after);
        }
    }

    @Override
    public void propertyChanged(PropertyState before, PropertyState after) {
        String name = after.getName();
        typeChanged = typeChanged || isTypeProperty(name);
        if (propertyNames.contains(name)) {
            beforeKeys = addValueKeys(beforeKeys, before);
            afterKeys = addValueKeys(afterKeys, after);
        }
    }

    @Override
    public void propertyDeleted(PropertyState before) {
        String name = before.getName();
        typeChanged = typeChanged || isTypeProperty(name);
        if (propertyNames.contains(name)) {
            beforeKeys = 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);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy