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 <[email protected]>
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;
     }
 }

Reply via email to