/*
* JBoss, Home of Professional Open Source.
* Copyright 2015, 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.cli.util;
import java.io.File;
import org.jboss.as.cli.parsing.UnresolvedExpressionException;
import org.wildfly.security.manager.WildFlySecurityManager;
/**
*
* @author Alexey Loubyansky
*/
public class CLIExpressionResolver {
/** File separator value */
private static final String FILE_SEPARATOR = File.separator;
/** Path separator value */
private static final String PATH_SEPARATOR = File.pathSeparator;
/** File separator alias */
private static final String FILE_SEPARATOR_ALIAS = "/";
/** Path separator alias */
private static final String PATH_SEPARATOR_ALIAS = ":";
/** Environment variable prefix */
private static final String ENV_PREFIX = "env.";
private static final int INITIAL = 0;
private static final int DOLLAR = 1;
/**
* Attempts to substitute all the found expressions in the input
* with their corresponding resolved values.
* If any of the found expressions failed to resolve the exception will be thrown.
* If the input does not contain any expression, the input is returned as is.
*
* @param input the input string
* @return the input with resolved expressions
* @throws UnresolvedExpressionException in case an expression could not be resolved
*/
public static String resolve(String input) throws UnresolvedExpressionException {
return resolve(input, true);
}
/**
* Attempts to substitute all the found expressions in the input
* with their corresponding resolved values.
* If any of the found expressions failed to resolve or
* if the input does not contain any expression, the input is returned as is.
*
* @param input the input string
* @return the input with resolved expressions or the original input in case
* the input didn't contain any expressions or at least one of the
* expressions could not be resolved
*/
public static String resolveOrOriginal(String input) {
try {
return resolve(input, true);
} catch (UnresolvedExpressionException e) {
return input;
}
}
/**
* Attempts to substitute all the found expressions in the input with their corresponding resolved values. If any of the
* found expressions failed to resolve, try to resolve expressions as more as possible and return the resolved value. If the
* input does not contain any expression, the input is returned as is. see https://issues.jboss.org/browse/WFCORE-1980,
*
* @param input the input string
* @return the input with resolved expressions or the original input in case the input didn't contain any expressions
*/
public static String resolveLax(String input) {
try {
return resolve(input, false);
} catch (UnresolvedExpressionException e) {
// XXX OK. It should not reach here as exceptionIfNotResolved is false.
return input;
}
}
private static String resolve(String input, boolean exceptionIfNotResolved)
throws UnresolvedExpressionException {
int state = INITIAL;
int i = 0;
while(i < input.length()) {
final char c = input.charAt(i++);
if(c == '$') {
if(state == DOLLAR) {
state = INITIAL;
final StringBuilder buf = new StringBuilder(input.length() - 1);
buf.append(input.substring(0, --i));
if(i + 1 < input.length()) {
buf.append(input.substring(i + 1));
}
input = buf.toString();
} else {
state = DOLLAR;
}
} else if(c == '{') {
if(state == DOLLAR) {
state = INITIAL;
final String[] inputRef = new String[]{input};
if(i - 2 == resolveProperty2(inputRef, i - 2, exceptionIfNotResolved)) {
input = inputRef[0];
i -= 2;
}
}
}
}
return input;
}
/**
*
* @param location the index of '$' starting the system property
* @param exceptionIfNotResolved whether to ignore unresolved expression or throw an exception
* @return the index the parsing should continue from
* @throws UnresolvedExpressionException in case the expression could not be resolved
*/
public static String resolveProperty(String input, final int location, boolean exceptionIfNotResolved)
throws UnresolvedExpressionException {
final String[] inputRef = new String[]{input};
if(location == resolveProperty2(inputRef, location, exceptionIfNotResolved)) {
return inputRef[0];
}
return input;
}
private static int resolveProperty2(String[] inputRef, final int location, boolean exceptionIfNotResolved)
throws UnresolvedExpressionException {
// there must be $ at location and { after it
int nestingLevel = 1;
int state = INITIAL;
final StringBuilder expression = new StringBuilder();
int i = location + 2;
while (i < inputRef[0].length()) {
final char c = inputRef[0].charAt(i++);
switch (c) {
case '$':
if (state == DOLLAR) {
expression.append(c);
state = INITIAL;
} else {
state = DOLLAR;
}
break;
case '{':
if (state == DOLLAR) {
state = INITIAL;
if(expression.length() > 0 && expression.charAt(expression.length() - 1) == ':') {
expression.append("${");
++nestingLevel;
} else {
i = resolveProperty2(inputRef, i - 2, exceptionIfNotResolved);
}
} else {
expression.append(c);
}
break;
case '}':
if (state == DOLLAR) {
expression.append('$');
}
if(--nestingLevel > 0) {
state = INITIAL;
expression.append(c);
continue;
}
final String propName = expression.toString();
final String resolved = resolveKey(propName);
if (resolved != null) {
final String input = inputRef[0];
final StringBuilder buf = new StringBuilder(input.length() - i + location + resolved.length());
buf.append(input.substring(0, location)).append(resolved);
if (i < input.length()) {
buf.append(input.substring(i));
}
inputRef[0] = buf.toString();
return location;
} else if (exceptionIfNotResolved) {
throw new UnresolvedExpressionException(inputRef[0].substring(location, i), "Unrecognized system property "
+ propName);
}
return i;
default:
if (state == DOLLAR) {
state = INITIAL;
expression.append('$');
}
expression.append(c);
}
}
return i;
}
private static String resolveKey(String key) {
String value = null;
// check for alias
if (FILE_SEPARATOR_ALIAS.equals(key)) {
value = FILE_SEPARATOR;
} else if (PATH_SEPARATOR_ALIAS.equals(key)) {
value = PATH_SEPARATOR;
} else {
// check from the properties
value = WildFlySecurityManager.getPropertyPrivileged(key, null);
if(value == null && key.startsWith(ENV_PREFIX)) {
value = System.getenv(key.substring(4));
}
if (value == null) {
// Check for a default value ${key:default}
int colon = key.indexOf(':');
if (colon > 0) {
String realKey = key.substring(0, colon);
value = WildFlySecurityManager.getPropertyPrivileged(realKey, null);
if(value == null && realKey.startsWith(ENV_PREFIX)) {
value = System.getenv(realKey.substring(4));
}
if (value == null) {
// Check for a composite key, "key1,key2"
value = resolveCompositeKey(realKey);
// Not a composite key either, use the
// specified default
if (value == null)
value = key.substring(colon + 1);
}
} else {
// No default, check for a composite key,
// "key1,key2"
value = resolveCompositeKey(key);
}
}
}
return value;
}
/**
* Try to resolve a "key" from the provided properties by checking if it is actually a "key1,key2", in which case try first
* "key1", then "key2". If all fails, return null.
*
* It also accepts "key1," and ",key2".
*
* @param key the key to resolve
* @param props the properties to use
* @return the resolved key or null
*/
private static String resolveCompositeKey(String key) {
String value = null;
// Look for the comma
int comma = key.indexOf(',');
if (comma > -1) {
// If we have a first part, try resolve it
if (comma > 0) {
// Check the first part
String key1 = key.substring(0, comma);
value = WildFlySecurityManager.getPropertyPrivileged(key1, null);
if (value == null && key1.startsWith(ENV_PREFIX)) {
value = System.getenv(key1.substring(4));
}
}
// Check the second part, if there is one and first lookup failed
if (value == null && comma < key.length() - 1) {
String key2 = key.substring(comma + 1);
value = WildFlySecurityManager.getPropertyPrivileged(key2, null);
if (value == null && key2.startsWith(ENV_PREFIX)) {
value = System.getenv(key2.substring(4));
}
}
}
// Return whatever we've found or null
return value;
}
}