org.modeshape.jcr.JcrNamespaceRegistry Maven / Gradle / Ivy
/*
* ModeShape (http://www.modeshape.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.modeshape.jcr;
import java.security.AccessControlException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.jcr.AccessDeniedException;
import javax.jcr.NamespaceException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.xml.XMLConstants;
import org.modeshape.common.annotation.NotThreadSafe;
import org.modeshape.common.util.CheckArg;
import org.modeshape.common.xml.XmlCharacters;
import org.modeshape.jcr.value.Name;
import org.modeshape.jcr.value.NamespaceRegistry;
import org.modeshape.jcr.value.NamespaceRegistry.Namespace;
import org.modeshape.jcr.value.ValueFormatException;
/**
* A thread-safe JCR {@link javax.jcr.NamespaceRegistry} implementation that has the standard JCR namespaces pre-registered and
* enforces the JCR semantics for {@link #registerNamespace(String, String) registering} and {@link #unregisterNamespace(String)
* unregistering} namespaces.
*
* Note that this implementation is {@link NotThreadSafe not thread safe}, since it is used within a single {@link JcrWorkspace}
* and single {@link JcrSession}, and according to the JCR specification these interfaces are not thread safe.
*
*/
@NotThreadSafe
class JcrNamespaceRegistry implements org.modeshape.jcr.api.NamespaceRegistry {
public static enum Behavior {
SESSION,
WORKSPACE;
}
static final String DEFAULT_NAMESPACE_PREFIX = "";
static final String DEFAULT_NAMESPACE_URI = "";
static final String XML_NAMESPACE_PREFIX = XMLConstants.XML_NS_PREFIX;
static final String XML_NAMESPACE_URI = XMLConstants.XML_NS_URI;
static final String XMLNS_NAMESPACE_PREFIX = XMLConstants.XMLNS_ATTRIBUTE;
static final String XMLNS_NAMESPACE_URI = XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
static final String XML_SCHEMA_NAMESPACE_PREFIX = "xs";
static final String XML_SCHEMA_NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema";
static final String XML_SCHEMA_INSTANCE_NAMESPACE_PREFIX = "xsi";
static final String XML_SCHEMA_INSTANCE_NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema-instance";
static final Set STANDARD_BUILT_IN_PREFIXES;
static final Set STANDARD_BUILT_IN_URIS;
static final Map STANDARD_BUILT_IN_NAMESPACES_BY_PREFIX;
static final Map STANDARD_BUILT_IN_PREFIXES_BY_NAMESPACE;
static {
// Set up the standard namespaces ...
Map namespaces = new HashMap();
namespaces.put(DEFAULT_NAMESPACE_PREFIX, DEFAULT_NAMESPACE_URI);
namespaces.put(JcrLexicon.Namespace.PREFIX, JcrLexicon.Namespace.URI);
namespaces.put(JcrNtLexicon.Namespace.PREFIX, JcrNtLexicon.Namespace.URI);
namespaces.put(JcrMixLexicon.Namespace.PREFIX, JcrMixLexicon.Namespace.URI);
namespaces.put(JcrSvLexicon.Namespace.PREFIX, JcrSvLexicon.Namespace.URI);
namespaces.put(XML_NAMESPACE_PREFIX, XML_NAMESPACE_URI);
namespaces.put(XMLNS_NAMESPACE_PREFIX, XMLNS_NAMESPACE_URI);
namespaces.put(XML_SCHEMA_NAMESPACE_PREFIX, XML_SCHEMA_NAMESPACE_URI);
namespaces.put(XML_SCHEMA_INSTANCE_NAMESPACE_PREFIX, XML_SCHEMA_INSTANCE_NAMESPACE_URI);
namespaces.put(ModeShapeLexicon.Namespace.PREFIX, ModeShapeLexicon.Namespace.URI);
// Set up the reverse map for the standard namespaces ...
Map prefixes = new HashMap();
for (Map.Entry entry : namespaces.entrySet()) {
String uri = entry.getValue();
String prefix = entry.getKey();
prefixes.put(uri, prefix);
}
// Now set up the unmodifiable static collections ...
STANDARD_BUILT_IN_NAMESPACES_BY_PREFIX = Collections.unmodifiableMap(namespaces);
STANDARD_BUILT_IN_PREFIXES_BY_NAMESPACE = Collections.unmodifiableMap(prefixes);
STANDARD_BUILT_IN_PREFIXES = Collections.unmodifiableSet(namespaces.keySet());
STANDARD_BUILT_IN_URIS = Collections.unmodifiableSet(new HashSet(namespaces.values()));
}
private final Behavior behavior;
private final NamespaceRegistry registry;
private final NamespaceRegistry workspaceRegistry;
private final JcrSession session;
protected JcrNamespaceRegistry( NamespaceRegistry workspaceRegistry,
JcrSession session ) {
this(Behavior.WORKSPACE, null, workspaceRegistry, session);
}
JcrNamespaceRegistry( Behavior behavior,
NamespaceRegistry localRegistry,
NamespaceRegistry workspaceRegistry,
JcrSession session ) {
this.behavior = behavior;
this.registry = localRegistry != null ? localRegistry : workspaceRegistry;
this.workspaceRegistry = workspaceRegistry;
this.session = session;
assert this.behavior != null;
assert this.registry != null;
assert this.workspaceRegistry != null;
assert this.session != null;
}
/**
* Check that the session is still valid and {@link Session#isLive() live}.
*
* @throws RepositoryException if the session is not valid or live
*/
protected final void checkSession() throws RepositoryException {
session.checkLive();
}
@Override
public String getPrefix( String uri ) throws NamespaceException, RepositoryException {
checkSession();
if (behavior == Behavior.WORKSPACE) {
// Check the standard ones first, ensuring that invalid changes to the persistent storage don't matter ...
String prefix = STANDARD_BUILT_IN_PREFIXES_BY_NAMESPACE.get(uri);
if (prefix != null) return prefix;
}
String prefix = registry.getPrefixForNamespaceUri(uri, false);
if (prefix == null) {
throw new NamespaceException(JcrI18n.noNamespaceWithUri.text(uri));
}
return prefix;
}
@Override
public String[] getPrefixes() throws RepositoryException {
checkSession();
Set namespaces = registry.getNamespaces();
String[] prefixes = new String[namespaces.size()];
int i = 0;
for (Namespace namespace : namespaces) {
prefixes[i++] = namespace.getPrefix();
}
return prefixes;
}
@Override
public String getURI( String prefix ) throws NamespaceException, RepositoryException {
checkSession();
if (behavior == Behavior.WORKSPACE) {
// Check the standard ones first, ensuring that invalid changes to the persistent storage don't matter ...
String uri = STANDARD_BUILT_IN_NAMESPACES_BY_PREFIX.get(prefix);
if (uri != null) return uri;
}
String uri = registry.getNamespaceForPrefix(prefix);
if (uri == null) {
throw new NamespaceException(JcrI18n.noNamespaceWithPrefix.text(prefix));
}
return uri;
}
@Override
public String[] getURIs() throws RepositoryException {
checkSession();
Set namespaces = registry.getNamespaces();
String[] uris = new String[namespaces.size()];
int i = 0;
for (Namespace namespace : namespaces) {
uris[i++] = namespace.getNamespaceUri();
}
return uris;
}
@Override
public boolean isRegisteredPrefix( String prefix ) {
return registry.getNamespaceForPrefix(prefix) != null;
}
@Override
public boolean isRegisteredUri( String uri ) {
return registry.isRegisteredNamespaceUri(uri);
}
@Override
public synchronized void registerNamespace( String prefix,
String uri ) throws NamespaceException, RepositoryException {
CheckArg.isNotNull(prefix, "prefix");
CheckArg.isNotNull(uri, "uri");
checkSession();
// Check the zero-length prefix and zero-length URI ...
if (DEFAULT_NAMESPACE_PREFIX.equals(prefix) || DEFAULT_NAMESPACE_URI.equals(uri)) {
throw new NamespaceException(JcrI18n.unableToChangeTheDefaultNamespace.text());
}
// Check whether the prefix begins with 'xml' (in any case) ...
if (prefix.toLowerCase().startsWith(XML_NAMESPACE_PREFIX)) {
throw new NamespaceException(JcrI18n.unableToRegisterNamespaceUsingXmlPrefix.text(prefix, uri));
}
// The prefix must be a valid XML Namespace prefix (i.e., a valid NCName) ...
if (!XmlCharacters.isValidNcName(prefix)) {
throw new NamespaceException(JcrI18n.unableToRegisterNamespaceWithInvalidPrefix.text(prefix, uri));
}
// The prefix must also be a valid JCR name ...
try {
session.nameFactory().create(prefix);
} catch (ValueFormatException e) {
throw new NamespaceException(JcrI18n.unableToRegisterNamespaceWithInvalidPrefix.text(prefix, uri));
}
String existingPrefixForURI = registry.getPrefixForNamespaceUri(uri, false);
if (prefix.equals(existingPrefixForURI)) {
//this is a no-op because we're trying to register the URI under the same prefix
return;
}
boolean global = false;
switch (behavior) {
case SESSION:
// --------------------------------------
// JSR-283 Session remapping behavior ...
// --------------------------------------
// Section 5.11:
// "All local mappings already present in the Session that include either the specified prefix
// or the specified uri are removed and the new mapping is added."
String existingUriForPrefix = registry.getNamespaceForPrefix(prefix);
if (existingUriForPrefix != null) {
registry.unregister(existingUriForPrefix);
}
registry.unregister(uri);
break;
case WORKSPACE:
// --------------------------------------------------
// JSR-170 & JSR-283 Workspace namespace registry ...
// --------------------------------------------------
global = true;
// Check whether the prefix or URI are reserved (case-sensitive) ...
if (STANDARD_BUILT_IN_PREFIXES.contains(prefix)) {
throw new NamespaceException(JcrI18n.unableToRegisterReservedNamespacePrefix.text(prefix, uri));
}
if (STANDARD_BUILT_IN_URIS.contains(uri)) {
throw new NamespaceException(JcrI18n.unableToRegisterReservedNamespaceUri.text(prefix, uri));
}
try {
session.checkWorkspacePermission(session.workspaceName(), ModeShapePermissions.REGISTER_NAMESPACE);
} catch (AccessControlException ace) {
throw new AccessDeniedException(ace);
}
if (existingPrefixForURI != null) {
//the uri is already mapped under a different prefix so check that it's not in use
checkURINotInUse(uri);
}
registry.unregister(uri);
break;
default:
assert false; // should never happen
}
// Now we're sure the prefix and URI are valid and okay for a custom mapping ...
try {
registry.register(prefix, uri);
// Signal the local node type manager ...
session.signalNamespaceChanges(global);
} catch (RuntimeException e) {
throw new RepositoryException(e.getMessage(), e.getCause());
}
}
@Override
public synchronized void unregisterNamespace( String prefix )
throws NamespaceException, AccessDeniedException, RepositoryException {
CheckArg.isNotNull(prefix, "prefix");
checkSession();
// Don't need to check permissions for transient registration/unregistration
if (behavior.equals(Behavior.WORKSPACE)) {
try {
session.checkWorkspacePermission(session.workspaceName(), ModeShapePermissions.REGISTER_NAMESPACE);
} catch (AccessControlException ace) {
throw new AccessDeniedException(ace);
}
}
// Look to see whether the prefix is registered ...
String uri = registry.getNamespaceForPrefix(prefix);
// It is an error to unregister a namespace that is not registered ...
if (uri == null) {
throw new NamespaceException(JcrI18n.unableToUnregisterPrefixForNamespaceThatIsNotRegistered.text(prefix));
}
// Unregistering a built-in prefix or URI is invalid ...
if (STANDARD_BUILT_IN_PREFIXES.contains(prefix)) {
throw new NamespaceException(JcrI18n.unableToUnregisterReservedNamespacePrefix.text(prefix, uri));
}
if (STANDARD_BUILT_IN_URIS.contains(uri)) {
throw new NamespaceException(JcrI18n.unableToUnregisterReservedNamespaceUri.text(prefix, uri));
}
// Do not allow to unregister a namespace which is used by a node type
checkURINotInUse(uri);
boolean global = false;
switch (behavior) {
case WORKSPACE: {
global = true;
break;
}
case SESSION: {
break;
}
default: {
//should never happen
assert false;
}
}
// Now we're sure the prefix is valid and is actually used in a mapping ...
try {
registry.unregister(uri);
session.signalNamespaceChanges(global);
} catch (RuntimeException e) {
throw new RepositoryException(e.getMessage(), e.getCause());
}
}
private void checkURINotInUse( String uri ) throws RepositoryException {
NodeTypes nodeTypes = session.nodeTypes();
for (Name nodeTypeName : nodeTypes.getAllNodeTypeNames()) {
if (nodeTypeName.getNamespaceUri().equals(uri)) {
throw new NamespaceException(JcrI18n.unableToUnregisterPrefixForNamespaceUsedByNodeType.text(uri, nodeTypeName));
}
}
}
@Override
public String registerNamespace( String uri )
throws UnsupportedRepositoryOperationException, AccessDeniedException, RepositoryException {
CheckArg.isNotNull(uri, "uri");
checkSession();
// See if the namespace is already registered, so this will handle all built-in namespaces ...
String prefix = registry.getPrefixForNamespaceUri(uri, false);
if (prefix != null) {
return prefix; // permission checks necessary ...
}
boolean global = false;
switch (behavior) {
case SESSION:
// --------------------------------------
// JSR-283 Session remapping behavior ...
// --------------------------------------
break;
case WORKSPACE:
// --------------------------------------------------
// JSR-170 & JSR-283 Workspace namespace registry ...
// --------------------------------------------------
global = true;
try {
session.checkWorkspacePermission(session.workspaceName(), ModeShapePermissions.REGISTER_NAMESPACE);
} catch (AccessControlException ace) {
throw new AccessDeniedException(ace);
}
break;
default:
assert false; // should never happen
}
// Now we're sure the prefix and URI are valid and okay for a custom mapping ...
try {
prefix = registry.getPrefixForNamespaceUri(uri, true);
// Signal the local node type manager ...
session.signalNamespaceChanges(global);
return prefix;
} catch (RuntimeException e) {
throw new RepositoryException(e.getMessage(), e.getCause());
}
}
@Override
public String toString() {
return registry.toString();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy