| /* |
| * 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()); |
| } |
| } |
| } |