blob: c9558901bb3a010fdfd853c9987cc9607f8e5bae [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.document.secondary;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.plugins.document.AbstractDocumentNodeState;
import org.apache.jackrabbit.oak.plugins.document.NodeStateDiffer;
import org.apache.jackrabbit.oak.plugins.document.Path;
import org.apache.jackrabbit.oak.plugins.document.RevisionVector;
import org.apache.jackrabbit.oak.plugins.memory.MemoryChildNodeEntry;
import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeBuilder;
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.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
/**
* NodeState wrapper which wraps another NodeState (mostly SegmentNodeState)
* so as to expose it as an {@link AbstractDocumentNodeState} by extracting
* the meta properties which are stored as hidden properties
*/
public class DelegatingDocumentNodeState extends AbstractDocumentNodeState {
//Hidden props holding DocumentNodeState meta properties
public static final String PROP_REVISION = ":doc-rev";
public static final String PROP_LAST_REV = ":doc-lastRev";
private static final Predicate<PropertyState> NOT_META_PROPS = new Predicate<PropertyState>() {
@Override
public boolean apply(PropertyState input) {
return !input.getName().startsWith(":doc-");
}
};
private final NodeStateDiffer differ;
private final NodeState delegate;
private final RevisionVector rootRevision;
private final boolean fromExternalChange;
private final Path path;
private RevisionVector lastRevision;
/**
* Wraps a given root NodeState as a {@link DelegatingDocumentNodeState} if
* it has required meta properties otherwise just returns the passed NodeState
*
* @param delegate nodeState to wrap
* @return wrapped state or original state
*/
public static NodeState wrapIfPossible(NodeState delegate, NodeStateDiffer differ) {
if (hasMetaProps(delegate)) {
String revVector = getRequiredProp(delegate, PROP_REVISION);
return new DelegatingDocumentNodeState(delegate, Path.ROOT, RevisionVector.fromString(revVector), false, differ);
}
return delegate;
}
public static boolean hasMetaProps(NodeState delegate) {
return delegate.hasProperty(PROP_REVISION);
}
public static AbstractDocumentNodeState wrap(NodeState delegate, NodeStateDiffer differ) {
String revVector = getRequiredProp(delegate, PROP_REVISION);
return new DelegatingDocumentNodeState(delegate, Path.ROOT, RevisionVector.fromString(revVector), false, differ);
}
private DelegatingDocumentNodeState(NodeState delegate, Path path, RevisionVector rootRevision,
boolean fromExternalChange, NodeStateDiffer differ) {
this.differ = differ;
this.delegate = delegate;
this.rootRevision = rootRevision;
this.fromExternalChange = fromExternalChange;
this.path = path;
}
private DelegatingDocumentNodeState(DelegatingDocumentNodeState original,
RevisionVector rootRevision, boolean fromExternalChange) {
this.differ = original.differ;
this.delegate = original.delegate;
this.rootRevision = rootRevision;
this.fromExternalChange = fromExternalChange;
this.path = original.path;
this.lastRevision = original.lastRevision;
}
//~----------------------------------< AbstractDocumentNodeState >
@Override
public Path getPath() {
return path;
}
@Override
public RevisionVector getLastRevision() {
if (lastRevision == null){
this.lastRevision = RevisionVector.fromString(getRequiredProp(PROP_LAST_REV));
}
return lastRevision;
}
@Override
public RevisionVector getRootRevision() {
return rootRevision;
}
@Override
public boolean isFromExternalChange() {
return fromExternalChange;
}
@Override
public AbstractDocumentNodeState withRootRevision(@NotNull RevisionVector root, boolean externalChange) {
if (rootRevision.equals(root) && fromExternalChange == externalChange) {
return this;
} else {
return new DelegatingDocumentNodeState(this, root, externalChange);
}
}
@Override
public boolean hasNoChildren() {
//Passing max as 1 so as to minimize any overhead.
return delegate.getChildNodeCount(1) == 0;
}
@Override
protected NodeStateDiffer getNodeStateDiffer() {
return differ;
}
//~----------------------------------< NodeState >
@Override
public boolean exists() {
return true;
}
@NotNull
@Override
public Iterable<? extends PropertyState> getProperties() {
return Iterables.filter(delegate.getProperties(), NOT_META_PROPS);
}
@Override
public boolean hasChildNode(@NotNull String name) {
return delegate.hasChildNode(name);
}
@NotNull
@Override
public NodeState getChildNode(@NotNull String name) throws IllegalArgumentException {
return decorate(name, delegate.getChildNode(name));
}
@NotNull
@Override
public Iterable<? extends ChildNodeEntry> getChildNodeEntries() {
return Iterables.transform(delegate.getChildNodeEntries(), new Function<ChildNodeEntry, ChildNodeEntry>() {
@Nullable
@Override
public ChildNodeEntry apply(ChildNodeEntry input) {
return new MemoryChildNodeEntry(input.getName(), decorate(input.getName(), input.getNodeState()));
}
});
}
@NotNull
@Override
public NodeBuilder builder() {
checkState(!getPath().isRoot(), "Builder cannot be opened for root " +
"path for state of type [%s]", delegate.getClass());
return new MemoryNodeBuilder(this);
}
//Following method should be overridden as default implementation in AbstractNodeState
//is not optimized
@Override
public PropertyState getProperty(@NotNull String name) {
return delegate.getProperty(name);
}
@Override
public long getPropertyCount() {
return Iterables.size(getProperties());
}
@Override
public boolean hasProperty(@NotNull String name) {
return delegate.hasProperty(name);
}
@Override
public boolean getBoolean(@NotNull String name) {
return delegate.getBoolean(name);
}
@Override
public long getLong(String name) {
return delegate.getLong(name);
}
@Override
public String getString(String name) {
return delegate.getString(name);
}
@NotNull
@Override
public Iterable<String> getStrings(@NotNull String name) {
return delegate.getStrings(name);
}
@Override
public String getName(@NotNull String name) {
return delegate.getName(name);
}
@NotNull
@Override
public Iterable<String> getNames(@NotNull String name) {
return delegate.getNames(name);
}
@Override
public long getChildNodeCount(long max) {
return delegate.getChildNodeCount(max);
}
@Override
public Iterable<String> getChildNodeNames() {
return delegate.getChildNodeNames();
}
//~--------------------------------------------< internal >
private NodeState decorate(String nodeName, NodeState childNode) {
if (childNode.exists()) {
return new DelegatingDocumentNodeState(childNode, new Path(path, nodeName), rootRevision,
fromExternalChange, differ);
}
return childNode;
}
private String getRequiredProp(String name){
return getRequiredProp(delegate, name);
}
private static String getRequiredProp(NodeState state, String name){
return checkNotNull(state.getString(name), "No property [%s] found in [%s]", name, state);
}
}