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          SESSION,
61          WORKSPACE;
62      }
63  
64      static final String DEFAULT_NAMESPACE_PREFIX = "";
65      static final String DEFAULT_NAMESPACE_URI = "";
66  
67      static final String XML_NAMESPACE_PREFIX = XMLConstants.XML_NS_PREFIX;
68      static final String XML_NAMESPACE_URI = XMLConstants.XML_NS_URI;
69      static final String XMLNS_NAMESPACE_PREFIX = XMLConstants.XMLNS_ATTRIBUTE;
70      static final String XMLNS_NAMESPACE_URI = XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
71  
72      static final String XML_SCHEMA_NAMESPACE_PREFIX = "xs";
73      static final String XML_SCHEMA_NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema";
74      static final String XML_SCHEMA_INSTANCE_NAMESPACE_PREFIX = "xsi";
75      static final String XML_SCHEMA_INSTANCE_NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema-instance";
76  
77      static final Set<String> STANDARD_BUILT_IN_PREFIXES;
78      static final Set<String> STANDARD_BUILT_IN_URIS;
79      static final Map<String, String> STANDARD_BUILT_IN_NAMESPACES_BY_PREFIX;
80      static final Map<String, String> STANDARD_BUILT_IN_PREFIXES_BY_NAMESPACE;
81  
82      static {
83          // Set up the standard namespaces ...
84          Map<String, String> namespaces = new HashMap<String, String>();
85          namespaces.put(DEFAULT_NAMESPACE_PREFIX, DEFAULT_NAMESPACE_URI);
86          namespaces.put(JcrLexicon.Namespace.PREFIX, JcrLexicon.Namespace.URI);
87          namespaces.put(JcrNtLexicon.Namespace.PREFIX, JcrNtLexicon.Namespace.URI);
88          namespaces.put(JcrMixLexicon.Namespace.PREFIX, JcrMixLexicon.Namespace.URI);
89          namespaces.put(JcrSvLexicon.Namespace.PREFIX, JcrSvLexicon.Namespace.URI);
90          namespaces.put(XML_NAMESPACE_PREFIX, XML_NAMESPACE_URI);
91          namespaces.put(XMLNS_NAMESPACE_PREFIX, XMLNS_NAMESPACE_URI);
92          namespaces.put(XML_SCHEMA_NAMESPACE_PREFIX, XML_SCHEMA_NAMESPACE_URI);
93          namespaces.put(XML_SCHEMA_INSTANCE_NAMESPACE_PREFIX, XML_SCHEMA_INSTANCE_NAMESPACE_URI);
94          namespaces.put(ModeShapeLexicon.Namespace.PREFIX, ModeShapeLexicon.Namespace.URI);
95          namespaces.put(ModeShapeIntLexicon.Namespace.PREFIX, ModeShapeIntLexicon.Namespace.URI);
96          // Set up the reverse map for the standard namespaces ...
97          Map<String, String> prefixes = new HashMap<String, String>();
98          for (Map.Entry<String, String> entry : namespaces.entrySet()) {
99              String uri = entry.getValue();
100             String prefix = entry.getKey();
101             prefixes.put(uri, prefix);
102         }
103         // Now set up the unmodifiable static collections ...
104         STANDARD_BUILT_IN_NAMESPACES_BY_PREFIX = Collections.unmodifiableMap(namespaces);
105         STANDARD_BUILT_IN_PREFIXES_BY_NAMESPACE = Collections.unmodifiableMap(prefixes);
106         STANDARD_BUILT_IN_PREFIXES = Collections.unmodifiableSet(namespaces.keySet());
107         STANDARD_BUILT_IN_URIS = Collections.unmodifiableSet(new HashSet<String>(namespaces.values()));
108     }
109 
110     private final Behavior behavior;
111     private final NamespaceRegistry registry;
112     private final NamespaceRegistry workspaceRegistry;
113     private final JcrSession session;
114 
115     JcrNamespaceRegistry( NamespaceRegistry workspaceRegistry,
116                           JcrSession session ) {
117         this(Behavior.WORKSPACE, null, workspaceRegistry, session);
118     }
119 
120     JcrNamespaceRegistry( Behavior behavior,
121                           NamespaceRegistry localRegistry,
122                           NamespaceRegistry workspaceRegistry,
123                           JcrSession session ) {
124         this.behavior = behavior;
125         this.registry = localRegistry != null ? localRegistry : workspaceRegistry;
126         this.workspaceRegistry = workspaceRegistry;
127         this.session = session;
128 
129         assert this.behavior != null;
130         assert this.registry != null;
131         assert this.workspaceRegistry != null;
132         assert this.session != null;
133     }
134 
135     /**
136      * Check that the session is still valid and {@link Session#isLive() live}.
137      * 
138      * @throws RepositoryException if the session is not valid or live
139      */
140     protected final void checkSession() throws RepositoryException {
141         session.checkLive();
142     }
143 
144     /**
145      * {@inheritDoc}
146      * 
147      * @see javax.jcr.NamespaceRegistry#getPrefix(java.lang.String)
148      */
149     public String getPrefix( String uri ) throws NamespaceException, RepositoryException {
150         checkSession();
151         if (behavior == Behavior.WORKSPACE) {
152             // Check the standard ones first, ensuring that invalid changes to the persistent storage don't matter ...
153             String prefix = STANDARD_BUILT_IN_PREFIXES_BY_NAMESPACE.get(uri);
154             if (prefix != null) return prefix;
155         }
156         // Now check the underlying registry ...
157         String prefix = registry.getPrefixForNamespaceUri(uri, false);
158         if (prefix == null) {
159             throw new NamespaceException(JcrI18n.noNamespaceWithUri.text(uri));
160         }
161         return prefix;
162     }
163 
164     /**
165      * {@inheritDoc}
166      * 
167      * @see javax.jcr.NamespaceRegistry#getPrefixes()
168      */
169     public String[] getPrefixes() throws RepositoryException {
170         checkSession();
171         Set<Namespace> namespaces = registry.getNamespaces();
172         String[] prefixes = new String[namespaces.size()];
173         int i = 0;
174         for (Namespace namespace : namespaces) {
175             prefixes[i++] = namespace.getPrefix();
176         }
177         return prefixes;
178     }
179 
180     /**
181      * {@inheritDoc}
182      * 
183      * @see javax.jcr.NamespaceRegistry#getURI(java.lang.String)
184      */
185     public String getURI( String prefix ) throws NamespaceException, RepositoryException {
186         checkSession();
187         if (behavior == Behavior.WORKSPACE) {
188             // Check the standard ones first, ensuring that invalid changes to the persistent storage don't matter ...
189             String uri = STANDARD_BUILT_IN_NAMESPACES_BY_PREFIX.get(prefix);
190             if (uri != null) return uri;
191         }
192         // Now check the underlying registry ...
193         String uri = registry.getNamespaceForPrefix(prefix);
194         if (uri == null) {
195             throw new NamespaceException(JcrI18n.noNamespaceWithPrefix.text(prefix));
196         }
197         return uri;
198     }
199 
200     /**
201      * {@inheritDoc}
202      * 
203      * @see javax.jcr.NamespaceRegistry#getURIs()
204      */
205     public String[] getURIs() throws RepositoryException {
206         checkSession();
207         Set<Namespace> namespaces = registry.getNamespaces();
208         String[] uris = new String[namespaces.size()];
209         int i = 0;
210         for (Namespace namespace : namespaces) {
211             uris[i++] = namespace.getNamespaceUri();
212         }
213         return uris;
214     }
215 
216     /**
217      * {@inheritDoc}
218      * 
219      * @see javax.jcr.NamespaceRegistry#registerNamespace(java.lang.String, java.lang.String)
220      */
221     public synchronized void registerNamespace( String prefix,
222                                                 String uri ) throws NamespaceException, RepositoryException {
223         CheckArg.isNotNull(prefix, "prefix");
224         CheckArg.isNotNull(uri, "uri");
225         checkSession();
226 
227         boolean global = false;
228         switch (behavior) {
229             case SESSION:
230                 // --------------------------------------
231                 // JSR-283 Session remapping behavior ...
232                 // --------------------------------------
233                 // Section 4.3.3 (of the Draft specification):
234                 // "All local mappings already present in the Session that include either the specified prefix
235                 // or the specified uri are removed and the new mapping is added."
236                 String existingUriForPrefix = registry.getNamespaceForPrefix(prefix);
237                 if (existingUriForPrefix != null) {
238                     registry.unregister(existingUriForPrefix);
239                 }
240                 registry.unregister(uri);
241 
242                 break;
243 
244             case WORKSPACE:
245                 // --------------------------------------------------
246                 // JSR-170 & JSR-283 Workspace namespace registry ...
247                 // --------------------------------------------------
248                 global = true;
249 
250                 try {
251                     session.checkPermission((Path)null, ModeShapePermissions.REGISTER_NAMESPACE);
252                 } catch (AccessControlException ace) {
253                     throw new AccessDeniedException(ace);
254                 }
255 
256                 // Check the zero-length prefix and zero-length URI ...
257                 if (DEFAULT_NAMESPACE_PREFIX.equals(prefix) || DEFAULT_NAMESPACE_URI.equals(uri)) {
258                     throw new NamespaceException(JcrI18n.unableToChangeTheDefaultNamespace.text());
259                 }
260                 // Check whether the prefix or URI are reserved (case-sensitive) ...
261                 if (STANDARD_BUILT_IN_PREFIXES.contains(prefix)) {
262                     throw new NamespaceException(JcrI18n.unableToRegisterReservedNamespacePrefix.text(prefix, uri));
263                 }
264                 if (STANDARD_BUILT_IN_URIS.contains(uri)) {
265                     throw new NamespaceException(JcrI18n.unableToRegisterReservedNamespaceUri.text(prefix, uri));
266                 }
267                 break;
268             default:
269                 assert false; // should never happen
270         }
271 
272         // Check the zero-length prefix and zero-length URI ...
273         if (DEFAULT_NAMESPACE_PREFIX.equals(prefix) || DEFAULT_NAMESPACE_URI.equals(uri)) {
274             throw new NamespaceException(JcrI18n.unableToChangeTheDefaultNamespace.text());
275         }
276 
277         // Check whether the prefix begins with 'xml' (in any case) ...
278         if (prefix.toLowerCase().startsWith(XML_NAMESPACE_PREFIX)) {
279             throw new NamespaceException(JcrI18n.unableToRegisterNamespaceUsingXmlPrefix.text(prefix, uri));
280         }
281 
282         // The prefix must be a valid XML Namespace prefix (i.e., a valid NCName) ...
283         if (!XmlCharacters.isValidName(prefix)) {
284             throw new NamespaceException(JcrI18n.unableToRegisterNamespaceWithInvalidPrefix.text(prefix, uri));
285         }
286 
287         // Signal the local node type manager ...
288         session.signalNamespaceChanges(global);
289 
290         // Now we're sure the prefix and URI are valid and okay for a custom mapping ...
291         try {
292             registry.register(prefix, uri);
293         } catch (RuntimeException e) {
294             throw new RepositoryException(e.getMessage(), e.getCause());
295         }
296     }
297 
298     /**
299      * {@inheritDoc}
300      * 
301      * @see javax.jcr.NamespaceRegistry#unregisterNamespace(java.lang.String)
302      */
303     public synchronized void unregisterNamespace( String prefix )
304         throws NamespaceException, AccessDeniedException, RepositoryException {
305         CheckArg.isNotNull(prefix, "prefix");
306         checkSession();
307 
308         // Don't need to check permissions for transient registration/unregistration
309         if (behavior.equals(Behavior.WORKSPACE)) {
310             try {
311                 session.checkPermission((Path)null, ModeShapePermissions.REGISTER_NAMESPACE);
312             } catch (AccessControlException ace) {
313                 throw new AccessDeniedException(ace);
314             }
315         }
316 
317         // Look to see whether the prefix is registered ...
318         String uri = registry.getNamespaceForPrefix(prefix);
319         // It is an error to unregister a namespace that is not registered ...
320         if (uri == null) {
321             throw new NamespaceException(JcrI18n.unableToUnregisterPrefixForNamespaceThatIsNotRegistered.text(prefix));
322         }
323         // Unregistering a built-in prefix or URI is invalid ...
324         if (STANDARD_BUILT_IN_PREFIXES.contains(prefix)) {
325             throw new NamespaceException(JcrI18n.unableToUnregisterReservedNamespacePrefix.text(prefix, uri));
326         }
327         if (STANDARD_BUILT_IN_URIS.contains(uri)) {
328             throw new NamespaceException(JcrI18n.unableToUnregisterReservedNamespaceUri.text(prefix, uri));
329         }
330 
331         // Signal the local node type manager ...
332         session.workspace().nodeTypeManager().signalNamespaceChanges();
333 
334         // Now we're sure the prefix is valid and is actually used in a mapping ...
335         try {
336             registry.unregister(uri);
337         } catch (RuntimeException e) {
338             throw new RepositoryException(e.getMessage(), e.getCause());
339         }
340     }
341 
342     /**
343      * {@inheritDoc}
344      * 
345      * @see java.lang.Object#toString()
346      */
347     @Override
348     public String toString() {
349         return registry.toString();
350     }
351 }