Skip to content

Commit 838f125

Browse files
committed
OAK-11617: Provide oak-run commands to analyze and fix inconsistencies in the namespace registry
Working prototype with basic test case.
1 parent 006d7fa commit 838f125

File tree

3 files changed

+118
-13
lines changed

3 files changed

+118
-13
lines changed

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

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,10 @@ public String getPrefix(String uri) throws NamespaceException {
139139
"No namespace prefix registered for URI " + uri);
140140
}
141141

142-
protected void checkConsistency() throws IllegalStateException {
142+
public boolean checkConsistency() throws IllegalStateException {
143143
NamespaceRegistryModel model = NamespaceRegistryModel.create(namespaces);
144-
if (!model.isConsistent()) {
144+
boolean consistent = model.isConsistent();
145+
if (!consistent) {
145146
LOG.warn("Namespace registry is inconsistent. "
146147
+ "Unregistered mapped prefixes: {}. "
147148
+ "Unregistered mapped namespaces: {}. "
@@ -152,7 +153,11 @@ protected void checkConsistency() throws IllegalStateException {
152153
model.getRegisteredUnmappedPrefixes(),
153154
model.getRegisteredUnmappedNamespaces());
154155
}
155-
CONSISTENCY_CHECKED = true;
156+
return consistent;
157+
}
158+
159+
public NamespaceRegistryModel createNamespaceRegistryModel() {
160+
return NamespaceRegistryModel.create(namespaces);
156161
}
157162

158163
protected static final class NamespaceRegistryModel {
@@ -181,8 +186,8 @@ private NamespaceRegistryModel(
181186
// encoded URIs to prefixes
182187
Map<String, String> namespaceToPrefixMap) {
183188
// 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());
189+
this.registeredPrefixes = registeredPrefixes.stream().filter(s -> !(Objects.isNull(s) || s.isEmpty())).collect(Collectors.toSet());
190+
this.registeredNamespacesEncoded = registeredNamespacesEncoded.stream().filter(s -> !(Objects.isNull(s) || s.isEmpty())).collect(Collectors.toSet());
186191
this.prefixToNamespaceMap = prefixToNamespaceMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
187192
this.namespaceToPrefixMap = namespaceToPrefixMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
188193
init();
@@ -215,7 +220,7 @@ static NamespaceRegistryModel create(Tree namespaces) {
215220
Map<String, String> namespaceToPrefixMap = new HashMap<>();
216221
for (PropertyState propertyState : namespaces.getProperties()) {
217222
String prefix = propertyState.getName();
218-
if (!prefix.equals(NodeTypeConstants.REP_PRIMARY_TYPE)) {
223+
if (!prefix.equals(NodeTypeConstants.JCR_PRIMARYTYPE)) {
219224
prefixToNamespaceMap.put(prefix, propertyState.getValue(STRING));
220225
}
221226
}
@@ -224,20 +229,21 @@ static NamespaceRegistryModel create(Tree namespaces) {
224229
switch (encodedUri) {
225230
case REP_PREFIXES:
226231
case REP_URIS:
227-
case NodeTypeConstants.REP_PRIMARY_TYPE:
232+
case NodeTypeConstants.JCR_PRIMARYTYPE:
228233
break;
229234
default:
230235
namespaceToPrefixMap.put(encodedUri, propertyState.getValue(STRING));
231236
}
232237
}
238+
Iterable<String> uris = nsdata.getProperty(REP_URIS).getValue(STRINGS);
233239
NamespaceRegistryModel model = new NamespaceRegistryModel(
234240
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()),
241+
StreamUtils.toStream(uris).map(Namespaces::encodeUri).collect(Collectors.toSet()),
236242
prefixToNamespaceMap, namespaceToPrefixMap);
237243
return model;
238244
}
239245

240-
NamespaceRegistryModel createFixedModel() {
246+
NamespaceRegistryModel tryRegistryRepair() {
241247
if (consistent) {
242248
return this;
243249
}
@@ -247,11 +253,13 @@ NamespaceRegistryModel createFixedModel() {
247253
HashSet<String> fixedRegisteredPrefixes = new HashSet<>();
248254
HashMap<String, String> fixedPrefixToNamespaceMap = new HashMap<>();
249255
for (String prefix : allPrefixes) {
250-
if (!mappedPrefixes.contains(prefix)) {
256+
fixedRegisteredPrefixes.add(prefix);
257+
if (mappedPrefixes.contains(prefix)) {
258+
fixedPrefixToNamespaceMap.put(prefix, prefixToNamespaceMap.get(prefix));
259+
} else {
251260
for (Map.Entry<String, String> entry : namespaceToPrefixMap.entrySet()) {
252261
if (entry.getValue().equals(prefix)) {
253262
fixedPrefixToNamespaceMap.put(prefix, Text.unescapeIllegalJcrChars(entry.getKey()));
254-
fixedRegisteredPrefixes.add(prefix);
255263
break;
256264
}
257265
}
@@ -260,11 +268,13 @@ NamespaceRegistryModel createFixedModel() {
260268
HashSet<String> fixedRegisteredNamespacesEncoded = new HashSet<>();
261269
HashMap<String, String> fixedNamespaceToPrefixMap = new HashMap<>();
262270
for (String encodedNamespace : allNamespacesEncoded) {
263-
if (!mappedNamespaces.contains(encodedNamespace)) {
271+
fixedRegisteredNamespacesEncoded.add(encodedNamespace);
272+
if (mappedNamespaces.contains(encodedNamespace)) {
273+
fixedNamespaceToPrefixMap.put(encodedNamespace, namespaceToPrefixMap.get(encodedNamespace));
274+
} else {
264275
for (Map.Entry<String, String> entry : prefixToNamespaceMap.entrySet()) {
265276
if (Namespaces.encodeUri(entry.getValue()).equals(encodedNamespace)) {
266277
fixedNamespaceToPrefixMap.put(encodedNamespace, entry.getKey());
267-
fixedRegisteredNamespacesEncoded.add(encodedNamespace);
268278
break;
269279
}
270280
}

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
*/
1717
package org.apache.jackrabbit.oak.plugins.name;
1818

19+
import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
1920
import static org.apache.jackrabbit.oak.api.Type.STRING;
21+
import static org.apache.jackrabbit.oak.api.Type.STRINGS;
2022

2123
import javax.jcr.NamespaceException;
2224
import javax.jcr.RepositoryException;
@@ -29,6 +31,8 @@
2931
import org.slf4j.Logger;
3032
import org.slf4j.LoggerFactory;
3133

34+
import java.util.Map;
35+
3236

3337
/**
3438
* Writable namespace registry. Mainly for use to implement the full JCR API.
@@ -141,4 +145,41 @@ public void unregisterNamespace(String prefix) throws RepositoryException {
141145
}
142146
}
143147

148+
protected void applyModel(NamespaceRegistryModel model) throws RepositoryException, CommitFailedException {
149+
if (model == null) {
150+
throw new IllegalArgumentException("Model must not be null");
151+
}
152+
if (!model.isConsistent()) {
153+
throw new IllegalArgumentException("Model is not consistent");
154+
}
155+
156+
for (PropertyState propertyState : namespaces.getProperties()) {
157+
String name = propertyState.getName();
158+
if (!JCR_PRIMARYTYPE.equals(name)) {
159+
namespaces.removeProperty(name);
160+
}
161+
}
162+
for (Map.Entry<String, String> entry : model.prefixToNamespaceMap.entrySet()) {
163+
String prefix = entry.getKey();
164+
String uri = entry.getValue();
165+
namespaces.setProperty(prefix, uri);
166+
}
167+
for (PropertyState propertyState : nsdata.getProperties()) {
168+
String name = propertyState.getName();
169+
if (!JCR_PRIMARYTYPE.equals(name)) {
170+
nsdata.removeProperty(name);
171+
}
172+
}
173+
for (Map.Entry<String, String> entry : model.namespaceToPrefixMap.entrySet()) {
174+
String encodedUri = entry.getKey();
175+
String prefix = entry.getValue();
176+
nsdata.setProperty(encodedUri, prefix);
177+
}
178+
nsdata.setProperty(REP_PREFIXES, model.mappedPrefixes, STRINGS);
179+
nsdata.setProperty(REP_URIS, model.prefixToNamespaceMap.values(), STRINGS);
180+
if (!checkConsistency()) {
181+
throw new IllegalStateException("Final registry consistency check failed.");
182+
}
183+
getWriteRoot().commit();
184+
}
144185
}

oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/name/ReadWriteNamespaceRegistryTest.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@
1616
*/
1717
package org.apache.jackrabbit.oak.plugins.name;
1818

19+
import static org.apache.jackrabbit.oak.spi.namespace.NamespaceConstants.REP_NSDATA;
20+
import static org.apache.jackrabbit.oak.spi.namespace.NamespaceConstants.REP_PREFIXES;
1921
import static org.junit.Assert.assertEquals;
22+
import static org.junit.Assert.assertFalse;
23+
import static org.junit.Assert.assertNotNull;
24+
import static org.junit.Assert.assertNull;
2025
import static org.junit.Assert.assertTrue;
2126
import static org.junit.Assert.fail;
2227

@@ -29,10 +34,14 @@
2934
import org.apache.jackrabbit.oak.Oak;
3035
import org.apache.jackrabbit.oak.OakBaseTest;
3136
import org.apache.jackrabbit.oak.api.ContentSession;
37+
import org.apache.jackrabbit.oak.api.PropertyState;
3238
import org.apache.jackrabbit.oak.api.Root;
39+
import org.apache.jackrabbit.oak.api.Tree;
40+
import org.apache.jackrabbit.oak.api.Type;
3341
import org.apache.jackrabbit.oak.commons.collections.SetUtils;
3442
import org.apache.jackrabbit.oak.commons.junit.LogCustomizer;
3543
import org.apache.jackrabbit.oak.fixture.NodeStoreFixture;
44+
import org.apache.jackrabbit.oak.plugins.memory.PropertyBuilder;
3645
import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
3746
import org.junit.Test;
3847
import org.slf4j.event.Level;
@@ -92,6 +101,10 @@ public void testInvalidNamespace() throws Exception {
92101
final Root root = session.getLatestRoot();
93102
NamespaceRegistry r = getNamespaceRegistry(session, root);
94103

104+
ReadOnlyNamespaceRegistry readOnlyNamespaceRegistry = (ReadOnlyNamespaceRegistry) r;
105+
readOnlyNamespaceRegistry.checkConsistency();
106+
ReadOnlyNamespaceRegistry.NamespaceRegistryModel model = readOnlyNamespaceRegistry.createNamespaceRegistryModel();
107+
95108
LogCustomizer customLogs = LogCustomizer.forLogger("org.apache.jackrabbit.oak.plugins.name.ReadWriteNamespaceRegistry").enable(Level.ERROR).create();
96109
try {
97110
customLogs.starting();
@@ -106,6 +119,47 @@ public void testInvalidNamespace() throws Exception {
106119
}
107120
}
108121

122+
@Test
123+
public void testNamespaceRegistryModel() throws Exception {
124+
final ContentSession session = createContentSession();
125+
final Root root = session.getLatestRoot();
126+
ReadWriteNamespaceRegistry registry = (ReadWriteNamespaceRegistry) getNamespaceRegistry(session, root);
127+
Tree namespaces = root.getTree("/jcr:system/rep:namespaces");
128+
Tree nsdata = namespaces.getChild(REP_NSDATA);
129+
PropertyState prefixes = nsdata.getProperty(REP_PREFIXES);
130+
131+
assertTrue(registry.checkConsistency());
132+
ReadOnlyNamespaceRegistry.NamespaceRegistryModel model = registry.createNamespaceRegistryModel();
133+
assertTrue(model.isConsistent());
134+
assertTrue(model.isFixable());
135+
136+
PropertyBuilder<String> builder = PropertyBuilder.copy(Type.STRING, prefixes);
137+
builder.addValue("foo");
138+
nsdata.setProperty(builder.getPropertyState());
139+
140+
assertFalse(registry.checkConsistency());
141+
model = registry.createNamespaceRegistryModel();
142+
assertFalse(model.isConsistent());
143+
assertFalse(model.isFixable());
144+
145+
ReadOnlyNamespaceRegistry.NamespaceRegistryModel fixedModel = model.tryRegistryRepair();
146+
assertNull(fixedModel);
147+
148+
namespaces.setProperty("foo", "urn:foo", Type.STRING);
149+
assertFalse(registry.checkConsistency());
150+
model = registry.createNamespaceRegistryModel();
151+
assertFalse(model.isConsistent());
152+
assertTrue(model.isFixable());
153+
154+
fixedModel = model.tryRegistryRepair();
155+
assertNotNull(fixedModel);
156+
assertTrue(fixedModel.isConsistent());
157+
assertTrue(fixedModel.isFixable());
158+
159+
registry.applyModel(fixedModel);
160+
assertTrue(registry.checkConsistency());
161+
}
162+
109163
private static NamespaceRegistry getNamespaceRegistry(ContentSession session, Root root) {
110164
return new ReadWriteNamespaceRegistry(root) {
111165
@Override

0 commit comments

Comments
 (0)