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 2025/05/13 12:40:31 UTC
(jackrabbit-oak) branch OAK-11617 updated: OAK-11617: Provide oak-run commands to analyze and fix inconsistencies in the namespace registry
This is an automated email from the ASF dual-hosted git repository.
baedke pushed a commit to branch OAK-11617
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
The following commit(s) were added to refs/heads/OAK-11617 by this push:
new 006d7fa909 OAK-11617: Provide oak-run commands to analyze and fix inconsistencies in the namespace registry
006d7fa909 is described below
commit 006d7fa9098421a03a35bacfff5321309dd12a0e
Author: Manfred Baedke <ma...@gmail.com>
AuthorDate: Tue May 13 14:38:33 2025 +0200
OAK-11617: Provide oak-run commands to analyze and fix inconsistencies in the namespace registry
New class NamespaceRegistryModel for consistency tests and registry repair.
---
.../plugins/name/ReadOnlyNamespaceRegistry.java | 226 +++++++++++++++++----
1 file changed, 183 insertions(+), 43 deletions(-)
diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/ReadOnlyNamespaceRegistry.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/ReadOnlyNamespaceRegistry.java
index ddf6509ce0..34871ca438 100644
--- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/ReadOnlyNamespaceRegistry.java
+++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/ReadOnlyNamespaceRegistry.java
@@ -25,18 +25,27 @@ import javax.jcr.NamespaceRegistry;
import javax.jcr.RepositoryException;
import javax.jcr.UnsupportedRepositoryOperationException;
-import org.apache.jackrabbit.util.Text;
+import org.apache.jackrabbit.oak.commons.collections.IterableUtils;
+import org.apache.jackrabbit.oak.commons.collections.SetUtils;
+import org.apache.jackrabbit.oak.commons.collections.StreamUtils;
+import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Root;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.spi.namespace.NamespaceConstants;
+import org.apache.jackrabbit.util.Text;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -130,56 +139,187 @@ public class ReadOnlyNamespaceRegistry
"No namespace prefix registered for URI " + uri);
}
- protected void checkConsistency() {
- final String jcrPrimaryType = "jcr:primaryType";
- List<String> prefixes = Arrays.asList(getPrefixes());
- List<String> encodedUris = Arrays.stream(getURIs()).map(Namespaces::encodeUri).collect(Collectors.toList());
- if (prefixes.size() != encodedUris.size()) {
- LOG.error("The namespace registry is inconsistent: found {} registered namespace prefixes and {} registered namespace URIs. The numbers have to be equal.", prefixes.size(), encodedUris.size());
- }
- int mappedPrefixCount = 0;
- for (PropertyState propertyState : namespaces.getProperties()) {
- String prefix = propertyState.getName();
- if (!prefix.equals(jcrPrimaryType)) {
- mappedPrefixCount++;
- if (!prefixes.contains(prefix)) {
- LOG.error("The namespace registry is inconsistent: namespace prefix {} is mapped to a namespace URI, but not contained in the list of registered namespace prefixes.", prefix);
+ protected void checkConsistency() throws IllegalStateException {
+ NamespaceRegistryModel model = NamespaceRegistryModel.create(namespaces);
+ if (!model.isConsistent()) {
+ LOG.warn("Namespace registry is inconsistent. "
+ + "Unregistered mapped prefixes: {}. "
+ + "Unregistered mapped namespaces: {}. "
+ + "Registered unmapped prefixes: {}. "
+ + "Registered unmapped namespaces: {}.",
+ model.getUnregisteredMappedPrefixes(),
+ model.getUnregisteredMappedNamespaces(),
+ model.getRegisteredUnmappedPrefixes(),
+ model.getRegisteredUnmappedNamespaces());
+ }
+ CONSISTENCY_CHECKED = true;
+ }
+
+ protected static final class NamespaceRegistryModel {
+ protected final Set<String> registeredPrefixes;
+ protected final Set<String> registeredNamespacesEncoded;
+ protected final Map<String, String> prefixToNamespaceMap;
+ protected final Map<String, String> namespaceToPrefixMap;
+
+ protected Set<String> mappedPrefixes;
+ protected Set<String> mappedNamespaces;
+ protected Set<String> mappedToPrefixes;
+ protected Set<String> mappedToNamespacesEncoded;
+ protected Set<String> allPrefixes;
+ protected Set<String> allNamespacesEncoded;
+ protected Set<String> consistentPrefixes;
+ protected Set<String> consistentNamespaces;
+ protected int registrySize;
+
+ private boolean consistent = false;
+ private boolean fixable = false;
+
+ private NamespaceRegistryModel(
+ Set<String> registeredPrefixes, Set<String> registeredNamespacesEncoded,
+ // prefixes to URIs
+ Map<String, String> prefixToNamespaceMap,
+ // encoded URIs to prefixes
+ Map<String, String> namespaceToPrefixMap) {
+ // ignore the empty namespace which is not mapped
+ this.registeredPrefixes = registeredPrefixes.stream().filter(s -> !Objects.isNull(s) && s.isEmpty()).collect(Collectors.toSet());
+ this.registeredNamespacesEncoded = registeredNamespacesEncoded.stream().filter(s -> !Objects.isNull(s) && s.isEmpty()).collect(Collectors.toSet());
+ this.prefixToNamespaceMap = prefixToNamespaceMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ this.namespaceToPrefixMap = namespaceToPrefixMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ init();
+ }
+
+ private void init() {
+ this.mappedPrefixes = prefixToNamespaceMap.keySet();
+ this.mappedNamespaces = namespaceToPrefixMap.keySet();
+ this.mappedToPrefixes = new HashSet<>(namespaceToPrefixMap.values());
+ this.mappedToNamespacesEncoded = prefixToNamespaceMap.values().stream().map(Namespaces::encodeUri).collect(Collectors.toSet());
+ allPrefixes = SetUtils.union(SetUtils.union(registeredPrefixes, mappedPrefixes), mappedToPrefixes);
+ allNamespacesEncoded = SetUtils.union(SetUtils.union(registeredNamespacesEncoded, mappedNamespaces), mappedToNamespacesEncoded);
+ registrySize = Math.max(allPrefixes.size(), allNamespacesEncoded.size());
+ consistentPrefixes = SetUtils.intersection(SetUtils.intersection(registeredPrefixes, mappedPrefixes), mappedToPrefixes);
+ consistentNamespaces = SetUtils.intersection(SetUtils.intersection(registeredNamespacesEncoded, mappedNamespaces), mappedToNamespacesEncoded);
+ consistent = consistentPrefixes.size() == consistentNamespaces.size()
+ && consistentPrefixes.size() == allPrefixes.size();
+ if (consistent) {
+ fixable = true;
+ } else {
+ // everything needs to be contained in at least one of the bijective mappings
+ fixable = registrySize == SetUtils.union(mappedPrefixes, mappedToPrefixes).size()
+ && registrySize == SetUtils.union(mappedNamespaces, mappedToNamespacesEncoded).size();
+ }
+ }
+
+ static NamespaceRegistryModel create(Tree namespaces) {
+ Tree nsdata = namespaces.getChild(REP_NSDATA);
+ Map<String, String> prefixToNamespaceMap = new HashMap<>();
+ Map<String, String> namespaceToPrefixMap = new HashMap<>();
+ for (PropertyState propertyState : namespaces.getProperties()) {
+ String prefix = propertyState.getName();
+ if (!prefix.equals(NodeTypeConstants.REP_PRIMARY_TYPE)) {
+ prefixToNamespaceMap.put(prefix, propertyState.getValue(STRING));
}
- try {
- getURI(prefix);
- } catch (NamespaceException e) {
- LOG.error("The namespace registry is inconsistent: namespace prefix {} is not mapped to a namespace URI.", prefix);
+ }
+ for (PropertyState propertyState : nsdata.getProperties()) {
+ String encodedUri = propertyState.getName();
+ switch (encodedUri) {
+ case REP_PREFIXES:
+ case REP_URIS:
+ case NodeTypeConstants.REP_PRIMARY_TYPE:
+ break;
+ default:
+ namespaceToPrefixMap.put(encodedUri, propertyState.getValue(STRING));
}
}
+ NamespaceRegistryModel model = new NamespaceRegistryModel(
+ new HashSet<>(Arrays.asList(IterableUtils.toArray(nsdata.getProperty(REP_PREFIXES).getValue(STRINGS), String.class))),
+ StreamUtils.toStream(nsdata.getProperty(REP_URIS).getValue(STRINGS)).map(Namespaces::encodeUri).collect(Collectors.toSet()),
+ prefixToNamespaceMap, namespaceToPrefixMap);
+ return model;
}
- //prefixes contains the unmapped empty prefix
- if (mappedPrefixCount + 1 != prefixes.size()) {
- LOG.error("The namespace registry is inconsistent: found {} mapped namespace prefixes and {} registered namespace prefixes. The numbers have to be equal.", mappedPrefixCount, prefixes.size());
- }
- int mappedUriCount = 0;
- for (PropertyState propertyState : nsdata.getProperties()) {
- String encodedUri = propertyState.getName();
- switch (encodedUri) {
- case REP_PREFIXES:
- case REP_URIS:
- case jcrPrimaryType:
- break;
- default:
- mappedUriCount++;
- if (!encodedUris.contains(encodedUri)) {
- LOG.error("The namespace registry is inconsistent: encoded namespace URI {} is mapped to a namespace prefix, but not contained in the list of registered namespace URIs.", encodedUri);
+
+ NamespaceRegistryModel createFixedModel() {
+ if (consistent) {
+ return this;
+ }
+ if (!fixable) {
+ return null;
+ }
+ HashSet<String> fixedRegisteredPrefixes = new HashSet<>();
+ HashMap<String, String> fixedPrefixToNamespaceMap = new HashMap<>();
+ for (String prefix : allPrefixes) {
+ if (!mappedPrefixes.contains(prefix)) {
+ for (Map.Entry<String, String> entry : namespaceToPrefixMap.entrySet()) {
+ if (entry.getValue().equals(prefix)) {
+ fixedPrefixToNamespaceMap.put(prefix, Text.unescapeIllegalJcrChars(entry.getKey()));
+ fixedRegisteredPrefixes.add(prefix);
+ break;
+ }
}
- try {
- getPrefix(Text.unescapeIllegalJcrChars(encodedUri));
- } catch (NamespaceException e) {
- LOG.error("The namespace registry is inconsistent: namespace URI {} is not mapped to a namespace prefix.", encodedUri);
+ }
+ }
+ HashSet<String> fixedRegisteredNamespacesEncoded = new HashSet<>();
+ HashMap<String, String> fixedNamespaceToPrefixMap = new HashMap<>();
+ for (String encodedNamespace : allNamespacesEncoded) {
+ if (!mappedNamespaces.contains(encodedNamespace)) {
+ for (Map.Entry<String, String> entry : prefixToNamespaceMap.entrySet()) {
+ if (Namespaces.encodeUri(entry.getValue()).equals(encodedNamespace)) {
+ fixedNamespaceToPrefixMap.put(encodedNamespace, entry.getKey());
+ fixedRegisteredNamespacesEncoded.add(encodedNamespace);
+ break;
+ }
}
+ }
}
+ return new NamespaceRegistryModel(fixedRegisteredPrefixes, fixedRegisteredNamespacesEncoded,
+ fixedPrefixToNamespaceMap, fixedNamespaceToPrefixMap);
}
- //encodedUris contains the unmapped empty namespace URI
- if (mappedUriCount + 1 != encodedUris.size()) {
- LOG.error("The namespace registry is inconsistent: found {} mapped namespace URIs and {} registered namespace URIs. The numbers have to be equal.", mappedUriCount, encodedUris.size());
+
+ boolean isConsistent() {
+ return consistent;
+ }
+
+ public boolean isFixable() {
+ return fixable;
+ }
+
+ Set<String> getUnregisteredMappedPrefixes() {
+ return SetUtils.difference(mappedPrefixes, registeredPrefixes);
+ }
+
+ Set<String> getRegisteredUnmappedPrefixes() {
+ return SetUtils.difference(registeredPrefixes, mappedPrefixes);
+ }
+
+ Set<String> getUnregisteredMappedNamespaces() {
+ return SetUtils.difference(mappedNamespaces, registeredNamespacesEncoded);
+ }
+
+ Set<String> getRegisteredUnmappedNamespaces() {
+ return SetUtils.difference(registeredNamespacesEncoded, mappedNamespaces);
+ }
+
+ Set<String> getRegisteredPrefixes() {
+ return registeredPrefixes;
+ }
+
+ Set<String> getRegisteredNamespacesEncoded() {
+ return registeredNamespacesEncoded;
+ }
+
+ Set<String> getMappedPrefixes() {
+ return mappedPrefixes;
+ }
+
+ Set<String> getMappedNamespaces() {
+ return mappedNamespaces;
+ }
+
+ Set<String> getAllPrefixes() {
+ return allPrefixes;
+ }
+
+ Set<String> getAllNamespaces() {
+ return allNamespacesEncoded;
}
- CONSISTENCY_CHECKED = true;
}
}