org.apache.jackrabbit.oak.upgrade.SameNameSiblingsEditor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of oak-upgrade Show documentation
Show all versions of oak-upgrade Show documentation
Tooling for upgrading Jackrabbit repositories to Oak
The newest version!
/*
* 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.upgrade;
import static org.apache.jackrabbit.guava.common.collect.Iterables.filter;
import static org.apache.jackrabbit.guava.common.collect.Iterables.transform;
import static org.apache.jackrabbit.JcrConstants.JCR_SAMENAMESIBLINGS;
import static org.apache.jackrabbit.JcrConstants.JCR_SYSTEM;
import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.JCR_NODE_TYPES;
import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.REP_NAMED_CHILD_NODE_DEFINITIONS;
import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.REP_RESIDUAL_CHILD_NODE_DEFINITIONS;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.jackrabbit.oak.api.CommitFailedException;
import org.apache.jackrabbit.oak.plugins.nodetype.TypePredicate;
import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
import org.apache.jackrabbit.oak.spi.commit.DefaultEditor;
import org.apache.jackrabbit.oak.spi.commit.Editor;
import org.apache.jackrabbit.oak.spi.commit.EditorProvider;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This editor check if same name sibling nodes are allowed under a given
* parent. If they are not, they will be renamed by replacing brackets with a
* underscore: {@code sns_name[3] -> sns_name_3_}.
*/
public class SameNameSiblingsEditor extends DefaultEditor {
private static final Logger logger = LoggerFactory.getLogger(SameNameSiblingsEditor.class);
private static final Pattern SNS_REGEX = Pattern.compile("^(.+)\\[(\\d+)\\]$");
private static final Predicate NO_SNS_PROPERTY = input -> !input.getBoolean(JCR_SAMENAMESIBLINGS);
/**
* List of node type definitions that doesn't allow to have SNS children.
*/
private final List childrenDefsWithoutSns;
/**
* Builder of the current node.
*/
private final NodeBuilder builder;
/**
* Path to the current node.
*/
private final String path;
public static class Provider implements EditorProvider {
@Override
public Editor getRootEditor(NodeState before, NodeState after, NodeBuilder builder, CommitInfo info)
throws CommitFailedException {
return new SameNameSiblingsEditor(builder);
}
}
public SameNameSiblingsEditor(NodeBuilder rootBuilder) {
this.childrenDefsWithoutSns = prepareChildDefsWithoutSns(rootBuilder.getNodeState());
this.builder = rootBuilder;
this.path = "";
}
public SameNameSiblingsEditor(SameNameSiblingsEditor parent, String name, NodeBuilder builder) {
this.childrenDefsWithoutSns = parent.childrenDefsWithoutSns;
this.builder = builder;
this.path = new StringBuilder(parent.path).append('/').append(name).toString();
}
@Override
public Editor childNodeAdded(String name, NodeState after) throws CommitFailedException {
return new SameNameSiblingsEditor(this, name, builder.getChildNode(name));
}
@Override
public Editor childNodeChanged(String name, NodeState before, NodeState after) throws CommitFailedException {
return new SameNameSiblingsEditor(this, name, builder.getChildNode(name));
}
@Override
public void leave(NodeState before, NodeState after) throws CommitFailedException {
if (hasSameNamedChildren(after)) {
renameSameNamedChildren(builder);
}
}
/**
* Prepare a list of node definitions that doesn't allow having SNS children.
*
* @param root Repository root
* @return a list of node definitions denying SNS children
*/
private static List prepareChildDefsWithoutSns(NodeState root) {
List defs = new ArrayList();
NodeState types = root.getChildNode(JCR_SYSTEM).getChildNode(JCR_NODE_TYPES);
for (ChildNodeEntry typeEntry : types.getChildNodeEntries()) {
NodeState type = typeEntry.getNodeState();
TypePredicate typePredicate = new TypePredicate(root, typeEntry.getName());
defs.addAll(parseResidualChildNodeDefs(root, type, typePredicate));
defs.addAll(parseNamedChildNodeDefs(root, type, typePredicate));
}
return defs;
}
private static List parseNamedChildNodeDefs(NodeState root, NodeState parentType,
TypePredicate parentTypePredicate) {
List defs = new ArrayList();
NodeState namedChildNodeDefinitions = parentType.getChildNode(REP_NAMED_CHILD_NODE_DEFINITIONS);
for (ChildNodeEntry childName : namedChildNodeDefinitions.getChildNodeEntries()) {
for (String childType : filterChildren(childName.getNodeState(), NO_SNS_PROPERTY)) {
TypePredicate childTypePredicate = new TypePredicate(root, childType);
defs.add(new ChildTypeDef(parentTypePredicate, childName.getName(), childTypePredicate));
}
}
return defs;
}
private static List parseResidualChildNodeDefs(NodeState root, NodeState parentType,
TypePredicate parentTypePredicate) {
List defs = new ArrayList();
NodeState resChildNodeDefinitions = parentType.getChildNode(REP_RESIDUAL_CHILD_NODE_DEFINITIONS);
for (String childType : filterChildren(resChildNodeDefinitions, NO_SNS_PROPERTY)) {
TypePredicate childTypePredicate = new TypePredicate(root, childType);
defs.add(new ChildTypeDef(parentTypePredicate, childTypePredicate));
}
return defs;
}
/**
* Filter children of the given node using predicate and return the list of matching child names.
*
* @param parent
* @param predicate
* @return a list of names of children accepting the predicate
*/
private static Iterable filterChildren(NodeState parent, final Predicate predicate) {
return transform(filter(parent.getChildNodeEntries(),
input -> predicate.test(input.getNodeState())),
input -> input.getName());
}
/**
* Check if there are SNS nodes under the given parent.
*
* @param parent
* @return {@code true} if there are SNS children
*/
private boolean hasSameNamedChildren(NodeState parent) {
for (String name : parent.getChildNodeNames()) {
if (SNS_REGEX.matcher(name).matches()) {
return true;
}
}
return false;
}
/**
* Rename all SNS children which are not allowed under the given parent.
*/
private void renameSameNamedChildren(NodeBuilder parent) {
NodeState parentNode = parent.getNodeState();
Map toBeRenamed = new HashMap();
for (String name : parent.getChildNodeNames()) {
Matcher m = SNS_REGEX.matcher(name);
if (!m.matches()) {
continue;
} else if (isSnsAllowedForChild(parentNode, name)) {
continue;
}
String prefix = m.group(1);
String index = m.group(2);
toBeRenamed.put(name, createNewName(parentNode, prefix, index));
}
for (Entry e : toBeRenamed.entrySet()) {
logger.warn("Renaming SNS {}/{} to {}", path, e.getKey(), e.getValue());
parent.getChildNode(e.getKey()).moveTo(parent, e.getValue());
}
}
/**
* Check if SNS with given name is allowed under the given parent using the {@link #childrenDefsWithoutSns} list.
*/
private boolean isSnsAllowedForChild(NodeState parent, String name) {
for (ChildTypeDef snsDef : childrenDefsWithoutSns) {
if (snsDef.applies(parent, name)) {
return false;
}
}
return true;
}
/**
* Create new name for the conflicting SNS node. This method makes sure that
* no node with this name already exists.
*
* @param prefix prefix of the new name, eg. my_name[3]
* @param index SNS index, eg. my_name[3]
* @param parent of the SNS node
* @return new and unused name for the node
*/
private String createNewName(NodeState parent, String prefix, String index) {
String newName;
int i = 1;
do {
if (i == 1) {
newName = String.format("%s_%s_", prefix, index);
} else {
newName = String.format("%s_%s_%d", prefix, index, i);
}
i++;
} while (parent.getChildNode(newName).exists());
return newName;
}
/**
* Definition of a children type. It contains the parent type, the child
* type and an optional child name.
*/
private static class ChildTypeDef {
private final TypePredicate parentType;
private final String childNameConstraint;
private final TypePredicate childType;
public ChildTypeDef(TypePredicate parentType, String childName, TypePredicate childType) {
this.parentType = parentType;
this.childNameConstraint = childName;
this.childType = childType;
}
public ChildTypeDef(TypePredicate parentType, TypePredicate childType) {
this(parentType, null, childType);
}
public boolean applies(NodeState parent, String childName) {
boolean result = true;
result &= parentType.test(parent);
result &= childNameConstraint == null || childName.startsWith(this.childNameConstraint + '[');
result &= childType.test(parent.getChildNode(childName));
return result;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append(parentType.toString()).append(" > ");
if (childNameConstraint == null) {
result.append("*");
} else {
result.append(childNameConstraint);
}
result.append(childType.toString());
return result.toString();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy