You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by ba...@apache.org on 2016/03/18 16:48:13 UTC
svn commit: r1735622 - in /jackrabbit/oak/trunk/oak-upgrade/src:
main/java/org/apache/jackrabbit/oak/upgrade/
test/java/org/apache/jackrabbit/oak/upgrade/
Author: baedke
Date: Fri Mar 18 15:48:02 2016
New Revision: 1735622
URL: http://svn.apache.org/viewvc?rev=1735622&view=rev
Log:
OAK-3846: Add parameter to skip SNS nodes
Added CommitHook to rename illegal sns during repo migration. Patch provided by Tomek Rekawek (tomekr@apache.org).
Added:
jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/SameNameSiblingsEditor.java
jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/SameNodeSiblingsTest.java
Modified:
jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositoryUpgrade.java
Modified: jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositoryUpgrade.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositoryUpgrade.java?rev=1735622&r1=1735621&r2=1735622&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositoryUpgrade.java (original)
+++ jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositoryUpgrade.java Fri Mar 18 15:48:02 2016
@@ -465,7 +465,8 @@ public class RepositoryUpgrade {
new RestrictionEditorProvider(),
new GroupEditorProvider(groupsPath),
// copy referenced version histories
- new VersionableEditor.Provider(sourceRoot, workspaceName, versionCopyConfiguration)
+ new VersionableEditor.Provider(sourceRoot, workspaceName, versionCopyConfiguration),
+ new SameNameSiblingsEditor.Provider()
)));
// security-related hooks
Added: jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/SameNameSiblingsEditor.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/SameNameSiblingsEditor.java?rev=1735622&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/SameNameSiblingsEditor.java (added)
+++ jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/SameNameSiblingsEditor.java Fri Mar 18 15:48:02 2016
@@ -0,0 +1,299 @@
+/*
+ * 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 com.google.common.collect.Iterables.filter;
+import static com.google.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.plugins.nodetype.NodeTypeConstants.JCR_NODE_TYPES;
+import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.REP_NAMED_CHILD_NODE_DEFINITIONS;
+import static org.apache.jackrabbit.oak.plugins.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.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;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+
+/**
+ * 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<NodeState> NO_SNS_PROPERTY = new Predicate<NodeState>() {
+ @Override
+ public boolean apply(NodeState input) {
+ return !input.getBoolean(JCR_SAMENAMESIBLINGS);
+ }
+ };
+
+ /**
+ * List of node type definitions that doesn't allow to have SNS children.
+ */
+ private final List<ChildTypeDef> 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<ChildTypeDef> prepareChildDefsWithoutSns(NodeState root) {
+ List<ChildTypeDef> defs = new ArrayList<ChildTypeDef>();
+ 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<ChildTypeDef> parseNamedChildNodeDefs(NodeState root, NodeState parentType,
+ TypePredicate parentTypePredicate) {
+ List<ChildTypeDef> defs = new ArrayList<ChildTypeDef>();
+ 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<ChildTypeDef> parseResidualChildNodeDefs(NodeState root, NodeState parentType,
+ TypePredicate parentTypePredicate) {
+ List<ChildTypeDef> defs = new ArrayList<ChildTypeDef>();
+ 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<String> filterChildren(NodeState parent, final Predicate<NodeState> predicate) {
+ return transform(filter(parent.getChildNodeEntries(), new Predicate<ChildNodeEntry>() {
+ @Override
+ public boolean apply(ChildNodeEntry input) {
+ return predicate.apply(input.getNodeState());
+ }
+ }), new Function<ChildNodeEntry, String>() {
+ @Override
+ public String apply(ChildNodeEntry input) {
+ return 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<String, String> toBeRenamed = new HashMap<String, String>();
+ 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<String, String> 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. <b>my_name</b>[3]
+ * @param index SNS index, eg. my_name[<b>3</b>]
+ * @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.apply(parent);
+ result &= childNameConstraint == null || childName.startsWith(this.childNameConstraint + '[');
+ result &= childType.apply(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();
+ }
+ }
+}
Added: jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/SameNodeSiblingsTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/SameNodeSiblingsTest.java?rev=1735622&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/SameNodeSiblingsTest.java (added)
+++ jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/SameNodeSiblingsTest.java Fri Mar 18 15:48:02 2016
@@ -0,0 +1,167 @@
+/*
+ * 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 com.google.common.collect.ImmutableSet.of;
+import static com.google.common.collect.Sets.newHashSet;
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Set;
+
+import javax.jcr.Credentials;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.core.RepositoryContext;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
+import org.apache.jackrabbit.oak.plugins.document.DocumentMK;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.io.Files;
+
+public class SameNodeSiblingsTest {
+
+ public static final Credentials CREDENTIALS = new SimpleCredentials("admin", "admin".toCharArray());
+
+ private File crx2RepoDir;
+
+ @Before
+ public void createCrx2RepoDir() throws IOException {
+ crx2RepoDir = Files.createTempDir();
+ }
+
+ @After
+ public void deleteCrx2RepoDir() {
+ FileUtils.deleteQuietly(crx2RepoDir);
+ }
+
+ @Test
+ public void snsShouldBeRenamed() throws RepositoryException, IOException {
+ DocumentNodeStore nodeStore = migrate(new SourceDataCreator() {
+ @Override
+ public void create(Session session) throws RepositoryException {
+ Node parent = session.getRootNode().addNode("parent");
+ parent.addNode("child", "nt:folder");
+ parent.addNode("child", "nt:folder");
+ parent.addNode("child", "nt:folder");
+ parent.addNode("something_else", "nt:folder");
+ session.save();
+
+ parent.setPrimaryType("nt:folder"); // change parent type to
+ // something that doesn't
+ // allow SNS
+ session.save();
+ }
+ });
+ try {
+ NodeState parent = nodeStore.getRoot().getChildNode("parent");
+ Set<String> children = newHashSet(parent.getChildNodeNames());
+ assertEquals(of("child", "child_2_", "child_3_", "something_else"), children);
+ } finally {
+ nodeStore.dispose();
+ }
+ }
+
+ @Test
+ public void snsShouldntBeRenamed() throws RepositoryException, IOException {
+ DocumentNodeStore nodeStore = migrate(new SourceDataCreator() {
+ @Override
+ public void create(Session session) throws RepositoryException {
+ Node parent = session.getRootNode().addNode("parent");
+ parent.addNode("child", "nt:folder");
+ parent.addNode("child", "nt:folder");
+ parent.addNode("child", "nt:folder");
+ parent.addNode("something_else", "nt:folder");
+ session.save();
+ }
+ });
+ try {
+ NodeState parent = nodeStore.getRoot().getChildNode("parent");
+ Set<String> children = newHashSet(parent.getChildNodeNames());
+ assertEquals(of("child", "child[2]", "child[3]", "something_else"), children);
+ } finally {
+ nodeStore.dispose();
+ }
+ }
+
+ @Test
+ public void snsNewNameAlreadyExists() throws RepositoryException, IOException {
+ DocumentNodeStore nodeStore = migrate(new SourceDataCreator() {
+ @Override
+ public void create(Session session) throws RepositoryException {
+ Node parent = session.getRootNode().addNode("parent");
+ parent.addNode("child", "nt:folder");
+ parent.addNode("child", "nt:folder");
+ parent.addNode("child", "nt:folder");
+ parent.addNode("child_2_", "nt:folder");
+ parent.addNode("child_3_", "nt:folder");
+ parent.addNode("child_3_2", "nt:folder");
+ session.save();
+
+ parent.setPrimaryType("nt:folder");
+ session.save();
+ }
+ });
+ try {
+ NodeState parent = nodeStore.getRoot().getChildNode("parent");
+ Set<String> children = newHashSet(parent.getChildNodeNames());
+ assertEquals(of("child", "child_2_", "child_3_", "child_2_2", "child_3_2", "child_3_3"), children);
+ } finally {
+ nodeStore.dispose();
+ }
+ }
+
+ private DocumentNodeStore migrate(SourceDataCreator sourceDataCreator) throws RepositoryException, IOException {
+ RepositoryConfig config = RepositoryConfig.install(crx2RepoDir);
+ RepositoryImpl repository = RepositoryImpl.create(config);
+
+ try {
+ Session session = repository.login(CREDENTIALS);
+ sourceDataCreator.create(session);
+ session.logout();
+ } finally {
+ repository.shutdown();
+ }
+
+ config = RepositoryConfig.install(crx2RepoDir); // re-create the config
+ RepositoryContext context = RepositoryContext.create(config);
+ DocumentNodeStore target = new DocumentMK.Builder().getNodeStore();
+ try {
+ RepositoryUpgrade upgrade = new RepositoryUpgrade(context, target);
+ upgrade.copy(null);
+ } finally {
+ context.getRepository().shutdown();
+ }
+ return target;
+ }
+
+ private static interface SourceDataCreator {
+ void create(Session session) throws RepositoryException;
+ }
+}