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 }