/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2011-2013 ForgeRock AS. All Rights Reserved * * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the License). You may not use this file except in * compliance with the License. * * You can obtain a copy of the License at * http://forgerock.org/license/CDDLv1.0.html * See the License for the specific language governing * permission and limitations under the License. * * When distributing Covered Code, include this CDDL * Header Notice in each file and include the License file * at http://forgerock.org/license/CDDLv1.0.html * If applicable, add the following below the CDDL Header, * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" */ package org.forgerock.openidm.core; import java.util.Stack; public class PropertyUtil { private PropertyUtil() { } public enum Delimiter { DOLLAR { @Override char getStartChar() { return '$'; } @Override String getStartString() { return DELIM_START_DOLLAR; } }, AMPERSAND { @Override char getStartChar() { return '&'; } @Override String getStartString() { return DELIM_START_AMPERSAND; } }; abstract char getStartChar(); abstract String getStartString(); } public static final String DELIM_START_DOLLAR = "${"; public static final String DELIM_START_AMPERSAND = "&{"; public static final char DELIM_STOP = '}'; /** * <p> * This method performs property variable substitution on the specified * value. If the specified value contains the syntax * <tt>&{<prop-name>}</tt>, where <tt><prop-name></tt> refers to * either a configuration property or a system property, then the * corresponding property value is substituted for the variable placeholder. * Multiple variable placeholders may exist in the specified value as well * as nested variable placeholders, which are substituted from inner most to * outer most. Configuration properties override system properties. * </p> * * @param val * The string on which to perform property substitution. * @param propertyAccessor * Set of configuration properties. * @return The value of the specified string after property substitution. **/ public static Object substVars(String val, final PropertyAccessor propertyAccessor, boolean doEscape) { return substVars(val, propertyAccessor, Delimiter.AMPERSAND, doEscape); } public static Object substVars(String val, final PropertyAccessor propertyAccessor, Delimiter delimiter, boolean doEscape) { // Assume we have a value that is something like: // "leading &{foo.&{bar}} middle ${baz} trailing" int stopDelim = -1; int startDelim = -1; if (!doEscape) { stopDelim = val.indexOf(DELIM_STOP, stopDelim + 1); // If there is no stopping delimiter, then just return // the value since there is no variable declared. if (stopDelim < 0) { return val; } startDelim = val.indexOf(delimiter.getStartString()); // If there is no starting delimiter, then just return // the value since there is no variable declared. if (startDelim < 0) { return val; } } StringBuilder parentBuilder = new StringBuilder(val.length()); Stack<StringBuilder> propertyStack = new Stack<StringBuilder>(); propertyStack.push(parentBuilder); for (int index = 0; index < val.length(); index++) { switch (val.charAt(index)) { case '\\': { if (doEscape) { index++; if (index < val.length()) { propertyStack.peek().append(val.charAt(index)); } } else { propertyStack.peek().append(val.charAt(index)); } break; } case '&': { if ('{' == val.charAt(index + 1) && val.charAt(index) == delimiter.getStartChar()) { // This is a start of a new property propertyStack.push(new StringBuilder(val.length())); index++; } else { propertyStack.peek().append(val.charAt(index)); } break; } case '$': { if ('{' == val.charAt(index + 1) && val.charAt(index) == delimiter.getStartChar()) { // This is a start of a new property propertyStack.push(new StringBuilder(val.length())); index++; } else { propertyStack.peek().append(val.charAt(index)); } break; } case DELIM_STOP: { // End of the actual property if (propertyStack.size() == 1) { // There is no start delimiter propertyStack.peek().append(val.charAt(index)); } else { String variable = propertyStack.pop().toString(); if ((index == val.length() - 1) && propertyStack.size() == 1 && parentBuilder.length() == 0) { // Replace entire value with an Object Object substValue = getSubstituteValue(Object.class, variable, propertyAccessor); if (null != substValue) { return substValue; } else { propertyStack.peek().append(delimiter.getStartString()) .append(variable).append(DELIM_STOP); return propertyStack.peek().toString(); } } else { String substValue = getSubstituteValue(String.class, variable, propertyAccessor); if (null != substValue) { propertyStack.peek().append(substValue); } else { propertyStack.peek().append(delimiter.getStartString()) .append(variable).append(DELIM_STOP); } } } break; } default: { propertyStack.peek().append(val.charAt(index)); } } } // Close the open &{ tags. for (int index = propertyStack.size(); index > 1; index--) { StringBuilder top = propertyStack.pop(); propertyStack.peek().append(delimiter.getStartString()).append(top.toString()); } return parentBuilder.toString(); } @SuppressWarnings("unchecked") private static <T> T getSubstituteValue(Class<T> type, String variable, final PropertyAccessor propertyAccessor) { T substValue = null; if (String.class.isAssignableFrom(type)) { // Get the value of the deepest nested variable // placeholder. // Try to configuration properties first. substValue = (propertyAccessor != null) ? (T) propertyAccessor.getProperty(variable, null, String.class) : null; } else { substValue = (propertyAccessor != null) ? propertyAccessor.getProperty(variable, null, type) : null; } return substValue; } }