blob: 1fb6537a750bbd84d35f3072d8ac5635dfb8f792 [file] [log] [blame]
/*
* 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.migration;
import com.google.common.base.Function;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState;
import org.apache.jackrabbit.oak.plugins.memory.MemoryChildNodeEntry;
import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
import org.apache.jackrabbit.oak.spi.state.AbstractNodeState;
import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;
import org.apache.jackrabbit.oak.spi.state.ReadOnlyBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import static com.google.common.base.Predicates.notNull;
import static org.apache.jackrabbit.oak.plugins.tree.TreeConstants.OAK_CHILD_ORDER;
public abstract class AbstractDecoratedNodeState extends AbstractNodeState {
protected final NodeState delegate;
private final boolean useNativeEquals;
protected AbstractDecoratedNodeState(@NotNull final NodeState delegate, boolean useNativeEquals) {
this.delegate = delegate;
this.useNativeEquals = useNativeEquals;
}
public NodeState getDelegate() {
return delegate;
}
protected boolean hideChild(@NotNull final String name, @NotNull final NodeState delegateChild) {
return false;
}
@NotNull
protected abstract NodeState decorateChild(@NotNull final String name, @NotNull final NodeState delegateChild);
@NotNull
private NodeState decorate(@NotNull final String name, @NotNull final NodeState child) {
return hideChild(name, child) ? EmptyNodeState.MISSING_NODE : decorateChild(name, child);
}
protected boolean hideProperty(@NotNull final String name) {
return false;
}
@NotNull
protected Iterable<PropertyState> getNewPropertyStates() {
return Collections.emptyList();
}
@Nullable
protected abstract PropertyState decorateProperty(@NotNull final PropertyState delegatePropertyState);
@Nullable
private PropertyState decorate(@Nullable final PropertyState property) {
return property == null || hideProperty(property.getName()) ? null : decorateProperty(property);
}
/**
* Convenience method to help implementations that hide nodes set the
* :childOrder (OAK_CHILD_ORDER) property to its correct value.
* <br>
* Intended to be used to implement {@link #decorateProperty(PropertyState)}.
*
* @param nodeState The current node state.
* @param propertyState The property that chould be checked.
* @return The original propertyState, unless the property is called {@code :childOrder}.
*/
protected static PropertyState fixChildOrderPropertyState(NodeState nodeState, PropertyState propertyState) {
if (propertyState != null && OAK_CHILD_ORDER.equals(propertyState.getName())) {
final Collection<String> childNodeNames = new ArrayList<String>();
Iterables.addAll(childNodeNames, nodeState.getChildNodeNames());
final Iterable<String> values = Iterables.filter(
propertyState.getValue(Type.NAMES), Predicates.in(childNodeNames));
return PropertyStates.createProperty(OAK_CHILD_ORDER, values, Type.NAMES);
}
return propertyState;
}
/**
* The AbstractDecoratedNodeState implementation returns a ReadOnlyBuilder, which
* will fail for any mutable operation.
*
* This method can be overridden to return a different NodeBuilder implementation.
*
* @return a NodeBuilder instance corresponding to this NodeState.
*/
@Override
@NotNull
public NodeBuilder builder() {
return new ReadOnlyBuilder(this);
}
@Override
public boolean exists() {
return delegate.exists();
}
@Override
public boolean hasChildNode(@NotNull final String name) {
return getChildNode(name).exists();
}
@Override
@NotNull
public NodeState getChildNode(@NotNull final String name) throws IllegalArgumentException {
return decorate(name, delegate.getChildNode(name));
}
@Override
@NotNull
public Iterable<? extends ChildNodeEntry> getChildNodeEntries() {
final Iterable<ChildNodeEntry> transformed = Iterables.transform(
delegate.getChildNodeEntries(),
new Function<ChildNodeEntry, ChildNodeEntry>() {
@Nullable
@Override
public ChildNodeEntry apply(@Nullable final ChildNodeEntry childNodeEntry) {
if (childNodeEntry != null) {
final String name = childNodeEntry.getName();
final NodeState nodeState = decorate(name, childNodeEntry.getNodeState());
if (nodeState.exists()) {
return new MemoryChildNodeEntry(name, nodeState);
}
}
return null;
}
}
);
return Iterables.filter(transformed, notNull());
}
@Override
@Nullable
public PropertyState getProperty(@NotNull String name) {
PropertyState ps = decorate(delegate.getProperty(name));
if (ps == null) {
for (PropertyState p : getNewPropertyStates()) {
if (name.equals(p.getName())) {
ps = p;
break;
}
}
}
return ps;
}
@Override
@NotNull
public Iterable<? extends PropertyState> getProperties() {
final Iterable<PropertyState> propertyStates = Iterables.transform(
delegate.getProperties(),
new Function<PropertyState, PropertyState>() {
@Override
@Nullable
public PropertyState apply(@Nullable final PropertyState propertyState) {
return decorate(propertyState);
}
}
);
return Iterables.filter(Iterables.concat(propertyStates, getNewPropertyStates()), notNull());
}
/**
* Note that any implementation-specific optimizations of wrapped NodeStates
* will not work if a AbstractDecoratedNodeState is passed into their {@code #equals()}
* method. This implementation will compare the wrapped NodeState, however. So
* optimizations work when calling {@code #equals()} on a ReportingNodeState.
*
* @param other Object to compare with this NodeState.
* @return true if the given object is equal to this NodeState, false otherwise.
*/
@Override
public boolean equals(final Object other) {
if (!(other instanceof NodeState)) {
return false;
}
if (useNativeEquals) {
if (this.getClass() == other.getClass()) {
final AbstractDecoratedNodeState o = (AbstractDecoratedNodeState) other;
return delegate.equals(o.delegate);
}
}
return AbstractDecoratedNodeState.equals(this, (NodeState) other);
}
@Override
public boolean compareAgainstBaseState(final NodeState base, final NodeStateDiff diff) {
NodeStateDiff decoratingDiff = new DecoratingDiff(diff, this);
if (!comparePropertiesAgainstBaseState(this, base, decoratingDiff)) {
return false;
}
Set<String> baseChildNodes = new HashSet<String>();
for (ChildNodeEntry beforeCNE : base.getChildNodeEntries()) {
String name = beforeCNE.getName();
NodeState beforeChild = beforeCNE.getNodeState();
NodeState afterChild = this.getChildNode(name);
if (!afterChild.exists()) {
if (!decoratingDiff.childNodeDeleted(name, beforeChild)) {
return false;
}
} else {
baseChildNodes.add(name);
if (!afterChild.equals(beforeChild)) { // TODO: fastEquals?
if (!decoratingDiff.childNodeChanged(name, beforeChild, afterChild)) {
return false;
}
}
}
}
for (ChildNodeEntry afterChild : this.getChildNodeEntries()) {
String name = afterChild.getName();
if (!baseChildNodes.contains(name)) {
if (!decoratingDiff.childNodeAdded(name, afterChild.getNodeState())) {
return false;
}
}
}
return true;
}
private static class DecoratingDiff implements NodeStateDiff {
private final NodeStateDiff diff;
private AbstractDecoratedNodeState nodeState;
private DecoratingDiff(final NodeStateDiff diff, final AbstractDecoratedNodeState nodeState) {
this.diff = diff;
this.nodeState = nodeState;
}
@Override
public boolean childNodeAdded(final String name, final NodeState after) {
return diff.childNodeAdded(name, nodeState.decorate(name, after));
}
@Override
public boolean childNodeChanged(final String name, final NodeState before, final NodeState after) {
return diff.childNodeChanged(name, before, nodeState.decorate(name, after));
}
@Override
public boolean childNodeDeleted(final String name, final NodeState before) {
return diff.childNodeDeleted(name, before);
}
@Override
public boolean propertyAdded(final PropertyState after) {
return diff.propertyAdded(nodeState.decorate(after));
}
@Override
public boolean propertyChanged(final PropertyState before, final PropertyState after) {
return diff.propertyChanged(nodeState.decorate(before), nodeState.decorate(after));
}
@Override
public boolean propertyDeleted(final PropertyState before) {
return diff.propertyDeleted(nodeState.decorate(before));
}
}
}