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    }