blob: 2e618af6cfb628e2b97131560f0369005874ffea [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;
import org.apache.jackrabbit.oak.commons.json.JsopBuilder;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.json.BlobSerializer;
import org.apache.jackrabbit.oak.json.JsonSerializer;
import org.apache.jackrabbit.oak.plugins.document.bundlor.BundlingHandler;
import org.apache.jackrabbit.oak.plugins.document.bundlor.DocumentBundlor;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;
import org.jetbrains.annotations.NotNull;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.MISSING_NODE;
import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty;
/**
* Implementation of a {@link NodeStateDiff}, which translates the diffs into
* {@link UpdateOp}s of a commit.
*/
class CommitDiff implements NodeStateDiff {
private final CommitBuilder commit;
private final JsopBuilder builder;
private final BlobSerializer blobs;
private final BundlingHandler bundlingHandler;
CommitDiff(@NotNull BundlingHandler bundlingHandler,
@NotNull CommitBuilder commitBuilder,
@NotNull BlobSerializer blobs) {
this(checkNotNull(commitBuilder),
checkNotNull(bundlingHandler),
new JsopBuilder(), checkNotNull(blobs));
}
private CommitDiff(CommitBuilder commitBuilder,
BundlingHandler bundlingHandler,
JsopBuilder builder,
BlobSerializer blobs) {
this.commit = commitBuilder;
this.bundlingHandler = bundlingHandler;
this.builder = builder;
this.blobs = blobs;
performBundlingRelatedOperations();
}
@Override
public boolean propertyAdded(PropertyState after) {
setProperty(after);
return true;
}
@Override
public boolean propertyChanged(PropertyState before, PropertyState after) {
setProperty(after);
return true;
}
@Override
public boolean propertyDeleted(PropertyState before) {
commit.updateProperty(bundlingHandler.getRootBundlePath(), bundlingHandler.getPropertyPath(before.getName()), null);
return true;
}
@Override
public boolean childNodeAdded(String name, NodeState after) {
BundlingHandler child = bundlingHandler.childAdded(name, after);
if (child.isBundlingRoot()) {
commit.addNode(child.getRootBundlePath());
}
setOrTouchChildrenFlag(child);
return after.compareAgainstBaseState(EMPTY_NODE,
new CommitDiff(commit, child, builder, blobs));
}
@Override
public boolean childNodeChanged(String name,
NodeState before,
NodeState after) {
//TODO [bundling] Handle change of primaryType. Current approach would work
//but if bundling was enabled for previous nodetype its "side effect"
//would still impact even though new nodetype does not have bundling enabled
BundlingHandler child = bundlingHandler.childChanged(name, before, after);
return after.compareAgainstBaseState(before,
new CommitDiff(commit, child, builder, blobs));
}
@Override
public boolean childNodeDeleted(String name, NodeState before) {
BundlingHandler child = bundlingHandler.childDeleted(name, before);
if (child.isBundlingRoot()) {
commit.removeNode(child.getRootBundlePath(), before);
}
return MISSING_NODE.compareAgainstBaseState(before,
new CommitDiff(commit, child, builder, blobs));
}
/**
* The number of changes recorded by this commit diff. A change is defined
* as a set of updates on a document. This also includes updates for a new
* document.
*
* @return the number of changes.
*/
int getNumChanges() {
return commit.getNumOperations();
}
//----------------------------< internal >----------------------------------
private void performBundlingRelatedOperations() {
setMetaProperties();
informCommitAboutBundledNodes();
removeRemovedProps();
}
private void setMetaProperties() {
for (PropertyState ps : bundlingHandler.getMetaProps()){
setProperty(ps);
}
}
private void informCommitAboutBundledNodes() {
if (bundlingHandler.isBundledNode()){
commit.addBundledNode(bundlingHandler.getNodeFullPath(), bundlingHandler.getRootBundlePath());
}
}
private void removeRemovedProps() {
for (String propName : bundlingHandler.getRemovedProps()){
commit.updateProperty(bundlingHandler.getRootBundlePath(),
bundlingHandler.getPropertyPath(propName), null);
}
}
private void setOrTouchChildrenFlag(BundlingHandler child) {
//Add hasChildren marker for bundling case
String propName = null;
if (child.isBundledNode()){
//1. Child is a bundled node. In that case current node would be part
// same NodeDocument in which the child would be saved
propName = DocumentBundlor.META_PROP_BUNDLED_CHILD;
} else if (bundlingHandler.isBundledNode()){
//2. Child is a non bundled node but current node was bundled. This would
// be the case where child node is not covered by bundling pattern. In
// that case also add marker to current node
// For case when current node is bundled but is bundling root
// this info is already captured in _hasChildren flag
propName = DocumentBundlor.META_PROP_NON_BUNDLED_CHILD;
}
//Retouch the property if already present to enable
//hierarchy conflict detection
if (propName != null){
setProperty(createProperty(propName, Boolean.TRUE));
}
}
private void setProperty(PropertyState property) {
builder.resetWriter();
JsonSerializer serializer = new JsonSerializer(builder, blobs);
serializer.serialize(property);
commit.updateProperty(bundlingHandler.getRootBundlePath(), bundlingHandler.getPropertyPath(property.getName()),
serializer.toString());
if ((property.getType() == Type.BINARY)
|| (property.getType() == Type.BINARIES)) {
this.commit.markNodeHavingBinary(bundlingHandler.getRootBundlePath());
}
}
}