/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.logging;
import static java.lang.Character.isJavaIdentifierPart;
import static java.lang.Character.isJavaIdentifierStart;
import static java.lang.Character.isWhitespace;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.logging.logging.LoggingLogger;
import org.jboss.dmr.ModelNode;
/**
* Filter utilties and constants.
*
* @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a>
*/
class Filters {
public static final String ACCEPT = "accept";
public static final String ALL = "all";
public static final String ANY = "any";
public static final String DENY = "deny";
public static final String LEVELS = "levels";
public static final String LEVEL_CHANGE = "levelChange";
public static final String LEVEL_RANGE = "levelRange";
public static final String MATCH = "match";
public static final String NOT = "not";
public static final String SUBSTITUTE = "substitute";
public static final String SUBSTITUTE_ALL = "substituteAll";
/**
* Converts the legacy {@link CommonAttributes#FILTER filter} to the new {@link CommonAttributes#FILTER_SPEC filter
* spec}.
*
* @param value the value to convert
*
* @return the filter expression (filter spec)
*
* @throws org.jboss.as.controller.OperationFailedException
* if a conversion error occurs
*/
static String filterToFilterSpec(final ModelNode value) throws OperationFailedException {
if (!value.isDefined()) {
return null;
}
if (value.hasDefined(CommonAttributes.ACCEPT.getName())) {
return ACCEPT;
} else if (value.hasDefined(CommonAttributes.ALL.getName())) {
final StringBuilder result = new StringBuilder(ALL).append('(');
boolean add = false;
for (ModelNode filterValue : value.get(CommonAttributes.ALL.getName()).asList()) {
if (add) {
result.append(",");
} else {
add = true;
}
result.append(filterToFilterSpec(filterValue));
}
return result.append(")").toString();
} else if (value.hasDefined(CommonAttributes.ANY.getName())) {
final StringBuilder result = new StringBuilder(ANY).append('(');
boolean add = false;
for (ModelNode filterValue : value.get(CommonAttributes.ANY.getName()).asList()) {
if (add) {
result.append(",");
} else {
add = true;
}
result.append(filterToFilterSpec(filterValue));
}
return result.append(")").toString();
} else if (value.hasDefined(CommonAttributes.CHANGE_LEVEL.getName())) {
return String.format("%s(%s)", LEVEL_CHANGE, value.get(CommonAttributes.CHANGE_LEVEL.getName()).asString());
} else if (value.hasDefined(CommonAttributes.DENY.getName())) {
return DENY;
} else if (value.hasDefined(CommonAttributes.LEVEL.getName())) {
return String.format("%s(%s)", LEVELS, value.get(CommonAttributes.LEVEL.getName()).asString());
} else if (value.hasDefined(CommonAttributes.LEVEL_RANGE_LEGACY.getName())) {
final ModelNode levelRange = value.get(CommonAttributes.LEVEL_RANGE_LEGACY.getName());
final StringBuilder result = new StringBuilder(LEVEL_RANGE);
final boolean minInclusive = (levelRange.hasDefined(CommonAttributes.MIN_INCLUSIVE.getName()) && levelRange.get(CommonAttributes.MIN_INCLUSIVE.getName()).asBoolean());
final boolean maxInclusive = (levelRange.hasDefined(CommonAttributes.MAX_INCLUSIVE.getName()) && levelRange.get(CommonAttributes.MAX_INCLUSIVE.getName()).asBoolean());
if (minInclusive) {
result.append("[");
} else {
result.append("(");
}
result.append(levelRange.get(CommonAttributes.MIN_LEVEL.getName()).asString()).append(",");
result.append(levelRange.get(CommonAttributes.MAX_LEVEL.getName()).asString());
if (maxInclusive) {
result.append("]");
} else {
result.append(")");
}
return result.toString();
} else if (value.hasDefined(CommonAttributes.MATCH.getName())) {
return String.format("%s(%s)", MATCH, escapeString(CommonAttributes.MATCH, value));
} else if (value.hasDefined(CommonAttributes.NOT.getName())) {
return String.format("%s(%s)", NOT, filterToFilterSpec(value.get(CommonAttributes.NOT.getName())));
} else if (value.hasDefined(CommonAttributes.REPLACE.getName())) {
final ModelNode replace = value.get(CommonAttributes.REPLACE.getName());
final boolean replaceAll;
if (replace.hasDefined(CommonAttributes.REPLACE_ALL.getName())) {
replaceAll = replace.get(CommonAttributes.REPLACE_ALL.getName()).asBoolean();
} else {
replaceAll = CommonAttributes.REPLACE_ALL.getDefaultValue().asBoolean();
}
final StringBuilder result = new StringBuilder();
if (replaceAll) {
result.append(SUBSTITUTE_ALL);
} else {
result.append(SUBSTITUTE);
}
return result.append("(")
.append(escapeString(CommonAttributes.FILTER_PATTERN, replace))
.append(",").append(escapeString(CommonAttributes.REPLACEMENT, replace))
.append(")").toString();
}
final String name = value.hasDefined(CommonAttributes.FILTER.getName()) ? value.get(CommonAttributes.FILTER.getName()).asString() : value.asString();
throw Logging.createOperationFailure(LoggingLogger.ROOT_LOGGER.invalidFilter(name));
}
/**
* Converts a {@link CommonAttributes#FILTER_SPEC filter spec} to a legacy {@link CommonAttributes#FILTER filter}.
*
* @param value the value to convert
*
* @return the complex filter object
*/
static ModelNode filterSpecToFilter(final String value) {
final ModelNode filter = new ModelNode(CommonAttributes.FILTER.getName()).setEmptyObject();
final Iterator<String> iterator = tokens(value).iterator();
parseFilterExpression(iterator, filter, true);
return filter;
}
private static void parseFilterExpression(final Iterator<String> iterator, final ModelNode model, final boolean outermost) {
if (!iterator.hasNext()) {
if (outermost) {
model.setEmptyObject();
return;
}
throw LoggingLogger.ROOT_LOGGER.unexpectedEnd();
}
final String token = iterator.next();
if (ACCEPT.equals(token)) {
set(CommonAttributes.ACCEPT, model, true);
} else if (DENY.equals(token)) {
set(CommonAttributes.DENY, model, true);
} else if (NOT.equals(token)) {
expect("(", iterator);
parseFilterExpression(iterator, model.get(CommonAttributes.NOT.getName()), false);
expect(")", iterator);
} else if (ALL.equals(token)) {
expect("(", iterator);
do {
final ModelNode m = model.get(CommonAttributes.ALL.getName());
parseFilterExpression(iterator, m, false);
} while (expect(",", ")", iterator));
} else if (ANY.equals(token)) {
expect("(", iterator);
do {
final ModelNode m = model.get(CommonAttributes.ANY.getName());
parseFilterExpression(iterator, m, false);
} while (expect(",", ")", iterator));
} else if (LEVEL_CHANGE.equals(token)) {
expect("(", iterator);
final String levelName = expectName(iterator);
set(CommonAttributes.CHANGE_LEVEL, model, levelName);
expect(")", iterator);
} else if (LEVELS.equals(token)) {
expect("(", iterator);
final Set<String> levels = new HashSet<String>();
do {
levels.add(expectName(iterator));
} while (expect(",", ")", iterator));
// The model only allows for one level,just use the first
if (levels.iterator().hasNext()) set(CommonAttributes.LEVEL, model, levels.iterator().next());
} else if (LEVEL_RANGE.equals(token)) {
final ModelNode levelRange = model.get(CommonAttributes.LEVEL_RANGE_LEGACY.getName());
final boolean minInclusive = expect("[", "(", iterator);
if (minInclusive) set(CommonAttributes.MIN_INCLUSIVE, levelRange, minInclusive);
final String minLevel = expectName(iterator);
set(CommonAttributes.MIN_LEVEL, levelRange, minLevel);
expect(",", iterator);
final String maxLevel = expectName(iterator);
set(CommonAttributes.MAX_LEVEL, levelRange, maxLevel);
final boolean maxInclusive = expect("]", ")", iterator);
if (maxInclusive) set(CommonAttributes.MAX_INCLUSIVE, levelRange, maxInclusive);
} else if (MATCH.equals(token)) {
expect("(", iterator);
final String pattern = expectString(iterator);
set(CommonAttributes.MATCH, model, pattern);
expect(")", iterator);
} else if (SUBSTITUTE.equals(token)) {
final ModelNode substitute = model.get(CommonAttributes.REPLACE.getName());
substitute.get(CommonAttributes.REPLACE_ALL.getName()).set(false);
expect("(", iterator);
final String pattern = expectString(iterator);
set(CommonAttributes.FILTER_PATTERN, substitute, pattern);
expect(",", iterator);
final String replacement = expectString(iterator);
set(CommonAttributes.REPLACEMENT, substitute, replacement);
expect(")", iterator);
} else if (SUBSTITUTE_ALL.equals(token)) {
final ModelNode substitute = model.get(CommonAttributes.REPLACE.getName());
substitute.get(CommonAttributes.REPLACE_ALL.getName()).set(true);
expect("(", iterator);
final String pattern = expectString(iterator);
set(CommonAttributes.FILTER_PATTERN, substitute, pattern);
expect(",", iterator);
final String replacement = expectString(iterator);
set(CommonAttributes.REPLACEMENT, substitute, replacement);
expect(")", iterator);
} else {
final String name = expectName(iterator);
throw LoggingLogger.ROOT_LOGGER.filterNotFound(name);
}
}
private static String expectName(Iterator<String> iterator) {
if (iterator.hasNext()) {
final String next = iterator.next();
if (isJavaIdentifierStart(next.codePointAt(0))) {
return next;
}
}
throw LoggingLogger.ROOT_LOGGER.expectedIdentifier();
}
private static String expectString(final Iterator<String> iterator) {
if (iterator.hasNext()) {
final String next = iterator.next();
if (next.codePointAt(0) == '"') {
return next.substring(1);
}
}
throw LoggingLogger.ROOT_LOGGER.expectedString();
}
private static boolean expect(final String trueToken, final String falseToken, final Iterator<String> iterator) {
final boolean hasNext = iterator.hasNext();
final String next = hasNext ? iterator.next() : null;
final boolean result;
if (!hasNext || !((result = trueToken.equals(next)) || falseToken.equals(next))) {
throw LoggingLogger.ROOT_LOGGER.expected(trueToken, falseToken);
}
return result;
}
private static void expect(String token, Iterator<String> iterator) {
if (!iterator.hasNext() || !token.equals(iterator.next())) {
throw LoggingLogger.ROOT_LOGGER.expected(token);
}
}
private static void set(final AttributeDefinition attribute, final ModelNode model, final String value) {
set(attribute.getName(), model, value);
}
private static void set(final AttributeDefinition attribute, final ModelNode model, final boolean value) {
set(attribute.getName(), model, value);
}
private static void set(final String name, final ModelNode model, final String value) {
model.get(name).set(value);
}
private static void set(final String name, final ModelNode model, final boolean value) {
model.get(name).set(value);
}
private static List<String> tokens(final String source) {
final List<String> tokens = new ArrayList<String>();
final int length = source.length();
int idx = 0;
while (idx < length) {
int ch;
ch = source.codePointAt(idx);
if (isWhitespace(ch)) {
ch = source.codePointAt(idx);
idx = source.offsetByCodePoints(idx, 1);
} else if (isJavaIdentifierStart(ch)) {
int start = idx;
do {
idx = source.offsetByCodePoints(idx, 1);
} while (idx < length && isJavaIdentifierPart(ch = source.codePointAt(idx)));
tokens.add(source.substring(start, idx));
} else if (ch == '"') {
final StringBuilder b = new StringBuilder();
// tag token as a string
b.append('"');
idx = source.offsetByCodePoints(idx, 1);
while (idx < length && (ch = source.codePointAt(idx)) != '"') {
ch = source.codePointAt(idx);
if (ch == '\\') {
idx = source.offsetByCodePoints(idx, 1);
if (idx == length) {
throw LoggingLogger.ROOT_LOGGER.truncatedFilterExpression();
}
ch = source.codePointAt(idx);
switch (ch) {
case '\\':
b.append('\\');
break;
case '\'':
b.append('\'');
break;
case '"':
b.append('"');
break;
case 'b':
b.append('\b');
break;
case 'f':
b.append('\f');
break;
case 'n':
b.append('\n');
break;
case 'r':
b.append('\r');
break;
case 't':
b.append('\t');
break;
default:
throw LoggingLogger.ROOT_LOGGER.invalidEscapeFoundInFilterExpression();
}
} else {
b.appendCodePoint(ch);
}
idx = source.offsetByCodePoints(idx, 1);
}
idx = source.offsetByCodePoints(idx, 1);
tokens.add(b.toString());
} else {
int start = idx;
idx = source.offsetByCodePoints(idx, 1);
tokens.add(source.substring(start, idx));
}
}
return tokens;
}
private static String escapeString(final AttributeDefinition attribute, final ModelNode value) throws OperationFailedException {
return String.format("\"%s\"", value.get(attribute.getName()).asString().replace("\\", "\\\\"));
}
}