blob: fe395cc1499c5bb5d3603e63e08840e4c4bde8b4 [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 com.google.common.collect.Sets.newHashSet;
import static org.apache.jackrabbit.oak.spi.namespace.NamespaceConstants.REP_NSDATA;
import static org.apache.jackrabbit.oak.spi.namespace.NamespaceConstants.REP_PREFIXES;
import java.util.Set;
import org.apache.jackrabbit.oak.api.CommitFailedException;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.spi.commit.DefaultValidator;
import org.apache.jackrabbit.oak.spi.commit.Validator;
import org.apache.jackrabbit.oak.spi.lifecycle.RepositoryInitializer;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* TODO document
*/
class NameValidator extends DefaultValidator {
private static final Logger LOG = LoggerFactory.getLogger(NameValidator.class);
private final NodeState namespaces;
private final Set<String> prefixes;
/**
* Flag controlling the strictness of the namespace checks. if {@code true}
* namespaces existence will not be checked, otherwise referencing a
* non-existent namespace will cause a {@link CommitFailedException}.
*
* Used only for the case where lucene index definitions are registered via
* {@link RepositoryInitializer}s.
*/
private final boolean initPhase;
private final boolean strictInitialNSChecks = Boolean.getBoolean("oak.strictInitialNSChecks");
NameValidator(NodeState namespaces, boolean initPhase) {
this.namespaces = namespaces;
this.prefixes = newHashSet(namespaces.getChildNode(REP_NSDATA).getStrings(REP_PREFIXES));
this.initPhase = initPhase;
}
// escape non-printable non-USASCII characters using standard Java escapes
protected static String getPrintableName(String s) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c >= ' ' && c < 127) {
sb.append(c);
} else if (c == '\b') {
sb.append("\\b");
} else if (c == '\f') {
sb.append("\\f");
} else if (c == '\n') {
sb.append("\\n");
} else if (c == '\r') {
sb.append("\\r");
} else if (c == '\t') {
sb.append("\\t");
} else {
sb.append(String.format("\\u%04x", (int) c));
}
}
return sb.toString();
}
protected void checkValidName(String name) throws CommitFailedException {
int colon = name.indexOf(':');
if (colon > 0) {
String prefix = name.substring(0, colon);
checkPrefix(prefix);
}
String local = name.substring(colon + 1);
int n = local.length();
if (n > 3 && local.charAt(n - 1) == ']') {
int i = n - 2;
while (i > 1 && Character.isDigit(local.charAt(i))) {
i--;
}
if (local.charAt(i) != '[') {
throw new CommitFailedException(
CommitFailedException.NAME, 2, "Invalid name index in: " + getPrintableName(name));
} else {
local = local.substring(0, i);
}
}
if (!Namespaces.isValidLocalName(local)) {
throw new CommitFailedException(
CommitFailedException.NAME, 3, "Invalid name: " + getPrintableName(name));
}
}
private void checkPrefix(String prefix) throws CommitFailedException {
if (prefix.isEmpty() || !contains(prefixes, namespaces, prefix)) {
String msg = "Invalid namespace prefix(" + prefixes + "): " + prefix;
if (initPhase && !strictInitialNSChecks) {
LOG.warn(msg);
return;
}
throw new CommitFailedException(CommitFailedException.NAME, 1, msg);
}
}
private static boolean contains(Set<String> prefixes, NodeState namespaces, String prefix) {
return prefixes.contains(prefix) || Namespaces.collectNamespaces(namespaces.getProperties()).containsKey(prefix);
}
protected void checkValidValue(PropertyState property)
throws CommitFailedException {
if (Type.NAME.equals(property.getType()) || Type.NAMES.equals(property.getType())) {
for (String value : property.getValue(Type.NAMES)) {
checkValidValue(value);
}
} else if (Type.PATH.equals(property.getType()) || Type.PATHS.equals(property.getType())) {
for (String value : property.getValue(Type.PATHS)) {
for (String name: PathUtils.elements(value)) {
checkValidValue(name);
}
}
}
}
protected void checkValidValue(String value)
throws CommitFailedException {
checkValidName(value);
}
//-------------------------------------------------------< NodeValidator >
@Override
public void propertyAdded(PropertyState after)
throws CommitFailedException {
checkValidName(after.getName());
checkValidValue(after);
}
@Override
public void propertyChanged(PropertyState before, PropertyState after)
throws CommitFailedException {
checkValidValue(after);
}
@Override
public void propertyDeleted(PropertyState before) {
// do nothing
}
@Override
public Validator childNodeAdded(String name, NodeState after)
throws CommitFailedException {
checkValidName(name);
return this;
}
@Override
public Validator childNodeChanged(
String name, NodeState before, NodeState after) {
return this;
}
@Override
public Validator childNodeDeleted(String name, NodeState before) {
return null;
}
}