001 /* 002 * JBoss DNA (http://www.jboss.org/dna) 003 * See the COPYRIGHT.txt file distributed with this work for information 004 * regarding copyright ownership. Some portions may be licensed 005 * to Red Hat, Inc. under one or more contributor license agreements. 006 * See the AUTHORS.txt file in the distribution for a full listing of 007 * individual contributors. 008 * 009 * Unless otherwise indicated, all code in JBoss DNA is licensed 010 * to you under the terms of the GNU Lesser General Public License as 011 * published by the Free Software Foundation; either version 2.1 of 012 * the License, or (at your option) any later version. 013 * 014 * JBoss DNA is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 017 * Lesser General Public License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this software; if not, write to the Free 021 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 022 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 023 */ 024 package org.jboss.dna.graph.property.basic; 025 026 import java.util.ArrayList; 027 import java.util.Collections; 028 import java.util.HashSet; 029 import java.util.List; 030 import java.util.Set; 031 import net.jcip.annotations.NotThreadSafe; 032 import org.jboss.dna.common.util.CheckArg; 033 import org.jboss.dna.graph.DnaLexicon; 034 import org.jboss.dna.graph.Graph; 035 import org.jboss.dna.graph.JcrLexicon; 036 import org.jboss.dna.graph.Location; 037 import org.jboss.dna.graph.Node; 038 import org.jboss.dna.graph.Subgraph; 039 import org.jboss.dna.graph.property.Name; 040 import org.jboss.dna.graph.property.NamespaceRegistry; 041 import org.jboss.dna.graph.property.Path; 042 import org.jboss.dna.graph.property.PathFactory; 043 import org.jboss.dna.graph.property.PathNotFoundException; 044 import org.jboss.dna.graph.property.Property; 045 import org.jboss.dna.graph.property.ValueFactory; 046 047 /** 048 * A {@link NamespaceRegistry} implementation that stores the namespaces in a Graph as individual nodes for each namespace, under 049 * a parent supplied by the constructor. 050 * 051 * @See {@link ThreadSafeNamespaceRegistry} 052 */ 053 @NotThreadSafe 054 public class GraphNamespaceRegistry implements NamespaceRegistry { 055 056 public static final Name DEFAULT_URI_PROPERTY_NAME = DnaLexicon.NAMESPACE_URI; 057 public static final String GENERATED_PREFIX = "ns"; 058 059 private SimpleNamespaceRegistry cache; 060 private final Graph store; 061 private final Path parentOfNamespaceNodes; 062 private final Name uriPropertyName; 063 private final List<Property> namespaceProperties; 064 065 public GraphNamespaceRegistry( Graph store, 066 Path parentOfNamespaceNodes, 067 Name uriPropertyName, 068 Property... additionalProperties ) { 069 this.cache = new SimpleNamespaceRegistry(); 070 this.store = store; 071 this.parentOfNamespaceNodes = parentOfNamespaceNodes; 072 this.uriPropertyName = uriPropertyName != null ? uriPropertyName : DEFAULT_URI_PROPERTY_NAME; 073 List<Property> properties = Collections.emptyList(); 074 if (additionalProperties != null && additionalProperties.length != 0) { 075 properties = new ArrayList<Property>(additionalProperties.length); 076 Set<Name> propertyNames = new HashSet<Name>(); 077 for (Property property : additionalProperties) { 078 if (!propertyNames.contains(property.getName())) properties.add(property); 079 } 080 } 081 this.namespaceProperties = Collections.unmodifiableList(properties); 082 createNamespaceParentIfNeeded(); 083 initializeCacheFromStore(cache); 084 } 085 086 private void createNamespaceParentIfNeeded() { 087 try { 088 this.store.getNodeAt(this.parentOfNamespaceNodes); 089 } catch (PathNotFoundException pnfe) { 090 // The node did not already exist - create it! 091 this.store.create(parentOfNamespaceNodes); 092 this.store.set(JcrLexicon.PRIMARY_TYPE).on(parentOfNamespaceNodes).to(DnaLexicon.NAMESPACES); 093 } 094 } 095 096 /** 097 * {@inheritDoc} 098 */ 099 public String getNamespaceForPrefix( String prefix ) { 100 CheckArg.isNotNull(prefix, "prefix"); 101 // Try the cache first ... 102 String uri = cache.getNamespaceForPrefix(prefix); 103 if (uri == null) { 104 // See if the store has it ... 105 uri = readUriFor(prefix); 106 if (uri != null) { 107 // update the cache ... 108 cache.register(prefix, uri); 109 } 110 } 111 return uri; 112 } 113 114 /** 115 * {@inheritDoc} 116 */ 117 public String getPrefixForNamespaceUri( String namespaceUri, 118 boolean generateIfMissing ) { 119 CheckArg.isNotNull(namespaceUri, "namespaceUri"); 120 // Try the cache first ... 121 String prefix = cache.getPrefixForNamespaceUri(namespaceUri, false); 122 if (prefix == null && generateIfMissing) { 123 prefix = readPrefixFor(namespaceUri, generateIfMissing); 124 if (prefix != null) { 125 cache.register(prefix, namespaceUri); 126 } 127 } 128 return prefix; 129 } 130 131 /** 132 * {@inheritDoc} 133 */ 134 public boolean isRegisteredNamespaceUri( String namespaceUri ) { 135 CheckArg.isNotNull(namespaceUri, "namespaceUri"); 136 if (cache.isRegisteredNamespaceUri(namespaceUri)) return true; 137 // Otherwise it was not found in the cache, so check the store ... 138 String prefix = readPrefixFor(namespaceUri, false); 139 if (prefix != null) { 140 cache.register(prefix, namespaceUri); 141 return true; 142 } 143 return false; 144 } 145 146 /** 147 * {@inheritDoc} 148 */ 149 public String getDefaultNamespaceUri() { 150 return this.getNamespaceForPrefix(""); 151 } 152 153 /** 154 * {@inheritDoc} 155 */ 156 public String register( String prefix, 157 String namespaceUri ) { 158 CheckArg.isNotNull(namespaceUri, "namespaceUri"); 159 namespaceUri = namespaceUri.trim(); 160 // Register it in the cache first ... 161 String previousCachedUriForPrefix = this.cache.register(prefix, namespaceUri); 162 // And register it in the source ... 163 String previousPersistentUriForPrefix = doRegister(prefix, namespaceUri); 164 return previousCachedUriForPrefix != null ? previousPersistentUriForPrefix : previousPersistentUriForPrefix; 165 } 166 167 /** 168 * {@inheritDoc} 169 * 170 * @see org.jboss.dna.graph.property.NamespaceRegistry#unregister(java.lang.String) 171 */ 172 public boolean unregister( String namespaceUri ) { 173 CheckArg.isNotNull(namespaceUri, "namespaceUri"); 174 namespaceUri = namespaceUri.trim(); 175 // Remove it from the cache ... 176 boolean found = this.cache.unregister(namespaceUri); 177 // Then from the source ... 178 return doUnregister(namespaceUri) || found; 179 } 180 181 /** 182 * {@inheritDoc} 183 */ 184 public Set<String> getRegisteredNamespaceUris() { 185 // Just return what's in the cache ... 186 return cache.getRegisteredNamespaceUris(); 187 } 188 189 /** 190 * {@inheritDoc} 191 * 192 * @see org.jboss.dna.graph.property.NamespaceRegistry#getNamespaces() 193 */ 194 public Set<Namespace> getNamespaces() { 195 // Just return what's in the cache ... 196 return cache.getNamespaces(); 197 } 198 199 public void refresh() { 200 SimpleNamespaceRegistry newCache = new SimpleNamespaceRegistry(); 201 initializeCacheFromStore(newCache); 202 this.cache = newCache; 203 } 204 205 /** 206 * {@inheritDoc} 207 * 208 * @see java.lang.Object#toString() 209 */ 210 @Override 211 public String toString() { 212 List<Namespace> namespaces = new ArrayList<Namespace>(getNamespaces()); 213 Collections.sort(namespaces); 214 return namespaces.toString(); 215 } 216 217 protected void initializeCacheFromStore( NamespaceRegistry cache ) { 218 // Get the namespaces that the store is using ... 219 Set<Namespace> toRegister = new HashSet<Namespace>(store.getContext().getNamespaceRegistry().getNamespaces()); 220 221 // Read the store ... 222 try { 223 Subgraph nsGraph = store.getSubgraphOfDepth(2).at(parentOfNamespaceNodes); 224 ValueFactory<String> stringFactory = store.getContext().getValueFactories().getStringFactory(); 225 for (Location nsLocation : nsGraph.getRoot().getChildren()) { 226 Node ns = nsGraph.getNode(nsLocation); 227 // This node is a namespace ... 228 String uri = stringFactory.create(ns.getProperty(uriPropertyName).getFirstValue()); 229 if (uri != null) { 230 String prefix = getPrefixFor(nsLocation.getPath()); 231 cache.register(prefix, uri); 232 // If we found it, we don't need to register it ... 233 toRegister.remove(new BasicNamespace(prefix, uri)); 234 } 235 } 236 237 // Empty prefix to namespace mapping is built-in 238 cache.register("", ""); 239 toRegister.remove(cache.getNamespaceForPrefix("")); 240 241 // Persist any namespaces that we didn't find ... 242 if (!toRegister.isEmpty()) { 243 Graph.Batch batch = store.batch(); 244 PathFactory pathFactory = store.getContext().getValueFactories().getPathFactory(); 245 for (Namespace namespace : toRegister) { 246 String prefix = namespace.getPrefix(); 247 if (prefix.length() == 0) continue; 248 String uri = namespace.getNamespaceUri(); 249 Path pathToNamespaceNode = pathFactory.create(parentOfNamespaceNodes, prefix); 250 batch.create(pathToNamespaceNode).with(namespaceProperties).and(uriPropertyName, uri).and(); 251 } 252 batch.execute(); 253 } 254 255 } catch (PathNotFoundException e) { 256 // Nothing to read 257 } 258 // Load in the namespaces from the execution context used by the store ... 259 for (Namespace namespace : store.getContext().getNamespaceRegistry().getNamespaces()) { 260 register(namespace.getPrefix(), namespace.getNamespaceUri()); 261 } 262 } 263 264 protected String readUriFor( String prefix ) { 265 // Read the store ... 266 try { 267 PathFactory pathFactory = store.getContext().getValueFactories().getPathFactory(); 268 Path pathToNamespaceNode = pathFactory.create(parentOfNamespaceNodes, prefix); 269 Property uri = store.getProperty(uriPropertyName).on(pathToNamespaceNode); 270 // Get the URI property value ... 271 ValueFactory<String> stringFactory = store.getContext().getValueFactories().getStringFactory(); 272 return stringFactory.create(uri.getFirstValue()); 273 } catch (PathNotFoundException e) { 274 // Nothing to read 275 return null; 276 } 277 } 278 279 protected String getPrefixFor( Path path ) { 280 Path.Segment lastSegment = path.getLastSegment(); 281 String localName = lastSegment.getName().getLocalName(); 282 int index = lastSegment.getIndex(); 283 if (index == 1) { 284 if (GENERATED_PREFIX.equals(localName)) return localName + "00" + index; 285 return localName; 286 } 287 if (index < 10) { 288 return localName + "00" + index; 289 } 290 if (index < 100) { 291 return localName + "0" + index; 292 } 293 return localName + index; 294 } 295 296 protected String readPrefixFor( String namespaceUri, 297 boolean generateIfMissing ) { 298 // Read the store ... 299 try { 300 Subgraph nsGraph = store.getSubgraphOfDepth(2).at(parentOfNamespaceNodes); 301 ValueFactory<String> stringFactory = store.getContext().getValueFactories().getStringFactory(); 302 for (Location nsLocation : nsGraph.getRoot().getChildren()) { 303 Node ns = nsGraph.getNode(nsLocation); 304 String prefix = getPrefixFor(nsLocation.getPath()); 305 String uri = stringFactory.create(ns.getProperty(uriPropertyName).getFirstValue()); 306 if (prefix != null && uri != null) { 307 if (uri.equals(namespaceUri)) return prefix; 308 } 309 } 310 if (generateIfMissing) { 311 // Generated prefixes are simply "ns" followed by the SNS index ... 312 PathFactory pathFactory = store.getContext().getValueFactories().getPathFactory(); 313 Path pathToNamespaceNode = pathFactory.create(parentOfNamespaceNodes, GENERATED_PREFIX); 314 Property uriProperty = store.getContext().getPropertyFactory().create(uriPropertyName, namespaceUri); 315 List<Property> props = new ArrayList<Property>(namespaceProperties); 316 props.add(uriProperty); 317 Location actualLocation = store.createIfMissing(pathToNamespaceNode, props).andReturn().getLocation(); 318 319 return getPrefixFor(actualLocation.getPath()); 320 } 321 322 } catch (PathNotFoundException e) { 323 // Nothing to read 324 } 325 return null; 326 } 327 328 protected String doRegister( String prefix, 329 String uri ) { 330 assert prefix != null; 331 assert uri != null; 332 prefix = prefix.trim(); 333 uri = uri.trim(); 334 335 // Empty prefix to namespace mapping is built in 336 if (prefix.length() == 0) { 337 return null; 338 } 339 340 // Read the store ... 341 String previousUri = null; 342 ValueFactory<String> stringFactory = store.getContext().getValueFactories().getStringFactory(); 343 PathFactory pathFactory = store.getContext().getValueFactories().getPathFactory(); 344 Path pathToNamespaceNode = pathFactory.create(parentOfNamespaceNodes, prefix); 345 try { 346 Subgraph nsGraph = store.getSubgraphOfDepth(2).at(parentOfNamespaceNodes); 347 // Iterate over the existing mappings, looking for one that uses the URI ... 348 Location nsNodeWithPrefix = null; 349 boolean updateNode = true; 350 Set<Location> locationsToRemove = new HashSet<Location>(); 351 for (Location nsLocation : nsGraph.getRoot().getChildren()) { 352 Node ns = nsGraph.getNode(nsLocation); 353 String actualPrefix = getPrefixFor(nsLocation.getPath()); 354 String actualUri = stringFactory.create(ns.getProperty(uriPropertyName).getFirstValue()); 355 if (actualPrefix != null && actualUri != null) { 356 if (actualPrefix.equals(prefix)) { 357 nsNodeWithPrefix = nsLocation; 358 if (actualUri.equals(uri)) { 359 updateNode = false; 360 break; 361 } 362 previousUri = actualUri; 363 } 364 if (actualUri.equals(uri)) { 365 locationsToRemove.add(ns.getLocation()); 366 } 367 } 368 } 369 Graph.Batch batch = store.batch(); 370 // Remove any other nodes that have the same URI ... 371 for (Location namespaceToRemove : locationsToRemove) { 372 batch.delete(namespaceToRemove).and(); 373 } 374 // Now update/create the namespace mapping ... 375 if (nsNodeWithPrefix == null) { 376 // We didn't find an existing node, so we have to create it ... 377 batch.create(pathToNamespaceNode).with(namespaceProperties).and(uriPropertyName, uri).and(); 378 } else { 379 if (updateNode) { 380 // There was already an existing node, so update it ... 381 batch.set(uriPropertyName).to(uri).on(pathToNamespaceNode).and(); 382 } 383 } 384 // Execute all these changes ... 385 batch.execute(); 386 } catch (PathNotFoundException e) { 387 // Nothing stored yet ... 388 store.createAt(pathToNamespaceNode).with(namespaceProperties).and(uriPropertyName, uri).getLocation(); 389 } 390 return previousUri; 391 } 392 393 protected boolean doUnregister( String uri ) { 394 // Read the store ... 395 ValueFactory<String> stringFactory = store.getContext().getValueFactories().getStringFactory(); 396 boolean result = false; 397 try { 398 Subgraph nsGraph = store.getSubgraphOfDepth(2).at(parentOfNamespaceNodes); 399 // Iterate over the existing mappings, looking for one that uses the prefix and uri ... 400 Set<Location> locationsToRemove = new HashSet<Location>(); 401 for (Location nsLocation : nsGraph.getRoot().getChildren()) { 402 Node ns = nsGraph.getNode(nsLocation); 403 String actualUri = stringFactory.create(ns.getProperty(uriPropertyName).getFirstValue()); 404 if (actualUri.equals(uri)) { 405 locationsToRemove.add(ns.getLocation()); 406 result = true; 407 } 408 } 409 // Remove any other nodes that have the same URI ... 410 Graph.Batch batch = store.batch(); 411 for (Location namespaceToRemove : locationsToRemove) { 412 batch.delete(namespaceToRemove).and(); 413 } 414 // Execute all these changes ... 415 batch.execute(); 416 } catch (PathNotFoundException e) { 417 // Nothing stored yet, so do nothing ... 418 } 419 return result; 420 } 421 422 }