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 // Load in the namespaces from the execution context used by the store ...
086 for (Namespace namespace : store.getContext().getNamespaceRegistry().getNamespaces()) {
087 register(namespace.getPrefix(), namespace.getNamespaceUri());
088 }
089 }
090
091 private void createNamespaceParentIfNeeded() {
092 try {
093 this.store.getNodeAt(this.parentOfNamespaceNodes);
094 } catch (PathNotFoundException pnfe) {
095 // The node did not already exist - create it!
096 this.store.create(parentOfNamespaceNodes);
097 this.store.set(JcrLexicon.PRIMARY_TYPE).on(parentOfNamespaceNodes).to(DnaLexicon.NAMESPACES);
098 }
099 }
100
101 /**
102 * {@inheritDoc}
103 */
104 public String getNamespaceForPrefix( String prefix ) {
105 CheckArg.isNotNull(prefix, "prefix");
106 // Try the cache first ...
107 String uri = cache.getNamespaceForPrefix(prefix);
108 if (uri == null) {
109 // See if the store has it ...
110 uri = readUriFor(prefix);
111 if (uri != null) {
112 // update the cache ...
113 cache.register(prefix, uri);
114 }
115 }
116 return uri;
117 }
118
119 /**
120 * {@inheritDoc}
121 */
122 public String getPrefixForNamespaceUri( String namespaceUri,
123 boolean generateIfMissing ) {
124 CheckArg.isNotNull(namespaceUri, "namespaceUri");
125 // Try the cache first ...
126 String prefix = cache.getPrefixForNamespaceUri(namespaceUri, false);
127 if (prefix == null && generateIfMissing) {
128 prefix = readPrefixFor(namespaceUri, generateIfMissing);
129 if (prefix != null) {
130 cache.register(prefix, namespaceUri);
131 }
132 }
133 return prefix;
134 }
135
136 /**
137 * {@inheritDoc}
138 */
139 public boolean isRegisteredNamespaceUri( String namespaceUri ) {
140 CheckArg.isNotNull(namespaceUri, "namespaceUri");
141 if (cache.isRegisteredNamespaceUri(namespaceUri)) return true;
142 // Otherwise it was not found in the cache, so check the store ...
143 String prefix = readPrefixFor(namespaceUri, false);
144 if (prefix != null) {
145 cache.register(prefix, namespaceUri);
146 return true;
147 }
148 return false;
149 }
150
151 /**
152 * {@inheritDoc}
153 */
154 public String getDefaultNamespaceUri() {
155 return this.getNamespaceForPrefix("");
156 }
157
158 /**
159 * {@inheritDoc}
160 */
161 public String register( String prefix,
162 String namespaceUri ) {
163 CheckArg.isNotNull(namespaceUri, "namespaceUri");
164 namespaceUri = namespaceUri.trim();
165 // Register it in the cache first ...
166 String previousCachedUriForPrefix = this.cache.register(prefix, namespaceUri);
167 // And register it in the source ...
168 String previousPersistentUriForPrefix = doRegister(prefix, namespaceUri);
169 return previousCachedUriForPrefix != null ? previousPersistentUriForPrefix : previousPersistentUriForPrefix;
170 }
171
172 /**
173 * {@inheritDoc}
174 *
175 * @see org.jboss.dna.graph.property.NamespaceRegistry#unregister(java.lang.String)
176 */
177 public boolean unregister( String namespaceUri ) {
178 CheckArg.isNotNull(namespaceUri, "namespaceUri");
179 namespaceUri = namespaceUri.trim();
180 // Remove it from the cache ...
181 boolean found = this.cache.unregister(namespaceUri);
182 // Then from the source ...
183 return doUnregister(namespaceUri) || found;
184 }
185
186 /**
187 * {@inheritDoc}
188 */
189 public Set<String> getRegisteredNamespaceUris() {
190 // Just return what's in the cache ...
191 return cache.getRegisteredNamespaceUris();
192 }
193
194 /**
195 * {@inheritDoc}
196 *
197 * @see org.jboss.dna.graph.property.NamespaceRegistry#getNamespaces()
198 */
199 public Set<Namespace> getNamespaces() {
200 // Just return what's in the cache ...
201 return cache.getNamespaces();
202 }
203
204 public void refresh() {
205 SimpleNamespaceRegistry newCache = new SimpleNamespaceRegistry();
206 initializeCacheFromStore(newCache);
207 this.cache = newCache;
208 }
209
210 /**
211 * {@inheritDoc}
212 *
213 * @see java.lang.Object#toString()
214 */
215 @Override
216 public String toString() {
217 List<Namespace> namespaces = new ArrayList<Namespace>(getNamespaces());
218 Collections.sort(namespaces);
219 return namespaces.toString();
220 }
221
222 protected void initializeCacheFromStore( NamespaceRegistry cache ) {
223 // Read the store ...
224 try {
225 Subgraph nsGraph = store.getSubgraphOfDepth(2).at(parentOfNamespaceNodes);
226 ValueFactory<String> stringFactory = store.getContext().getValueFactories().getStringFactory();
227 for (Location nsLocation : nsGraph.getRoot().getChildren()) {
228 Node ns = nsGraph.getNode(nsLocation);
229 // This node is a namespace ...
230 String uri = stringFactory.create(ns.getProperty(uriPropertyName).getFirstValue());
231 if (uri != null) {
232 String prefix = getPrefixFor(nsLocation.getPath());
233 cache.register(prefix, uri);
234 }
235 }
236
237 // Empty prefix to namespace mapping is built-in
238 cache.register("", "");
239 } catch (PathNotFoundException e) {
240 // Nothing to read
241 }
242 }
243
244 protected String readUriFor( String prefix ) {
245 // Read the store ...
246 try {
247 PathFactory pathFactory = store.getContext().getValueFactories().getPathFactory();
248 Path pathToNamespaceNode = pathFactory.create(parentOfNamespaceNodes, prefix);
249 Property uri = store.getProperty(uriPropertyName).on(pathToNamespaceNode);
250 // Get the URI property value ...
251 ValueFactory<String> stringFactory = store.getContext().getValueFactories().getStringFactory();
252 return stringFactory.create(uri.getFirstValue());
253 } catch (PathNotFoundException e) {
254 // Nothing to read
255 return null;
256 }
257 }
258
259 protected String getPrefixFor( Path path ) {
260 Path.Segment lastSegment = path.getLastSegment();
261 String localName = lastSegment.getName().getLocalName();
262 int index = lastSegment.getIndex();
263 if (index == 1) {
264 if (GENERATED_PREFIX.equals(localName)) return localName + "00" + index;
265 return localName;
266 }
267 if (index < 10) {
268 return localName + "00" + index;
269 }
270 if (index < 100) {
271 return localName + "0" + index;
272 }
273 return localName + index;
274 }
275
276 protected String readPrefixFor( String namespaceUri,
277 boolean generateIfMissing ) {
278 // Read the store ...
279 try {
280 Subgraph nsGraph = store.getSubgraphOfDepth(2).at(parentOfNamespaceNodes);
281 ValueFactory<String> stringFactory = store.getContext().getValueFactories().getStringFactory();
282 for (Location nsLocation : nsGraph.getRoot().getChildren()) {
283 Node ns = nsGraph.getNode(nsLocation);
284 String prefix = getPrefixFor(nsLocation.getPath());
285 String uri = stringFactory.create(ns.getProperty(uriPropertyName).getFirstValue());
286 if (prefix != null && uri != null) {
287 if (uri.equals(namespaceUri)) return prefix;
288 }
289 }
290 if (generateIfMissing) {
291 // Generated prefixes are simply "ns" followed by the SNS index ...
292 PathFactory pathFactory = store.getContext().getValueFactories().getPathFactory();
293 Path pathToNamespaceNode = pathFactory.create(parentOfNamespaceNodes, GENERATED_PREFIX);
294 Location actualLocation = store.createAt(pathToNamespaceNode)
295 .with(namespaceProperties)
296 .and(uriPropertyName, namespaceUri)
297 .getLocation();
298
299 return getPrefixFor(actualLocation.getPath());
300 }
301
302 } catch (PathNotFoundException e) {
303 // Nothing to read
304 }
305 return null;
306 }
307
308 protected String doRegister( String prefix,
309 String uri ) {
310 assert prefix != null;
311 assert uri != null;
312 prefix = prefix.trim();
313 uri = uri.trim();
314
315 // Empty prefix to namespace mapping is built in
316 if (prefix.length() == 0) {
317 return null;
318 }
319
320 // Read the store ...
321 String previousUri = null;
322 ValueFactory<String> stringFactory = store.getContext().getValueFactories().getStringFactory();
323 PathFactory pathFactory = store.getContext().getValueFactories().getPathFactory();
324 Path pathToNamespaceNode = pathFactory.create(parentOfNamespaceNodes, prefix);
325 try {
326 Subgraph nsGraph = store.getSubgraphOfDepth(2).at(parentOfNamespaceNodes);
327 // Iterate over the existing mappings, looking for one that uses the URI ...
328 Location nsNodeWithPrefix = null;
329 boolean updateNode = true;
330 Set<Location> locationsToRemove = new HashSet<Location>();
331 for (Location nsLocation : nsGraph.getRoot().getChildren()) {
332 Node ns = nsGraph.getNode(nsLocation);
333 String actualPrefix = getPrefixFor(nsLocation.getPath());
334 String actualUri = stringFactory.create(ns.getProperty(uriPropertyName).getFirstValue());
335 if (actualPrefix != null && actualUri != null) {
336 if (actualPrefix.equals(prefix)) {
337 nsNodeWithPrefix = nsLocation;
338 if (actualUri.equals(uri)) {
339 updateNode = false;
340 break;
341 }
342 previousUri = actualUri;
343 }
344 if (actualUri.equals(uri)) {
345 locationsToRemove.add(ns.getLocation());
346 }
347 }
348 }
349 Graph.Batch batch = store.batch();
350 // Remove any other nodes that have the same URI ...
351 for (Location namespaceToRemove : locationsToRemove) {
352 batch.delete(namespaceToRemove).and();
353 }
354 // Now update/create the namespace mapping ...
355 if (nsNodeWithPrefix == null) {
356 // We didn't find an existing node, so we have to create it ...
357 batch.create(pathToNamespaceNode).with(namespaceProperties).and(uriPropertyName, uri).and();
358 } else {
359 if (updateNode) {
360 // There was already an existing node, so update it ...
361 batch.set(uriPropertyName).to(uri).on(pathToNamespaceNode).and();
362 }
363 }
364 // Execute all these changes ...
365 batch.execute();
366 } catch (PathNotFoundException e) {
367 // Nothing stored yet ...
368 store.createAt(pathToNamespaceNode).with(namespaceProperties).and(uriPropertyName, uri).getLocation();
369 }
370 return previousUri;
371 }
372
373 protected boolean doUnregister( String uri ) {
374 // Read the store ...
375 ValueFactory<String> stringFactory = store.getContext().getValueFactories().getStringFactory();
376 boolean result = false;
377 try {
378 Subgraph nsGraph = store.getSubgraphOfDepth(2).at(parentOfNamespaceNodes);
379 // Iterate over the existing mappings, looking for one that uses the prefix and uri ...
380 Set<Location> locationsToRemove = new HashSet<Location>();
381 for (Location nsLocation : nsGraph.getRoot().getChildren()) {
382 Node ns = nsGraph.getNode(nsLocation);
383 String actualUri = stringFactory.create(ns.getProperty(uriPropertyName).getFirstValue());
384 if (actualUri.equals(uri)) {
385 locationsToRemove.add(ns.getLocation());
386 result = true;
387 }
388 }
389 // Remove any other nodes that have the same URI ...
390 Graph.Batch batch = store.batch();
391 for (Location namespaceToRemove : locationsToRemove) {
392 batch.delete(namespaceToRemove).and();
393 }
394 // Execute all these changes ...
395 batch.execute();
396 } catch (PathNotFoundException e) {
397 // Nothing stored yet, so do nothing ...
398 }
399 return result;
400 }
401
402 }