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    }