Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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);
}
}