| /* |
| * 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; |
| } |
| |
| } |