This is an automated email from the ASF dual-hosted git repository.
baedke pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
The following commit(s) were added to refs/heads/trunk by this push:
new e158656bce OAK-11617: Provide oak-run commands to analyze and fix
inconsistencie… (#2615)
e158656bce is described below
commit e158656bceede498436331e9be8ae76c3f918127
Author: mbaedke <[email protected]>
AuthorDate: Tue Nov 25 14:07:25 2025 +0100
OAK-11617: Provide oak-run commands to analyze and fix inconsistencie…
(#2615)
* OAK-11617: Provide oak-run commands to analyze and fix inconsistencies in
the namespace registry
Introduced new option --prune, added documentation and tests.
---
.../oak/plugins/name/NamespaceRegistryModel.java | 54 +++++-
.../oak/plugins/name/NamespaceRegistryTest.java | 184 ++++++++++++++++++++-
oak-run/README.md | 34 ++++
.../oak/run/NamespaceRegistryCommand.java | 44 ++---
.../oak/run/NamespaceRegistryOptions.java | 4 +
.../oak/run/NamespaceRegistryCommandTest.java | 88 +++++++++-
6 files changed, 377 insertions(+), 31 deletions(-)
diff --git
a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/NamespaceRegistryModel.java
b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/NamespaceRegistryModel.java
index 99868effdb..4817fbe726 100644
---
a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/NamespaceRegistryModel.java
+++
b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/NamespaceRegistryModel.java
@@ -30,6 +30,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@@ -188,6 +189,22 @@ public final class NamespaceRegistryModel {
}
}
+ /**
+ * Remove any unmapped prefixes and URIs, so that they don't have to be
mapped to dummy values in order to
+ * create a consistent registry.
+ * @return new NamespaceRegistryModel without unmapped prefixes or URIs.
+ */
+ public NamespaceRegistryModel prune() {
+ List<String> newRegisteredPrefixesList = new
ArrayList<>(registeredPrefixes);
+ newRegisteredPrefixesList.removeAll(danglingPrefixes);
+ List<String> newRegisteredNamespacesEncodedList = new
ArrayList<>(registeredNamespacesEncoded);
+
newRegisteredNamespacesEncodedList.removeAll(danglingNamespacesEncoded);
+ Map<String, String> newPrefixToNamespaceMap = new
HashMap<>(prefixToNamespaceMap);
+ Map<String, String> newEncodedNamespaceToPrefixMap = new
HashMap<>(encodedNamespaceToPrefixMap);
+ return new NamespaceRegistryModel(newRegisteredPrefixesList,
newRegisteredNamespacesEncodedList,
+ newPrefixToNamespaceMap, newEncodedNamespaceToPrefixMap);
+ }
+
/**
* Creates a new {@link NamespaceRegistryModel} with the given mappings.
Used by {@see NamespaceRegistryCommand} to
* repair a namespace registry that cannot be fixed automatically because
mapping information is missing.
@@ -205,19 +222,44 @@ public final class NamespaceRegistryModel {
String uri = entry.getValue();
String encodedUri = Namespaces.encodeUri(uri);
+ String previousMappedUri = newPrefixToNamespaceMap.get(prefix);
+ //if the prefix of the new mapping is already mapped to an
existing URI, remove this existing mapping so
+ //that it doesn't need to be explicitly overridden (which still
may be done, if needed).
+ if (newPrefixToNamespaceMap.containsValue(uri)) {
+ Optional<String> s =
newPrefixToNamespaceMap.entrySet().stream()
+ .filter(mapEntry -> uri.equals(mapEntry.getValue()))
+ .map(Map.Entry::getKey).findFirst();
+ if (s.isPresent() && !prefix.equals(s.get())) {
+ newPrefixToNamespaceMap.remove(s.get());
+ }
+ }
+ //if the URI of the new mapping is already mapped to an existing
prefix, remove this existing mapping so
+ //that it doesn't need to be explicitly overridden (which still
may be done, if needed).
+ String previousMappedPrefix =
newEncodedNamespaceToPrefixMap.get(encodedUri);
+ if (newEncodedNamespaceToPrefixMap.containsValue(prefix)) {
+ Optional<String> s =
newEncodedNamespaceToPrefixMap.entrySet().stream()
+ .filter(mapEntry -> prefix.equals(mapEntry.getValue()))
+ .map(Map.Entry::getKey).findFirst();
+ if (s.isPresent() && !encodedUri.equals(s.get())) {
+ newEncodedNamespaceToPrefixMap.remove(s.get());
+ }
+ }
+ if (previousMappedPrefix != null) {
+ newRegisteredPrefixesList.remove(previousMappedPrefix);
+ newPrefixToNamespaceMap.remove(previousMappedPrefix);
+ }
if (!newRegisteredPrefixesList.contains(prefix)) {
newRegisteredPrefixesList.add(prefix);
}
- if (!newRegisteredNamespacesEncodedList.contains(encodedUri)) {
- newRegisteredNamespacesEncodedList.add(encodedUri);
- }
- String previousUri = newPrefixToNamespaceMap.get(prefix);
newPrefixToNamespaceMap.put(prefix, uri);
- if (previousUri != null) {
- String previousEncodedUri = Namespaces.encodeUri(previousUri);
+ if (previousMappedUri != null) {
+ String previousEncodedUri =
Namespaces.encodeUri(previousMappedUri);
newRegisteredNamespacesEncodedList.remove(previousEncodedUri);
newEncodedNamespaceToPrefixMap.remove(previousEncodedUri);
}
+ if (!newRegisteredNamespacesEncodedList.contains(encodedUri)) {
+ newRegisteredNamespacesEncodedList.add(encodedUri);
+ }
newEncodedNamespaceToPrefixMap.put(encodedUri, prefix);
}
return new NamespaceRegistryModel(newRegisteredPrefixesList,
newRegisteredNamespacesEncodedList,
diff --git
a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/name/NamespaceRegistryTest.java
b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/name/NamespaceRegistryTest.java
index 5fd2590ed4..dde91baef5 100644
---
a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/name/NamespaceRegistryTest.java
+++
b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/name/NamespaceRegistryTest.java
@@ -28,6 +28,7 @@ import
org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
import org.junit.Test;
+import javax.jcr.NamespaceException;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
@@ -40,6 +41,7 @@ import static
org.apache.jackrabbit.oak.spi.namespace.NamespaceConstants.REP_URI
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.spy;
@@ -222,9 +224,10 @@ public class NamespaceRegistryTest {
assertFalse(model.isConsistent());
assertTrue(model.isFixable());
- // Add a registered namespace uri without any mapping
+ // Add two registered namespace uris without any mapping
builder = PropertyBuilder.copy(Type.STRING, namespaceProp);
builder.addValue("urn:bar2");
+ builder.addValue("urn:bar3");
nsdata.setProperty(builder.getPropertyState());
// Cannot be fixed automatically
@@ -234,12 +237,13 @@ public class NamespaceRegistryTest {
assertFalse(model.isConsistent());
assertFalse(model.isFixable());
- // remap a prefix and map the new URI to make it fixable
+ // remap a prefix, map the first new URI and remove the dangling
second new URI to make it fixable
HashMap<String, String> mappings = new HashMap<>();
mappings.put("foo", "urn:foo2");
mappings.put("bar2", "urn:bar2");
assertFalse(registry.checkConsistency(root));
model = model.setMappings(mappings);
+ model = model.prune();
assertFalse(model.isConsistent());
assertTrue(model.isFixable());
@@ -280,6 +284,182 @@ public class NamespaceRegistryTest {
}
}
+ @Test
+ public void testNamespaceRegistryModelRemappedPrefix() throws Exception {
+ try (ContentSession session = new Oak()
+ .with(new OpenSecurityProvider())
+ .with(new InitialContent())
+ .with(new NamespaceEditorProvider())
+ .createContentSession()) {
+ Root root = session.getLatestRoot();
+ ReadWriteNamespaceRegistry registry = new
TestNamespaceRegistry(root);
+
+ // Add a mapping
+ NamespaceRegistryModel model = NamespaceRegistryModel.create(root);
+ String foo = "foo";
+ String fooUri = "urn:foo";
+ model = model.setMappings(Collections.singletonMap(foo, fooUri));
+ assertTrue(model.isConsistent());
+ model.apply(root);
+ assertTrue(registry.checkConsistency());
+ assertEquals(foo, registry.getPrefix(fooUri));
+
+ // re-map
+ String barUri = "urn:bar";
+ model = model.setMappings(Collections.singletonMap(foo, barUri));
+ assertTrue(model.isConsistent());
+ model.apply(root);
+ assertTrue(registry.checkConsistency());
+ assertEquals(foo, registry.getPrefix(barUri));
+ assertThrows(NamespaceException.class, () ->
registry.getPrefix(fooUri));
+ }
+ }
+
+ @Test
+ public void testNamespaceRegistryModelRemappedNamespace() throws Exception
{
+ try (ContentSession session = new Oak()
+ .with(new OpenSecurityProvider())
+ .with(new InitialContent())
+ .with(new NamespaceEditorProvider())
+ .createContentSession()) {
+ Root root = session.getLatestRoot();
+ ReadWriteNamespaceRegistry registry = new
TestNamespaceRegistry(root);
+
+ // Add a mapping
+ NamespaceRegistryModel model = NamespaceRegistryModel.create(root);
+ String foo = "foo";
+ String fooUri = "urn:foo";
+ model = model.setMappings(Collections.singletonMap(foo, fooUri));
+ assertTrue(model.isConsistent());
+ model.apply(root);
+ assertTrue(registry.checkConsistency());
+ assertEquals(foo, registry.getPrefix(fooUri));
+
+ // re-map
+ String bar = "bar";
+ model = model.setMappings(Collections.singletonMap(bar, fooUri));
+ assertTrue(model.isConsistent());
+ model.apply(root);
+ assertTrue(registry.checkConsistency());
+ assertEquals(bar, registry.getPrefix(fooUri));
+ assertThrows(NamespaceException.class, () -> registry.getURI(foo));
+ }
+ }
+
+ @Test
+ public void testNamespaceRegistryModelAmbiguousUri() throws Exception {
+ try (ContentSession session = new Oak()
+ .with(new OpenSecurityProvider())
+ .with(new InitialContent())
+ .with(new NamespaceEditorProvider())
+ .createContentSession()) {
+ Root root = session.getLatestRoot();
+ ReadWriteNamespaceRegistry registry = new
TestNamespaceRegistry(root);
+ Tree namespaces = root.getTree("/jcr:system/rep:namespaces");
+ Tree nsdata = namespaces.getChild(REP_NSDATA);
+
+ // Add a mapping and an incompatible reverse mapping
+ String foo = "foo";
+ String bar = "bar";
+ String fooUri = "urn:foo";
+ String barUri = "urn:bar";
+ namespaces.setProperty(foo, fooUri);
+ nsdata.setProperty(Namespaces.encodeUri(barUri), foo);
+
+ NamespaceRegistryModel model = NamespaceRegistryModel.create(root);
+ assertNotNull(model);
+ assertFalse(registry.checkConsistency(root));
+ assertFalse(model.isConsistent());
+ assertFalse(model.isFixable());
+ model = model.setMappings(Collections.singletonMap(foo, fooUri));
+ assertTrue(model.isConsistent());
+ model.apply(root);
+ assertTrue(registry.checkConsistency());
+
+ assertEquals(fooUri, registry.getURI(foo));
+ assertThrows(NamespaceException.class, () -> registry.getURI(bar));
+ assertEquals(foo, registry.getPrefix(fooUri));
+ assertThrows(NamespaceException.class, () ->
registry.getPrefix(barUri));
+ }
+ }
+
+ @Test
+ public void testNamespaceRegistryModelAmbiguousPrefix() throws Exception {
+ try (ContentSession session = new Oak()
+ .with(new OpenSecurityProvider())
+ .with(new InitialContent())
+ .with(new NamespaceEditorProvider())
+ .createContentSession()) {
+ Root root = session.getLatestRoot();
+ ReadWriteNamespaceRegistry registry = new
TestNamespaceRegistry(root);
+ Tree namespaces = root.getTree("/jcr:system/rep:namespaces");
+ Tree nsdata = namespaces.getChild(REP_NSDATA);
+
+ // Add a mapping and an incompatible reverse mapping
+ String foo = "foo";
+ String bar = "bar";
+ String fooUri = "urn:foo";
+ String barUri = "urn:bar";
+ namespaces.setProperty(foo, fooUri);
+ nsdata.setProperty(Namespaces.encodeUri(fooUri), bar);
+
+ NamespaceRegistryModel model = NamespaceRegistryModel.create(root);
+ assertNotNull(model);
+ assertFalse(registry.checkConsistency(root));
+ assertFalse(model.isConsistent());
+ assertFalse(model.isFixable());
+ model = model.setMappings(Collections.singletonMap(foo, fooUri));
+ assertTrue(model.isConsistent());
+ model.apply(root);
+ assertTrue(registry.checkConsistency());
+
+ assertEquals(fooUri, registry.getURI(foo));
+ assertThrows(NamespaceException.class, () -> registry.getURI(bar));
+ assertEquals(foo, registry.getPrefix(fooUri));
+ assertThrows(NamespaceException.class, () ->
registry.getPrefix(barUri));
+ }
+ }
+
+ @Test
+ public void testNamespaceRegistryModelPruneUnmappedData() throws Exception
{
+ try (ContentSession session = new Oak()
+ .with(new OpenSecurityProvider())
+ .with(new InitialContent())
+ .with(new NamespaceEditorProvider())
+ .createContentSession()) {
+ Root root = session.getLatestRoot();
+ ReadWriteNamespaceRegistry registry = new
TestNamespaceRegistry(root);
+ Tree namespaces = root.getTree("/jcr:system/rep:namespaces");
+ Tree nsdata = namespaces.getChild(REP_NSDATA);
+ PropertyState prefixProp = nsdata.getProperty(REP_PREFIXES);
+ PropertyState namespaceProp = nsdata.getProperty(REP_URIS);
+
+ // Add a prefix and an URI without mappings
+ String foo = "foo";
+ String barUri = "urn:bar";
+
+ PropertyBuilder<String> builder =
PropertyBuilder.copy(Type.STRING, prefixProp);
+ builder.addValue(foo);
+ nsdata.setProperty(builder.getPropertyState());
+ builder = PropertyBuilder.copy(Type.STRING, namespaceProp);
+ builder.addValue(barUri);
+ nsdata.setProperty(builder.getPropertyState());
+
+ NamespaceRegistryModel model = NamespaceRegistryModel.create(root);
+ assertNotNull(model);
+ assertFalse(registry.checkConsistency(root));
+ assertFalse(model.isConsistent());
+ assertFalse(model.isFixable());
+ model = model.prune();
+ assertTrue(model.isConsistent());
+ model.apply(root);
+ assertTrue(registry.checkConsistency());
+
+ assertThrows(NamespaceException.class, () -> registry.getURI(foo));
+ assertThrows(NamespaceException.class, () ->
registry.getPrefix(barUri));
+ }
+ }
+
static class TestNamespaceRegistry extends ReadWriteNamespaceRegistry {
public TestNamespaceRegistry(Root root) {
super(root);
diff --git a/oak-run/README.md b/oak-run/README.md
index 049961d3e7..00ec262fcb 100644
--- a/oak-run/README.md
+++ b/oak-run/README.md
@@ -24,6 +24,7 @@ The following runmodes are currently available:
* help : Print a list of available runmodes
* history : Trace the history of a node
* iotrace : Collect a trace of segment store read accesses
+ * namespace-registry : Analyze and optionally repair the namespace
registry in an Oak repository
* recovery : Run a _lastRev recovery on a DocumentMK repository
* resetclusterid : Resets the cluster id
* restore : Restore a backup of an Oak repository
@@ -778,6 +779,39 @@ Upgrades the JR2 DataStore cache by moving files to the
Upload staging and the d
--moveCache <true|false> \
--deleteMapFile <true|false>
+Namespace Registry
+-------
+
+The 'namespace-registry' mode analyses and optionally repairs the
namespace-registry of an existing Oak repository.
+
+It will write a human-readable summary to stdout. If inconsistencies are
detected, they will be reported and
+if they can be repaired automatically, a dry run of the fix operation will be
performed and the results will be
+written to stdout. If automatic repair is not possible, the missing
information may be added using the --mappings
+option.
+
+ $ java -jar oak-run-*.jar namespace-registry \
+ { /path/to/oak/repository | mongodb://host:port/database |
jdbc:...} \
+ [--analyse] \
+ [--fix --read-write] \
+ [--prune] \
+ [--mappings prefix=uri]...
+
+The following options are available:
+
+ --analyse - An analysis of the namespace registry will be
performed. The result will be written
+ to stdout in human-readable form. If
inconsistencies are detected, they will be
+ reported and if they can be repaired
automatically, a dry run of the --fix operation
+ will be performed and the results will be
written to stdout.
+ If --mappings are supplied, they will be
considered part of the registry.
+ --fix - This will try an automatic repair of an
inconsistent namespace registry.
+ If --mappings are supplied, they will be added
to the registry, overriding existing
+ mappings containing the same prefix or URI.
+ --mappings - Sometimes an inconsistent namespace registry
cannot be repaired without additional
+ information. The missing information can be
supplied using this option if the form
+ of prefix to URI mappings which will override
any (incomplete) mappings in the
+ registry during --analyse or --fix operations.
+ --prune - Remove any unmapped prefixes and URIs before
--analyse or --fix operations.
+
Unlock DocumentMK upgrade
-------------------------
diff --git
a/oak-run/src/main/java/org/apache/jackrabbit/oak/run/NamespaceRegistryCommand.java
b/oak-run/src/main/java/org/apache/jackrabbit/oak/run/NamespaceRegistryCommand.java
index 605c7e0cb9..6e8849f966 100644
---
a/oak-run/src/main/java/org/apache/jackrabbit/oak/run/NamespaceRegistryCommand.java
+++
b/oak-run/src/main/java/org/apache/jackrabbit/oak/run/NamespaceRegistryCommand.java
@@ -23,7 +23,6 @@ import org.apache.jackrabbit.oak.api.ContentSession;
import org.apache.jackrabbit.oak.api.Root;
import org.apache.jackrabbit.oak.commons.pio.Closer;
import org.apache.jackrabbit.oak.plugins.name.NamespaceRegistryModel;
-import org.apache.jackrabbit.oak.plugins.name.ReadWriteNamespaceRegistry;
import org.apache.jackrabbit.oak.run.cli.CommonOptions;
import org.apache.jackrabbit.oak.run.cli.NodeStoreFixture;
import org.apache.jackrabbit.oak.run.cli.NodeStoreFixtureProvider;
@@ -41,7 +40,7 @@ import java.util.Map;
/**
* Command to analyze and repair the namespace registry in an Oak repository
({@link NamespaceRegistryModel}).
- * Possible options are: --analyse, --fix, and --mappings, which will execute
corresponding operations on
+ * Possible options are: --analyse, --fix, --prune and --mappings, which will
execute corresponding operations on
* the namespace registry.
* <p>
* --analyse executes an operation that will print the current consistency
state of the namespace registry to
@@ -51,7 +50,9 @@ import java.util.Map;
* --fix executes an operation that will attempt to repair an inconsistent the
namespace registry.
* <p>
* --mappings is an option for both operations, allowing to specify additional
namespace mappings in
- * the format "prefix=uri", which will be applied during the operation.
+ * the format "prefix=uri", which will be applied during the operation. It may
be used multiple times.
+ * <p>
+ * --prune is an option for both operations. All unmapped data will be removed.
*/
public class NamespaceRegistryCommand implements Command {
@@ -110,32 +111,31 @@ public class NamespaceRegistryCommand implements Command {
throws IOException, RepositoryException, CommitFailedException {
boolean analyse = namespaceRegistryOptions.analyse();
boolean fix = namespaceRegistryOptions.fix();
+ boolean prune = namespaceRegistryOptions.prune();
List<String> mappings = namespaceRegistryOptions.mappings();
Oak oak = new Oak(fixture.getStore()).with(new OpenSecurityProvider());
try (ContentSession contentSession = oak.createContentSession()) {
Root root = contentSession.getLatestRoot();
- ReadWriteNamespaceRegistry namespaceRegistry = new
ReadWriteNamespaceRegistry(root) {
- @Override
- protected Root getWriteRoot() {
- return root;
- }
- };
if (analyse || fix) {
- NamespaceRegistryModel registryModel =
NamespaceRegistryModel.create(root);
- if (fix) {
- Map<String, String> additionalMappings = new HashMap<>();
- if (mappings != null) {
- for (String mapping : mappings) {
- String[] parts = mapping.split("=");
- if (parts.length != 2) {
- System.err.println("Invalid mapping: " +
mapping);
- return;
- }
- additionalMappings.put(parts[0].trim(),
parts[1].trim());
+ NamespaceRegistryModel originalModel =
NamespaceRegistryModel.create(root);
+ NamespaceRegistryModel registryModel = originalModel;
+ Map<String, String> additionalMappings = new HashMap<>();
+ if (mappings != null) {
+ for (String mapping : mappings) {
+ String[] parts = mapping.split("=");
+ if (parts.length != 2) {
+ System.err.println("Invalid mapping: " + mapping);
+ return;
}
+ additionalMappings.put(parts[0].trim(),
parts[1].trim());
}
- registryModel =
registryModel.setMappings(additionalMappings);
- if (registryModel.isConsistent() &&
additionalMappings.isEmpty()) {
+ }
+ registryModel = registryModel.setMappings(additionalMappings);
+ if (prune) {
+ registryModel = registryModel.prune();
+ }
+ if (fix) {
+ if (originalModel.isConsistent() &&
additionalMappings.isEmpty()) {
System.out.println("The namespace registry is already
consistent. No action is required.");
} else if (registryModel.isFixable()) {
registryModel.dump(System.out);
diff --git
a/oak-run/src/main/java/org/apache/jackrabbit/oak/run/NamespaceRegistryOptions.java
b/oak-run/src/main/java/org/apache/jackrabbit/oak/run/NamespaceRegistryOptions.java
index 49b5d8f357..85d156a0cb 100644
---
a/oak-run/src/main/java/org/apache/jackrabbit/oak/run/NamespaceRegistryOptions.java
+++
b/oak-run/src/main/java/org/apache/jackrabbit/oak/run/NamespaceRegistryOptions.java
@@ -38,11 +38,13 @@ public class NamespaceRegistryOptions implements
OptionsBean {
private final OptionSpec<Void> analyseOpt;
private final OptionSpec<Void> fixOpt;
+ private final OptionSpec<Void> pruneOpt;
private final OptionSpec<String> mappingsOpt;
public NamespaceRegistryOptions(OptionParser parser) {
analyseOpt = parser.accepts("analyse", "List the prefix to namespace
map and check for consistency.");
fixOpt = parser.accepts("fix", "List the prefix to namespace map,
check for consistency and fix any inconsistencies, if possible.");
+ pruneOpt = parser.accepts("prune", "Remove any unmapped uris and
prefixes");
mappingsOpt = parser.accepts("mappings", "Optionally specify explicit
prefix to namespace mappings ad a list of prefix=uri
expressions").withRequiredArg();
actionOpts = Set.of(analyseOpt, fixOpt);
operationNames = collectionOperationNames(actionOpts);
@@ -90,6 +92,8 @@ public class NamespaceRegistryOptions implements OptionsBean {
return options.has(fixOpt);
}
+ public boolean prune() { return options.has(pruneOpt); }
+
public List<String> mappings() {
return options.valuesOf(mappingsOpt);
}
diff --git
a/oak-run/src/test/java/org/apache/jackrabbit/oak/run/NamespaceRegistryCommandTest.java
b/oak-run/src/test/java/org/apache/jackrabbit/oak/run/NamespaceRegistryCommandTest.java
index 89d0ed2d73..86aa1079f5 100644
---
a/oak-run/src/test/java/org/apache/jackrabbit/oak/run/NamespaceRegistryCommandTest.java
+++
b/oak-run/src/test/java/org/apache/jackrabbit/oak/run/NamespaceRegistryCommandTest.java
@@ -34,7 +34,11 @@ import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import static org.apache.jackrabbit.oak.api.Type.STRINGS;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
@@ -76,16 +80,98 @@ public class NamespaceRegistryCommandTest {
}
@Test
- public void breakAndFix() throws Exception {
+ public void breakAndFixNoReverseMapping() throws Exception {
NodeBuilder rootBuilder = store.getRoot().builder();
NodeBuilder namespaces =
rootBuilder.getChildNode(JcrConstants.JCR_SYSTEM).getChildNode(NamespaceConstants.REP_NAMESPACES);
+ //inconsistent mapping: no reverse mapping
namespaces.setProperty("foo", "urn:foo");
store.merge(rootBuilder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
store.runBackgroundOperations();
+ //complete information: automatic fix
testCmd(new String[] { MongoUtils.URL, "--analyse" }, new String[] {
"This namespace registry model is inconsistent. The inconsistency can be
fixed.", "The repaired registry would contain the following mappings:", "foo ->
urn:foo" });
testCmd(new String[] { MongoUtils.URL, "--fix", "--read-write" }, new
String[] { "This namespace registry model is consistent, containing the
following mappings from prefixes to namespace uris:", "foo -> urn:foo" });
}
+ @Test
+ public void breakAndFixPrefixAmbiguity() throws Exception {
+ NodeBuilder rootBuilder = store.getRoot().builder();
+ NodeBuilder namespaces =
rootBuilder.getChildNode(JcrConstants.JCR_SYSTEM).getChildNode(NamespaceConstants.REP_NAMESPACES);
+ NodeBuilder nsdata =
namespaces.getChildNode(NamespaceConstants.REP_NSDATA);
+ //inconsistent mapping: one URI, two prefixes
+ namespaces.setProperty("foo", "urn:foo");
+ nsdata.setProperty(Namespaces.encodeUri("urn:foo"), "bar");
+ store.merge(rootBuilder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ store.runBackgroundOperations();
+ //ambiguous information: no automatic fix
+ testCmd(new String[] { MongoUtils.URL, "--analyse" }, new String[] {
"This namespace registry model is inconsistent. The inconsistency can NOT be
fixed." });
+ //consistent with supplied specific mapping
+ testCmd(new String[] { MongoUtils.URL, "--analyse", "--mappings",
"foo=urn:foo" }, new String[] { "This namespace registry model is consistent,
containing the following mappings from prefixes to namespace uris:", "foo ->
urn:foo" });
+ testCmd(new String[] { MongoUtils.URL, "--fix", "--read-write",
"--mappings", "foo=urn:foo" }, new String[] { "This namespace registry model is
consistent, containing the following mappings from prefixes to namespace
uris:", "foo -> urn:foo" });
+ }
+
+ @Test
+ public void breakAndFixUriAmbiguity() throws Exception {
+ NodeBuilder rootBuilder = store.getRoot().builder();
+ NodeBuilder namespaces =
rootBuilder.getChildNode(JcrConstants.JCR_SYSTEM).getChildNode(NamespaceConstants.REP_NAMESPACES);
+ NodeBuilder nsdata =
namespaces.getChildNode(NamespaceConstants.REP_NSDATA);
+ //inconsistent mapping: one prefix, two URIs
+ namespaces.setProperty("foo", "urn:foo");
+ nsdata.setProperty(Namespaces.encodeUri("urn:bar"), "foo");
+ store.merge(rootBuilder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ store.runBackgroundOperations();
+ //ambiguous information: no automatic fix
+ testCmd(new String[] { MongoUtils.URL, "--analyse" }, new String[] {
"This namespace registry model is inconsistent. The inconsistency can NOT be
fixed." });
+ //consistent with supplied specific mapping
+ testCmd(new String[] { MongoUtils.URL, "--analyse", "--mappings",
"foo=urn:foo" }, new String[] { "This namespace registry model is consistent,
containing the following mappings from prefixes to namespace uris:", "foo ->
urn:foo" });
+ testCmd(new String[] { MongoUtils.URL, "--fix", "--read-write",
"--mappings", "foo=urn:foo" }, new String[] { "This namespace registry model is
consistent, containing the following mappings from prefixes to namespace
uris:", "foo -> urn:foo" });
+ }
+
+ @Test
+ public void breakAndFixDanglingPrefix() throws Exception {
+ NodeBuilder rootBuilder = store.getRoot().builder();
+ NodeBuilder namespaces =
rootBuilder.getChildNode(JcrConstants.JCR_SYSTEM).getChildNode(NamespaceConstants.REP_NAMESPACES);
+ NodeBuilder nsdata = namespaces.child(NamespaceConstants.REP_NSDATA);
+ //adding a prefix without any mapping to an URI
+ Iterable<String> prefixes =
Objects.requireNonNull(nsdata.getProperty(NamespaceConstants.REP_PREFIXES)).getValue(STRINGS);
+ List<String> newValue = new ArrayList<>();
+ prefixes.forEach(newValue::add);
+ newValue.add("foo");
+ nsdata.setProperty(NamespaceConstants.REP_PREFIXES, newValue, STRINGS);
+ store.merge(rootBuilder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ store.runBackgroundOperations();
+ //missing information: no automatic fix
+ testCmd(new String[] { MongoUtils.URL, "--analyse" }, new String[] {
"This namespace registry model is inconsistent. The inconsistency can NOT be
fixed." });
+ //consistent after removal of unmapped data.
+ testCmd(new String[] { MongoUtils.URL, "--analyse", "--prune" }, new
String[] { "This namespace registry model is consistent" });
+ //consistent with supplied complete information.
+ testCmd(new String[] { MongoUtils.URL, "--analyse", "--mappings",
"foo=urn:foo" }, new String[] { "This namespace registry model is consistent,
containing the following mappings from prefixes to namespace uris:", "foo ->
urn:foo" });
+ testCmd(new String[] { MongoUtils.URL, "--fix", "--read-write" }, new
String[] { "This namespace registry model is inconsistent. The inconsistency
can NOT be fixed." });
+ testCmd(new String[] { MongoUtils.URL, "--fix", "--read-write",
"--prune" }, new String[] { "This namespace registry model is consistent" });
+ }
+
+ @Test
+ public void breakAndFixDanglingUri() throws Exception {
+ NodeBuilder rootBuilder = store.getRoot().builder();
+ NodeBuilder namespaces =
rootBuilder.getChildNode(JcrConstants.JCR_SYSTEM).getChildNode(NamespaceConstants.REP_NAMESPACES);
+ NodeBuilder nsdata = namespaces.child(NamespaceConstants.REP_NSDATA);
+ //adding an URI without any mapping to a prefix
+ Iterable<String> prefixes =
Objects.requireNonNull(nsdata.getProperty(NamespaceConstants.REP_URIS)).getValue(STRINGS);
+ List<String> newValue = new ArrayList<>();
+ prefixes.forEach(newValue::add);
+ newValue.add("urn:foo");
+ nsdata.setProperty(NamespaceConstants.REP_URIS, newValue, STRINGS);
+ store.merge(rootBuilder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ store.runBackgroundOperations();
+ //missing information: no automatic fix
+ testCmd(new String[] { MongoUtils.URL, "--analyse" }, new String[] {
"This namespace registry model is inconsistent. The inconsistency can NOT be
fixed." });
+ //consistent after removal of unmapped data.
+ testCmd(new String[] { MongoUtils.URL, "--analyse", "--prune" }, new
String[] { "This namespace registry model is consistent" });
+ //consistent with supplied complete information.
+ testCmd(new String[] { MongoUtils.URL, "--analyse", "--mappings",
"foo=urn:foo" }, new String[] { "This namespace registry model is consistent,
containing the following mappings from prefixes to namespace uris:", "foo ->
urn:foo" });
+ testCmd(new String[] { MongoUtils.URL, "--fix", "--read-write" }, new
String[] { "This namespace registry model is inconsistent. The inconsistency
can NOT be fixed." });
+ testCmd(new String[] { MongoUtils.URL, "--fix", "--read-write",
"--prune" }, new String[] { "This namespace registry model is consistent" });
+ }
+
@Test
public void mappings() throws Exception {
testCmd(new String[] { MongoUtils.URL, "--analyse" }, new String[] {
"This namespace registry model is consistent"});