Skip to content

Commit 006d7fa

Browse files
committed
OAK-11617: Provide oak-run commands to analyze and fix inconsistencies in the namespace registry
New class NamespaceRegistryModel for consistency tests and registry repair.
1 parent 9ef4707 commit 006d7fa

File tree

1 file changed

+183
-43
lines changed

1 file changed

+183
-43
lines changed

oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/name/ReadOnlyNamespaceRegistry.java

Lines changed: 183 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,27 @@
2525
import javax.jcr.RepositoryException;
2626
import javax.jcr.UnsupportedRepositoryOperationException;
2727

28-
import org.apache.jackrabbit.util.Text;
28+
import org.apache.jackrabbit.oak.commons.collections.IterableUtils;
29+
import org.apache.jackrabbit.oak.commons.collections.SetUtils;
30+
import org.apache.jackrabbit.oak.commons.collections.StreamUtils;
31+
import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants;
2932
import org.apache.jackrabbit.oak.api.PropertyState;
3033
import org.apache.jackrabbit.oak.api.Root;
3134
import org.apache.jackrabbit.oak.api.Tree;
3235
import org.apache.jackrabbit.oak.spi.namespace.NamespaceConstants;
36+
import org.apache.jackrabbit.util.Text;
3337
import org.jetbrains.annotations.NotNull;
3438
import org.slf4j.Logger;
3539
import org.slf4j.LoggerFactory;
3640

3741
import java.util.ArrayList;
3842
import java.util.Arrays;
43+
import java.util.HashMap;
44+
import java.util.HashSet;
3945
import java.util.List;
46+
import java.util.Map;
47+
import java.util.Objects;
48+
import java.util.Set;
4049
import java.util.stream.Collectors;
4150

4251
/**
@@ -130,56 +139,187 @@ public String getPrefix(String uri) throws NamespaceException {
130139
"No namespace prefix registered for URI " + uri);
131140
}
132141

133-
protected void checkConsistency() {
134-
final String jcrPrimaryType = "jcr:primaryType";
135-
List<String> prefixes = Arrays.asList(getPrefixes());
136-
List<String> encodedUris = Arrays.stream(getURIs()).map(Namespaces::encodeUri).collect(Collectors.toList());
137-
if (prefixes.size() != encodedUris.size()) {
138-
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());
139-
}
140-
int mappedPrefixCount = 0;
141-
for (PropertyState propertyState : namespaces.getProperties()) {
142-
String prefix = propertyState.getName();
143-
if (!prefix.equals(jcrPrimaryType)) {
144-
mappedPrefixCount++;
145-
if (!prefixes.contains(prefix)) {
146-
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);
142+
protected void checkConsistency() throws IllegalStateException {
143+
NamespaceRegistryModel model = NamespaceRegistryModel.create(namespaces);
144+
if (!model.isConsistent()) {
145+
LOG.warn("Namespace registry is inconsistent. "
146+
+ "Unregistered mapped prefixes: {}. "
147+
+ "Unregistered mapped namespaces: {}. "
148+
+ "Registered unmapped prefixes: {}. "
149+
+ "Registered unmapped namespaces: {}.",
150+
model.getUnregisteredMappedPrefixes(),
151+
model.getUnregisteredMappedNamespaces(),
152+
model.getRegisteredUnmappedPrefixes(),
153+
model.getRegisteredUnmappedNamespaces());
154+
}
155+
CONSISTENCY_CHECKED = true;
156+
}
157+
158+
protected static final class NamespaceRegistryModel {
159+
protected final Set<String> registeredPrefixes;
160+
protected final Set<String> registeredNamespacesEncoded;
161+
protected final Map<String, String> prefixToNamespaceMap;
162+
protected final Map<String, String> namespaceToPrefixMap;
163+
164+
protected Set<String> mappedPrefixes;
165+
protected Set<String> mappedNamespaces;
166+
protected Set<String> mappedToPrefixes;
167+
protected Set<String> mappedToNamespacesEncoded;
168+
protected Set<String> allPrefixes;
169+
protected Set<String> allNamespacesEncoded;
170+
protected Set<String> consistentPrefixes;
171+
protected Set<String> consistentNamespaces;
172+
protected int registrySize;
173+
174+
private boolean consistent = false;
175+
private boolean fixable = false;
176+
177+
private NamespaceRegistryModel(
178+
Set<String> registeredPrefixes, Set<String> registeredNamespacesEncoded,
179+
// prefixes to URIs
180+
Map<String, String> prefixToNamespaceMap,
181+
// encoded URIs to prefixes
182+
Map<String, String> namespaceToPrefixMap) {
183+
// ignore the empty namespace which is not mapped
184+
this.registeredPrefixes = registeredPrefixes.stream().filter(s -> !Objects.isNull(s) && s.isEmpty()).collect(Collectors.toSet());
185+
this.registeredNamespacesEncoded = registeredNamespacesEncoded.stream().filter(s -> !Objects.isNull(s) && s.isEmpty()).collect(Collectors.toSet());
186+
this.prefixToNamespaceMap = prefixToNamespaceMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
187+
this.namespaceToPrefixMap = namespaceToPrefixMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
188+
init();
189+
}
190+
191+
private void init() {
192+
this.mappedPrefixes = prefixToNamespaceMap.keySet();
193+
this.mappedNamespaces = namespaceToPrefixMap.keySet();
194+
this.mappedToPrefixes = new HashSet<>(namespaceToPrefixMap.values());
195+
this.mappedToNamespacesEncoded = prefixToNamespaceMap.values().stream().map(Namespaces::encodeUri).collect(Collectors.toSet());
196+
allPrefixes = SetUtils.union(SetUtils.union(registeredPrefixes, mappedPrefixes), mappedToPrefixes);
197+
allNamespacesEncoded = SetUtils.union(SetUtils.union(registeredNamespacesEncoded, mappedNamespaces), mappedToNamespacesEncoded);
198+
registrySize = Math.max(allPrefixes.size(), allNamespacesEncoded.size());
199+
consistentPrefixes = SetUtils.intersection(SetUtils.intersection(registeredPrefixes, mappedPrefixes), mappedToPrefixes);
200+
consistentNamespaces = SetUtils.intersection(SetUtils.intersection(registeredNamespacesEncoded, mappedNamespaces), mappedToNamespacesEncoded);
201+
consistent = consistentPrefixes.size() == consistentNamespaces.size()
202+
&& consistentPrefixes.size() == allPrefixes.size();
203+
if (consistent) {
204+
fixable = true;
205+
} else {
206+
// everything needs to be contained in at least one of the bijective mappings
207+
fixable = registrySize == SetUtils.union(mappedPrefixes, mappedToPrefixes).size()
208+
&& registrySize == SetUtils.union(mappedNamespaces, mappedToNamespacesEncoded).size();
209+
}
210+
}
211+
212+
static NamespaceRegistryModel create(Tree namespaces) {
213+
Tree nsdata = namespaces.getChild(REP_NSDATA);
214+
Map<String, String> prefixToNamespaceMap = new HashMap<>();
215+
Map<String, String> namespaceToPrefixMap = new HashMap<>();
216+
for (PropertyState propertyState : namespaces.getProperties()) {
217+
String prefix = propertyState.getName();
218+
if (!prefix.equals(NodeTypeConstants.REP_PRIMARY_TYPE)) {
219+
prefixToNamespaceMap.put(prefix, propertyState.getValue(STRING));
147220
}
148-
try {
149-
getURI(prefix);
150-
} catch (NamespaceException e) {
151-
LOG.error("The namespace registry is inconsistent: namespace prefix {} is not mapped to a namespace URI.", prefix);
221+
}
222+
for (PropertyState propertyState : nsdata.getProperties()) {
223+
String encodedUri = propertyState.getName();
224+
switch (encodedUri) {
225+
case REP_PREFIXES:
226+
case REP_URIS:
227+
case NodeTypeConstants.REP_PRIMARY_TYPE:
228+
break;
229+
default:
230+
namespaceToPrefixMap.put(encodedUri, propertyState.getValue(STRING));
152231
}
153232
}
233+
NamespaceRegistryModel model = new NamespaceRegistryModel(
234+
new HashSet<>(Arrays.asList(IterableUtils.toArray(nsdata.getProperty(REP_PREFIXES).getValue(STRINGS), String.class))),
235+
StreamUtils.toStream(nsdata.getProperty(REP_URIS).getValue(STRINGS)).map(Namespaces::encodeUri).collect(Collectors.toSet()),
236+
prefixToNamespaceMap, namespaceToPrefixMap);
237+
return model;
154238
}
155-
//prefixes contains the unmapped empty prefix
156-
if (mappedPrefixCount + 1 != prefixes.size()) {
157-
LOG.error("The namespace registry is inconsistent: found {} mapped namespace prefixes and {} registered namespace prefixes. The numbers have to be equal.", mappedPrefixCount, prefixes.size());
158-
}
159-
int mappedUriCount = 0;
160-
for (PropertyState propertyState : nsdata.getProperties()) {
161-
String encodedUri = propertyState.getName();
162-
switch (encodedUri) {
163-
case REP_PREFIXES:
164-
case REP_URIS:
165-
case jcrPrimaryType:
166-
break;
167-
default:
168-
mappedUriCount++;
169-
if (!encodedUris.contains(encodedUri)) {
170-
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);
239+
240+
NamespaceRegistryModel createFixedModel() {
241+
if (consistent) {
242+
return this;
243+
}
244+
if (!fixable) {
245+
return null;
246+
}
247+
HashSet<String> fixedRegisteredPrefixes = new HashSet<>();
248+
HashMap<String, String> fixedPrefixToNamespaceMap = new HashMap<>();
249+
for (String prefix : allPrefixes) {
250+
if (!mappedPrefixes.contains(prefix)) {
251+
for (Map.Entry<String, String> entry : namespaceToPrefixMap.entrySet()) {
252+
if (entry.getValue().equals(prefix)) {
253+
fixedPrefixToNamespaceMap.put(prefix, Text.unescapeIllegalJcrChars(entry.getKey()));
254+
fixedRegisteredPrefixes.add(prefix);
255+
break;
256+
}
171257
}
172-
try {
173-
getPrefix(Text.unescapeIllegalJcrChars(encodedUri));
174-
} catch (NamespaceException e) {
175-
LOG.error("The namespace registry is inconsistent: namespace URI {} is not mapped to a namespace prefix.", encodedUri);
258+
}
259+
}
260+
HashSet<String> fixedRegisteredNamespacesEncoded = new HashSet<>();
261+
HashMap<String, String> fixedNamespaceToPrefixMap = new HashMap<>();
262+
for (String encodedNamespace : allNamespacesEncoded) {
263+
if (!mappedNamespaces.contains(encodedNamespace)) {
264+
for (Map.Entry<String, String> entry : prefixToNamespaceMap.entrySet()) {
265+
if (Namespaces.encodeUri(entry.getValue()).equals(encodedNamespace)) {
266+
fixedNamespaceToPrefixMap.put(encodedNamespace, entry.getKey());
267+
fixedRegisteredNamespacesEncoded.add(encodedNamespace);
268+
break;
269+
}
176270
}
271+
}
177272
}
273+
return new NamespaceRegistryModel(fixedRegisteredPrefixes, fixedRegisteredNamespacesEncoded,
274+
fixedPrefixToNamespaceMap, fixedNamespaceToPrefixMap);
178275
}
179-
//encodedUris contains the unmapped empty namespace URI
180-
if (mappedUriCount + 1 != encodedUris.size()) {
181-
LOG.error("The namespace registry is inconsistent: found {} mapped namespace URIs and {} registered namespace URIs. The numbers have to be equal.", mappedUriCount, encodedUris.size());
276+
277+
boolean isConsistent() {
278+
return consistent;
279+
}
280+
281+
public boolean isFixable() {
282+
return fixable;
283+
}
284+
285+
Set<String> getUnregisteredMappedPrefixes() {
286+
return SetUtils.difference(mappedPrefixes, registeredPrefixes);
287+
}
288+
289+
Set<String> getRegisteredUnmappedPrefixes() {
290+
return SetUtils.difference(registeredPrefixes, mappedPrefixes);
291+
}
292+
293+
Set<String> getUnregisteredMappedNamespaces() {
294+
return SetUtils.difference(mappedNamespaces, registeredNamespacesEncoded);
295+
}
296+
297+
Set<String> getRegisteredUnmappedNamespaces() {
298+
return SetUtils.difference(registeredNamespacesEncoded, mappedNamespaces);
299+
}
300+
301+
Set<String> getRegisteredPrefixes() {
302+
return registeredPrefixes;
303+
}
304+
305+
Set<String> getRegisteredNamespacesEncoded() {
306+
return registeredNamespacesEncoded;
307+
}
308+
309+
Set<String> getMappedPrefixes() {
310+
return mappedPrefixes;
311+
}
312+
313+
Set<String> getMappedNamespaces() {
314+
return mappedNamespaces;
315+
}
316+
317+
Set<String> getAllPrefixes() {
318+
return allPrefixes;
319+
}
320+
321+
Set<String> getAllNamespaces() {
322+
return allNamespacesEncoded;
182323
}
183-
CONSISTENCY_CHECKED = true;
184324
}
185325
}

0 commit comments

Comments
 (0)