View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors. 
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   *
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.jcr;
25  
26  import java.security.AccessControlException;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.Map;
31  import java.util.Set;
32  import javax.jcr.AccessDeniedException;
33  import javax.jcr.NamespaceException;
34  import javax.jcr.RepositoryException;
35  import javax.jcr.Session;
36  import javax.xml.XMLConstants;
37  import net.jcip.annotations.NotThreadSafe;
38  import org.modeshape.common.util.CheckArg;
39  import org.modeshape.common.xml.XmlCharacters;
40  import org.modeshape.graph.JcrLexicon;
41  import org.modeshape.graph.JcrMixLexicon;
42  import org.modeshape.graph.JcrNtLexicon;
43  import org.modeshape.graph.property.NamespaceRegistry;
44  import org.modeshape.graph.property.Path;
45  import org.modeshape.graph.property.NamespaceRegistry.Namespace;
46  
47  /**
48   * A thread-safe JCR {@link javax.jcr.NamespaceRegistry} implementation that has the standard JCR namespaces pre-registered and
49   * enforces the JCR semantics for {@link #registerNamespace(String, String) registering} and {@link #unregisterNamespace(String)
50   * unregistering} namespaces.
51   * <p>
52   * Note that this implementation is {@link NotThreadSafe not thread safe}, since it is used within a single {@link JcrWorkspace}
53   * and single {@link JcrSession}, and according to the JCR specification these interfaces are not thread safe.
54   * </p>
55   */
56  @NotThreadSafe
57  class JcrNamespaceRegistry implements javax.jcr.NamespaceRegistry {
58  
59      public static enum Behavior {
60          JSR170_SESSION,
61          JSR283_SESSION,
62          WORKSPACE;
63      }
64  
65      static final String DEFAULT_NAMESPACE_PREFIX = "";
66      static final String DEFAULT_NAMESPACE_URI = "";
67  
68      static final String XML_NAMESPACE_PREFIX = XMLConstants.XML_NS_PREFIX;
69      static final String XML_NAMESPACE_URI = XMLConstants.XML_NS_URI;
70      static final String XMLNS_NAMESPACE_PREFIX = XMLConstants.XMLNS_ATTRIBUTE;
71      static final String XMLNS_NAMESPACE_URI = XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
72  
73      static final String XML_SCHEMA_NAMESPACE_PREFIX = "xs";
74      static final String XML_SCHEMA_NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema";
75      static final String XML_SCHEMA_INSTANCE_NAMESPACE_PREFIX = "xsi";
76      static final String XML_SCHEMA_INSTANCE_NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema-instance";
77  
78      static final Set<String> STANDARD_BUILT_IN_PREFIXES;
79      static final Set<String> STANDARD_BUILT_IN_URIS;
80      static final Map<String, String> STANDARD_BUILT_IN_NAMESPACES_BY_PREFIX;
81      static final Map<String, String> STANDARD_BUILT_IN_PREFIXES_BY_NAMESPACE;
82  
83      static {
84          // Set up the standard namespaces ...
85          Map<String, String> namespaces = new HashMap<String, String>();
86          namespaces.put(DEFAULT_NAMESPACE_PREFIX, DEFAULT_NAMESPACE_URI);
87          namespaces.put(JcrLexicon.Namespace.PREFIX, JcrLexicon.Namespace.URI);
88          namespaces.put(JcrNtLexicon.Namespace.PREFIX, JcrNtLexicon.Namespace.URI);
89          namespaces.put(JcrMixLexicon.Namespace.PREFIX, JcrMixLexicon.Namespace.URI);
90          namespaces.put(JcrSvLexicon.Namespace.PREFIX, JcrSvLexicon.Namespace.URI);
91          namespaces.put(XML_NAMESPACE_PREFIX, XML_NAMESPACE_URI);
92          namespaces.put(XMLNS_NAMESPACE_PREFIX, XMLNS_NAMESPACE_URI);
93          namespaces.put(XML_SCHEMA_NAMESPACE_PREFIX, XML_SCHEMA_NAMESPACE_URI);
94          namespaces.put(XML_SCHEMA_INSTANCE_NAMESPACE_PREFIX, XML_SCHEMA_INSTANCE_NAMESPACE_URI);
95          namespaces.put(ModeShapeLexicon.Namespace.PREFIX, ModeShapeLexicon.Namespace.URI);
96          namespaces.put(ModeShapeIntLexicon.Namespace.PREFIX, ModeShapeIntLexicon.Namespace.URI);
97          // Set up the reverse map for the standard namespaces ...
98          Map<String, String> prefixes = new HashMap<String, String>();
99          for (Map.Entry<String, String> entry : namespaces.entrySet()) {
100             String uri = entry.getValue();
101             String prefix = entry.getKey();
102             prefixes.put(uri, prefix);
103         }
104         // Now set up the unmodifiable static collections ...
105         STANDARD_BUILT_IN_NAMESPACES_BY_PREFIX = Collections.unmodifiableMap(namespaces);
106         STANDARD_BUILT_IN_PREFIXES_BY_NAMESPACE = Collections.unmodifiableMap(prefixes);
107         STANDARD_BUILT_IN_PREFIXES = Collections.unmodifiableSet(namespaces.keySet());
108         STANDARD_BUILT_IN_URIS = Collections.unmodifiableSet(new HashSet<String>(namespaces.values()));
109     }
110 
111     private final Behavior behavior;
112     private final NamespaceRegistry registry;
113     private final NamespaceRegistry workspaceRegistry;
114     private final JcrSession session;
115 
116     JcrNamespaceRegistry( NamespaceRegistry workspaceRegistry,
117                           JcrSession session ) {
118         this(Behavior.WORKSPACE, null, workspaceRegistry, session);
119     }
120 
121     JcrNamespaceRegistry( Behavior behavior,
122                           NamespaceRegistry localRegistry,
123                           NamespaceRegistry workspaceRegistry,
124                           JcrSession session ) {
125         this.behavior = behavior;
126         this.registry = localRegistry != null ? localRegistry : workspaceRegistry;
127         this.workspaceRegistry = workspaceRegistry;
128         this.session = session;
129 
130         assert this.behavior != null;
131         assert this.registry != null;
132         assert this.workspaceRegistry != null;
133         assert this.session != null;
134     }
135 
136     /**
137      * Check that the session is still valid and {@link Session#isLive() live}.
138      * 
139      * @throws RepositoryException if the session is not valid or live
140      */
141     protected final void checkSession() throws RepositoryException {
142         session.checkLive();
143     }
144 
145     /**
146      * {@inheritDoc}
147      * 
148      * @see javax.jcr.NamespaceRegistry#getPrefix(java.lang.String)
149      */
150     public String getPrefix( String uri ) throws NamespaceException, RepositoryException {
151         checkSession();
152         if (behavior == Behavior.WORKSPACE) {
153             // Check the standard ones first, ensuring that invalid changes to the persistent storage don't matter ...
154             String prefix = STANDARD_BUILT_IN_PREFIXES_BY_NAMESPACE.get(uri);
155             if (prefix != null) return prefix;
156         }
157         // Now check the underlying registry ...
158         String prefix = registry.getPrefixForNamespaceUri(uri, false);
159         if (prefix == null) {
160             throw new NamespaceException(JcrI18n.noNamespaceWithUri.text(uri));
161         }
162         return prefix;
163     }
164 
165     /**
166      * {@inheritDoc}
167      * 
168      * @see javax.jcr.NamespaceRegistry#getPrefixes()
169      */
170     public String[] getPrefixes() throws RepositoryException {
171         checkSession();
172         Set<Namespace> namespaces = registry.getNamespaces();
173         String[] prefixes = new String[namespaces.size()];
174         int i = 0;
175         for (Namespace namespace : namespaces) {
176             prefixes[i++] = namespace.getPrefix();
177         }
178         return prefixes;
179     }
180 
181     /**
182      * {@inheritDoc}
183      * 
184      * @see javax.jcr.NamespaceRegistry#getURI(java.lang.String)
185      */
186     public String getURI( String prefix ) throws NamespaceException, RepositoryException {
187         checkSession();
188         if (behavior == Behavior.WORKSPACE) {
189             // Check the standard ones first, ensuring that invalid changes to the persistent storage don't matter ...
190             String uri = STANDARD_BUILT_IN_NAMESPACES_BY_PREFIX.get(prefix);
191             if (uri != null) return uri;
192         }
193         // Now check the underlying registry ...
194         String uri = registry.getNamespaceForPrefix(prefix);
195         if (uri == null) {
196             throw new NamespaceException(JcrI18n.noNamespaceWithPrefix.text(prefix));
197         }
198         return uri;
199     }
200 
201     /**
202      * {@inheritDoc}
203      * 
204      * @see javax.jcr.NamespaceRegistry#getURIs()
205      */
206     public String[] getURIs() throws RepositoryException {
207         checkSession();
208         Set<Namespace> namespaces = registry.getNamespaces();
209         String[] uris = new String[namespaces.size()];
210         int i = 0;
211         for (Namespace namespace : namespaces) {
212             uris[i++] = namespace.getNamespaceUri();
213         }
214         return uris;
215     }
216 
217     /**
218      * {@inheritDoc}
219      * 
220      * @see javax.jcr.NamespaceRegistry#registerNamespace(java.lang.String, java.lang.String)
221      */
222     public synchronized void registerNamespace( String prefix,
223                                                 String uri ) throws NamespaceException, RepositoryException {
224         CheckArg.isNotNull(prefix, "prefix");
225         CheckArg.isNotNull(uri, "uri");
226         checkSession();
227 
228         boolean global = false;
229         switch (behavior) {
230             case JSR170_SESSION:
231                 // ----------------------------------------------------------
232                 // JSR-170 Session remapping behavior (see Section 6.3.3) ...
233                 // ----------------------------------------------------------
234                 // Section 6.3.3:
235                 // "If existingUri is not registered in the NamespaceRegistry a NamespaceException will be thrown.
236                 //
237                 // If newPrefix is already locally mapped to existingUri (i.e., within this Session, by virtue
238                 // of an earlier setNamespaceRegistry call) then this method returns silently and has no effect.
239                 //
240                 // If newPrefix is already locally mapped to a URI other than existingUri, then that URI reverts to its
241                 // globally mapped prefix (as set in the NamespaceRegistry) and newPrefix is locally mapped to existingUri.
242                 //
243                 // If newPrefix is already assigned in the global NamespaceRegistry to otheruri (which differs from
244                 // existingUri) and otherUri has not been locally mapped to another prefix which differs from newPrefix,
245                 // then a NamespaceException will be thrown. In order to successfully locally map newPrefix to existingUri,
246                 // otherUri must first be locally mapped to another prefix."
247 
248                 // The URI must already be registered ...
249                 String existingPrefix = registry.getPrefixForNamespaceUri(uri, false);
250                 if (existingPrefix == null) {
251                     // Paragraph 1 ...
252                     throw new NamespaceException(JcrI18n.unableToRemapUriNotRegisteredInNamespaceRegistry.text(prefix, uri));
253                 }
254                 if (existingPrefix.equals(prefix)) return; // Paragraph 2
255 
256                 // Is the prefix already used in a mapping ...
257                 String existingUri = registry.getNamespaceForPrefix(prefix);
258                 if (existingUri != null) {
259                     // Is this existing mapping local, or is it in the global (workspace) registry?
260                     String globalPrefix = workspaceRegistry.getPrefixForNamespaceUri(existingUri, false);
261                     if (!prefix.equals(globalPrefix)) {
262                         // Paragraph 3: The mapping is local to the session, so this local mapping should just be reverted ...
263                         registry.unregister(existingUri);
264                     }
265 
266                     // Paragraph 4: The mapping is global, so this is not allowed; the existing ...
267                     String msg = JcrI18n.unableToRemapUriUsingPrefixUsedInNamespaceRegistry.text(prefix, uri, existingUri);
268                     throw new NamespaceException(msg);
269                 }
270 
271                 // Otherwise, the prefix is not already used in a mapping, so we can continue ...
272 
273                 break;
274 
275             case JSR283_SESSION:
276                 // --------------------------------------
277                 // JSR-283 Session remapping behavior ...
278                 // --------------------------------------
279                 // Section 4.3.3 (of the Draft specification):
280                 // "All local mappings already present in the Session that include either the specified prefix
281                 // or the specified uri are removed and the new mapping is added."
282                 String existingUriForPrefix = registry.getNamespaceForPrefix(prefix);
283                 if (existingUriForPrefix != null) {
284                     registry.unregister(existingUriForPrefix);
285                 }
286                 registry.unregister(uri);
287 
288                 break;
289 
290             case WORKSPACE:
291                 // --------------------------------------------------
292                 // JSR-170 & JSR-283 Workspace namespace registry ...
293                 // --------------------------------------------------
294                 global = true;
295 
296                 try {
297                     session.checkPermission((Path)null, ModeShapePermissions.REGISTER_NAMESPACE);
298                 } catch (AccessControlException ace) {
299                     throw new AccessDeniedException(ace);
300                 }
301 
302                 // Check the zero-length prefix and zero-length URI ...
303                 if (DEFAULT_NAMESPACE_PREFIX.equals(prefix) || DEFAULT_NAMESPACE_URI.equals(uri)) {
304                     throw new NamespaceException(JcrI18n.unableToChangeTheDefaultNamespace.text());
305                 }
306                 // Check whether the prefix or URI are reserved (case-sensitive) ...
307                 if (STANDARD_BUILT_IN_PREFIXES.contains(prefix)) {
308                     throw new NamespaceException(JcrI18n.unableToRegisterReservedNamespacePrefix.text(prefix, uri));
309                 }
310                 if (STANDARD_BUILT_IN_URIS.contains(uri)) {
311                     throw new NamespaceException(JcrI18n.unableToRegisterReservedNamespaceUri.text(prefix, uri));
312                 }
313                 break;
314             default:
315                 assert false; // should never happen
316         }
317 
318         // Check the zero-length prefix and zero-length URI ...
319         if (DEFAULT_NAMESPACE_PREFIX.equals(prefix) || DEFAULT_NAMESPACE_URI.equals(uri)) {
320             throw new NamespaceException(JcrI18n.unableToChangeTheDefaultNamespace.text());
321         }
322 
323         // Check whether the prefix begins with 'xml' (in any case) ...
324         if (prefix.toLowerCase().startsWith(XML_NAMESPACE_PREFIX)) {
325             throw new NamespaceException(JcrI18n.unableToRegisterNamespaceUsingXmlPrefix.text(prefix, uri));
326         }
327 
328         // The prefix must be a valid XML Namespace prefix (i.e., a valid NCName) ...
329         if (!XmlCharacters.isValidName(prefix)) {
330             throw new NamespaceException(JcrI18n.unableToRegisterNamespaceWithInvalidPrefix.text(prefix, uri));
331         }
332 
333         // Signal the local node type manager ...
334         session.signalNamespaceChanges(global);
335 
336         // Now we're sure the prefix and URI are valid and okay for a custom mapping ...
337         try {
338             registry.register(prefix, uri);
339         } catch (RuntimeException e) {
340             throw new RepositoryException(e.getMessage(), e.getCause());
341         }
342     }
343 
344     /**
345      * {@inheritDoc}
346      * 
347      * @see javax.jcr.NamespaceRegistry#unregisterNamespace(java.lang.String)
348      */
349     public synchronized void unregisterNamespace( String prefix )
350         throws NamespaceException, AccessDeniedException, RepositoryException {
351         CheckArg.isNotNull(prefix, "prefix");
352         checkSession();
353 
354         // Don't need to check permissions for transient registration/unregistration
355         if (behavior.equals(Behavior.WORKSPACE)) {
356             try {
357                 session.checkPermission((Path)null, ModeShapePermissions.REGISTER_NAMESPACE);
358             } catch (AccessControlException ace) {
359                 throw new AccessDeniedException(ace);
360             }
361         }
362 
363         // Look to see whether the prefix is registered ...
364         String uri = registry.getNamespaceForPrefix(prefix);
365         // It is an error to unregister a namespace that is not registered ...
366         if (uri == null) {
367             throw new NamespaceException(JcrI18n.unableToUnregisterPrefixForNamespaceThatIsNotRegistered.text(prefix));
368         }
369         // Unregistering a built-in prefix or URI is invalid ...
370         if (STANDARD_BUILT_IN_PREFIXES.contains(prefix)) {
371             throw new NamespaceException(JcrI18n.unableToUnregisterReservedNamespacePrefix.text(prefix, uri));
372         }
373         if (STANDARD_BUILT_IN_URIS.contains(uri)) {
374             throw new NamespaceException(JcrI18n.unableToUnregisterReservedNamespaceUri.text(prefix, uri));
375         }
376 
377         // Signal the local node type manager ...
378         session.workspace().nodeTypeManager().signalNamespaceChanges();
379 
380         // Now we're sure the prefix is valid and is actually used in a mapping ...
381         try {
382             registry.unregister(uri);
383         } catch (RuntimeException e) {
384             throw new RepositoryException(e.getMessage(), e.getCause());
385         }
386     }
387 
388     /**
389      * {@inheritDoc}
390      * 
391      * @see java.lang.Object#toString()
392      */
393     @Override
394     public String toString() {
395         return registry.toString();
396     }
397 }