blob: 2662aa4eec6b092642edf82862bd49bc5b8f0335 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.jackrabbit.oak.jcr.session;
import static com.google.common.collect.Iterables.toArray;
import static com.google.common.collect.Sets.newHashSet;
import static java.util.Collections.emptyList;
import static org.apache.jackrabbit.oak.api.Type.STRINGS;
import static org.apache.jackrabbit.oak.plugins.name.NamespaceConstants.REP_PREFIXES;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.jcr.NamespaceException;
import javax.jcr.Session;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Root;
import org.apache.jackrabbit.oak.namepath.LocalNameMapper;
import org.apache.jackrabbit.util.XMLChar;
import com.google.common.collect.Maps;
/**
* <code>SessionNamespaces</code> implements namespace handling on the JCR
* Session level. That is, it maintains a map of session local namespace
* re-mappings and takes a snapshot of the namespace registry when initialized
* (see JCR 2.0 specification, section 3.5.1).
*/
class SessionNamespaces extends LocalNameMapper {
SessionNamespaces(@Nonnull Root root) {
super(root, Maps.<String, String>newHashMap());
}
// The code below was initially copied from JCR Commons AbstractSession,
// but has since been radically modified
/**
* @see Session#setNamespacePrefix(String, String)
*/
synchronized void setNamespacePrefix(String prefix, String uri)
throws NamespaceException {
if (prefix == null) {
throw new IllegalArgumentException("Prefix must not be null");
} else if (uri == null) {
throw new IllegalArgumentException("Namespace must not be null");
} else if (prefix.isEmpty()) {
throw new NamespaceException(
"Empty prefix is reserved and can not be remapped");
} else if (uri.isEmpty()) {
throw new NamespaceException(
"Default namespace is reserved and can not be remapped");
} else if (prefix.toLowerCase(Locale.ENGLISH).startsWith("xml")) {
throw new NamespaceException(
"XML prefixes are reserved: " + prefix);
} else if (!XMLChar.isValidNCName(prefix)) {
throw new NamespaceException(
"Prefix is not a valid XML NCName: " + prefix);
}
// remove the possible existing mapping for the given prefix
local.remove(prefix);
// remove the possible existing mapping(s) for the given URI
Set<String> prefixes = new HashSet<String>();
for (Map.Entry<String, String> entry : local.entrySet()) {
if (entry.getValue().equals(uri)) {
prefixes.add(entry.getKey());
}
}
local.keySet().removeAll(prefixes);
// add the new mapping
local.put(prefix, uri);
}
/**
* @see Session#getNamespacePrefixes()
*/
synchronized String[] getNamespacePrefixes() {
// get registered namespace prefixes
Iterable<String> global = emptyList();
PropertyState property = nsdata.getProperty(REP_PREFIXES);
if (property != null && property.getType() == STRINGS) {
global = property.getValue(STRINGS);
}
// unless there are local remappings just use the registered ones
if (local.isEmpty()) {
return toArray(global, String.class);
}
Set<String> prefixes = newHashSet(global);
// remove the prefixes of the namespaces that have been remapped
for (String uri : local.values()) {
String prefix = getOakPrefixOrNull(uri);
if (prefix != null) {
prefixes.remove(prefix);
}
}
// add the prefixes in local remappings
prefixes.addAll(local.keySet());
return prefixes.toArray(new String[prefixes.size()]);
}
/**
* @see Session#getNamespaceURI(String)
*/
synchronized String getNamespaceURI(String prefix)
throws NamespaceException {
// first check local remappings
String uri = local.get(prefix);
if (uri == null) {
// Not in snapshot mappings, try the global ones
uri = getOakURIOrNull(prefix);
if (uri == null || local.containsValue(uri)) {
// URI is either not registered or locally mapped to some
// other prefix, so there are no mappings for this prefix
throw new NamespaceException(
"Unknown namespace prefix: " + prefix);
}
}
return uri;
}
/**
* @see Session#getNamespacePrefix(String)
*/
synchronized String getNamespacePrefix(String uri)
throws NamespaceException {
// first check local remappings
for (Map.Entry<String, String> entry : local.entrySet()) {
if (entry.getValue().equals(uri)) {
return entry.getKey();
}
}
// then try the global mappings
String prefix = getOakPrefixOrNull(uri);
if (prefix == null) {
throw new NamespaceException("Unknown namespace URI: " + uri);
}
// Generate a new prefix if already locally mapped to something else
String base = prefix;
for (int i = 2; local.containsKey(prefix); i++) {
prefix = base + i;
}
if (base != prefix) {
local.put(prefix, uri);
}
return prefix;
}
/**
* Clears the re-mapped namespaces map.
*/
synchronized void clear() {
local.clear();
}
}