blob: 38729b5025f26ef8c3f09c2218c940967ceb85d3 [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.plugins.name;
import static javax.jcr.NamespaceRegistry.PREFIX_JCR;
import static javax.jcr.NamespaceRegistry.PREFIX_MIX;
import static javax.jcr.NamespaceRegistry.PREFIX_NT;
import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
import static org.apache.jackrabbit.JcrConstants.JCR_SYSTEM;
import static org.apache.jackrabbit.oak.api.Type.STRING;
import static org.apache.jackrabbit.oak.spi.namespace.NamespaceConstants.REP_NAMESPACES;
import static org.apache.jackrabbit.oak.spi.namespace.NamespaceConstants.REP_NSDATA;
import static org.apache.jackrabbit.oak.spi.namespace.NamespaceConstants.REP_URIS;
import static org.apache.jackrabbit.oak.plugins.name.Namespaces.isValidPrefix;
import java.util.Locale;
import java.util.Set;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import org.apache.jackrabbit.oak.api.CommitFailedException;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.spi.commit.DefaultEditor;
import org.apache.jackrabbit.oak.spi.commit.Editor;
import org.apache.jackrabbit.oak.spi.namespace.NamespaceConstants;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.jackrabbit.oak.spi.state.NodeState;
/**
* TODO document
*/
class NamespaceEditor extends DefaultEditor {
/**
* Flag controlling the strictness of the check to disallow modifications to
* the internal node 'ns:data'. If enabled, any changes will throw a
* CommitFailedException, otherwise the index node will be rebuilt on any
* external change.
*/
private static final boolean strictIntegrityCheck = Boolean.getBoolean("oak.strictIntegrityCheck");
private final NodeBuilder builder;
private boolean modified = false;
private final NodeState namespaces;
public NamespaceEditor(NodeState root, NodeBuilder builder) {
this.namespaces = root.getChildNode(JCR_SYSTEM).getChildNode(
REP_NAMESPACES);
this.builder = builder;
}
@Override
public void propertyAdded(PropertyState after) throws CommitFailedException {
String prefix = after.getName();
// ignore jcr:primaryType
if (JCR_PRIMARYTYPE.equals(prefix)) {
return;
}
if (namespaces.hasProperty(prefix)) {
throw new CommitFailedException(CommitFailedException.NAMESPACE, 1,
"Namespace mapping already registered: " + prefix);
} else if (isValidPrefix(prefix)) {
if (after.isArray() || !STRING.equals(after.getType())) {
throw new CommitFailedException(
CommitFailedException.NAMESPACE, 2,
"Invalid namespace mapping: " + prefix);
} else if (prefix.toLowerCase(Locale.ENGLISH).startsWith("xml") && namespaces.hasProperty("xml")) {
throw new CommitFailedException(
CommitFailedException.NAMESPACE, 3,
"XML prefixes are reserved: " + prefix);
} else if (containsValue(namespaces, after.getValue(STRING))) {
throw modificationNotAllowed(prefix);
}
} else {
throw new CommitFailedException(CommitFailedException.NAMESPACE, 4,
"Not a valid namespace prefix: " + prefix);
}
modified = true;
}
private static boolean containsValue(NodeState namespaces, String value) {
return Iterables.contains(namespaces.getChildNode(REP_NSDATA).getStrings(REP_URIS), value);
}
@Override
public void propertyChanged(PropertyState before, PropertyState after)
throws CommitFailedException {
// TODO allow changes if there is no content referencing the mappings
throw modificationNotAllowed(after.getName());
}
@Override
public void propertyDeleted(PropertyState before)
throws CommitFailedException {
// FIXME Desired Behavior: if we enable it, there are a few generic
// #unregister tests that fail
// TODO allow changes if there is no content referencing the mappings
// throw modificationNotAllowed(before.getName());
// FIXME Best effort backwards compatible:
if (jcrSystemNS.contains(before.getName())) {
throw modificationNotAllowed(before.getName());
}
modified = true;
}
private static Set<String> jcrSystemNS = ImmutableSet.of(PREFIX_JCR,
PREFIX_NT, PREFIX_MIX, NamespaceConstants.PREFIX_SV);
private static CommitFailedException modificationNotAllowed(String prefix) {
return new CommitFailedException(CommitFailedException.NAMESPACE, 5,
"Namespace modification not allowed: " + prefix);
}
@Override
public void leave(NodeState before, NodeState after)
throws CommitFailedException {
if (!modified) {
return;
}
Namespaces.buildIndexNode(builder.getChildNode(JCR_SYSTEM).child(
REP_NAMESPACES));
}
@Override
public Editor childNodeChanged(String name, NodeState before,
NodeState after) throws CommitFailedException {
if (REP_NSDATA.equals(name) && !before.equals(after)) {
if (strictIntegrityCheck) {
throw modificationNotAllowed(name);
} else {
modified = true;
}
}
return null;
}
}