| /* |
| * 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.bundlor; |
| |
| import java.util.Set; |
| |
| import com.google.common.collect.Sets; |
| import org.apache.jackrabbit.oak.api.PropertyState; |
| import org.apache.jackrabbit.oak.commons.PathUtils; |
| import org.apache.jackrabbit.oak.plugins.document.Path; |
| import org.apache.jackrabbit.oak.spi.state.NodeState; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore.META_PROP_NAMES; |
| import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE; |
| import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty; |
| |
| public class BundlingHandler { |
| |
| private final BundledTypesRegistry registry; |
| private final Path path; |
| private final BundlingContext ctx; |
| private final NodeState nodeState; |
| |
| public BundlingHandler(BundledTypesRegistry registry) { |
| this(checkNotNull(registry), BundlingContext.NULL, Path.ROOT, EMPTY_NODE); |
| } |
| |
| private BundlingHandler(BundledTypesRegistry registry, BundlingContext ctx, Path path, NodeState nodeState) { |
| this.registry = registry; |
| this.path = path; |
| this.ctx = ctx; |
| this.nodeState = nodeState; |
| } |
| |
| /** |
| * Returns property path. For non bundling case this is the actual property name |
| * while for bundling case this is the relative path from bundling root |
| */ |
| public String getPropertyPath(String propertyName) { |
| return ctx.isBundling() ? ctx.getPropertyPath(propertyName) : propertyName; |
| } |
| |
| /** |
| * Returns true if and only if current node is bundled in another node |
| */ |
| public boolean isBundledNode(){ |
| return ctx.matcher.depth() > 0; |
| } |
| |
| /** |
| * Returns absolute path of the current node |
| */ |
| public Path getNodeFullPath() { |
| return path; |
| } |
| |
| public NodeState getNodeState() { |
| return nodeState; |
| } |
| |
| public Set<PropertyState> getMetaProps() { |
| return ctx.metaProps; |
| } |
| |
| /** |
| * Returns name of properties which needs to be removed or marked as deleted |
| */ |
| public Set<String> getRemovedProps(){ |
| return ctx.removedProps; |
| } |
| |
| public Path getRootBundlePath() { |
| return ctx.isBundling() ? ctx.bundlingPath : path; |
| } |
| |
| public BundlingHandler childAdded(String name, NodeState state){ |
| Path childPath = childPath(name); |
| BundlingContext childContext; |
| Matcher childMatcher = ctx.matcher.next(name); |
| if (childMatcher.isMatch()) { |
| childContext = createChildContext(childMatcher); |
| childContext.addMetaProp(createProperty(DocumentBundlor.META_PROP_BUNDLING_PATH, childMatcher.getMatchedPath())); |
| } else { |
| DocumentBundlor bundlor = registry.getBundlor(state); |
| if (bundlor != null){ |
| PropertyState bundlorConfig = bundlor.asPropertyState(); |
| childContext = new BundlingContext(childPath, bundlor.createMatcher()); |
| childContext.addMetaProp(bundlorConfig); |
| } else { |
| childContext = BundlingContext.NULL; |
| } |
| } |
| return new BundlingHandler(registry, childContext, childPath, state); |
| } |
| |
| public BundlingHandler childDeleted(String name, NodeState state){ |
| Path childPath = childPath(name); |
| BundlingContext childContext; |
| Matcher childMatcher = ctx.matcher.next(name); |
| if (childMatcher.isMatch()) { |
| childContext = createChildContext(childMatcher); |
| removeDeletedChildProperties(state, childContext); |
| } else { |
| childContext = getBundlorContext(childPath, state); |
| if (childContext.isBundling()){ |
| removeBundlingMetaProps(state, childContext); |
| } |
| } |
| return new BundlingHandler(registry, childContext, childPath, state); |
| } |
| |
| public BundlingHandler childChanged(String name, NodeState before, NodeState after){ |
| Path childPath = childPath(name); |
| BundlingContext childContext; |
| Matcher childMatcher = ctx.matcher.next(name); |
| if (childMatcher.isMatch()) { |
| childContext = createChildContext(childMatcher); |
| } else { |
| //Use the before state for looking up bundlor config |
| //as after state may have been recreated all together |
| //and bundlor config might have got lost |
| childContext = getBundlorContext(childPath, before); |
| } |
| |
| return new BundlingHandler(registry, childContext, childPath, after); |
| } |
| |
| public boolean isBundlingRoot() { |
| if (ctx.isBundling()){ |
| return ctx.bundlingPath.equals(path); |
| } |
| return true; |
| } |
| |
| @Override |
| public String toString() { |
| String result = path.toString(); |
| if (isBundledNode()){ |
| result = path + "( Bundling root - " + getRootBundlePath() + ")"; |
| } |
| return result; |
| } |
| |
| private Path childPath(String name){ |
| return new Path(path, name); |
| } |
| |
| private BundlingContext createChildContext(Matcher childMatcher) { |
| return ctx.child(childMatcher); |
| } |
| |
| private static BundlingContext getBundlorContext(Path path, NodeState state) { |
| BundlingContext result = BundlingContext.NULL; |
| PropertyState bundlorConfig = state.getProperty(DocumentBundlor.META_PROP_PATTERN); |
| if (bundlorConfig != null){ |
| DocumentBundlor bundlor = DocumentBundlor.from(bundlorConfig); |
| result = new BundlingContext(path, bundlor.createMatcher()); |
| } |
| return result; |
| } |
| |
| private static void removeDeletedChildProperties(NodeState state, BundlingContext childContext) { |
| removeBundlingMetaProps(state, childContext); |
| for (PropertyState ps : state.getProperties()){ |
| childContext.removeProperty(ps.getName()); |
| } |
| } |
| |
| private static void removeBundlingMetaProps(NodeState state, BundlingContext childContext) { |
| //Explicitly remove meta prop related to bundling as it would not |
| //be part of normal listing of properties and hence would not be deleted |
| //as part of diff |
| for (String name : META_PROP_NAMES) { |
| if (state.hasProperty(name)) { |
| childContext.removeProperty(name); |
| } |
| } |
| } |
| |
| private static class BundlingContext { |
| static final BundlingContext NULL = new BundlingContext(Path.ROOT, Matcher.NON_MATCHING); |
| final Path bundlingPath; |
| final Matcher matcher; |
| final Set<PropertyState> metaProps = Sets.newHashSet(); |
| final Set<String> removedProps = Sets.newHashSet(); |
| |
| public BundlingContext(Path bundlingPath, Matcher matcher) { |
| this.bundlingPath = bundlingPath; |
| this.matcher = matcher; |
| } |
| |
| public BundlingContext child(Matcher matcher){ |
| return new BundlingContext(bundlingPath, matcher); |
| } |
| |
| public boolean isBundling(){ |
| return matcher.isMatch(); |
| } |
| |
| public String getPropertyPath(String propertyName) { |
| return PathUtils.concat(matcher.getMatchedPath(), propertyName); |
| } |
| |
| public void addMetaProp(PropertyState state){ |
| metaProps.add(state); |
| } |
| |
| public void removeProperty(String name){ |
| removedProps.add(name); |
| } |
| } |
| } |