|
25 | 25 | import javax.jcr.RepositoryException; |
26 | 26 | import javax.jcr.UnsupportedRepositoryOperationException; |
27 | 27 |
|
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; |
29 | 32 | import org.apache.jackrabbit.oak.api.PropertyState; |
30 | 33 | import org.apache.jackrabbit.oak.api.Root; |
31 | 34 | import org.apache.jackrabbit.oak.api.Tree; |
32 | 35 | import org.apache.jackrabbit.oak.spi.namespace.NamespaceConstants; |
| 36 | +import org.apache.jackrabbit.util.Text; |
33 | 37 | import org.jetbrains.annotations.NotNull; |
34 | 38 | import org.slf4j.Logger; |
35 | 39 | import org.slf4j.LoggerFactory; |
36 | 40 |
|
37 | 41 | import java.util.ArrayList; |
38 | 42 | import java.util.Arrays; |
| 43 | +import java.util.HashMap; |
| 44 | +import java.util.HashSet; |
39 | 45 | import java.util.List; |
| 46 | +import java.util.Map; |
| 47 | +import java.util.Objects; |
| 48 | +import java.util.Set; |
40 | 49 | import java.util.stream.Collectors; |
41 | 50 |
|
42 | 51 | /** |
@@ -130,56 +139,187 @@ public String getPrefix(String uri) throws NamespaceException { |
130 | 139 | "No namespace prefix registered for URI " + uri); |
131 | 140 | } |
132 | 141 |
|
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)); |
147 | 220 | } |
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)); |
152 | 231 | } |
153 | 232 | } |
| 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; |
154 | 238 | } |
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 | + } |
171 | 257 | } |
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 | + } |
176 | 270 | } |
| 271 | + } |
177 | 272 | } |
| 273 | + return new NamespaceRegistryModel(fixedRegisteredPrefixes, fixedRegisteredNamespacesEncoded, |
| 274 | + fixedPrefixToNamespaceMap, fixedNamespaceToPrefixMap); |
178 | 275 | } |
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; |
182 | 323 | } |
183 | | - CONSISTENCY_CHECKED = true; |
184 | 324 | } |
185 | 325 | } |
0 commit comments