All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tangosol.config.expression.ValueMacroExpression Maven / Gradle / Ivy

There is a newer version: 24.09
Show newest version
/*
 * Copyright (c) 2000, 2021, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * http://oss.oracle.com/licenses/upl.
 */
package com.tangosol.config.expression;

import com.oracle.coherence.common.base.Logger;

import com.tangosol.io.ExternalizableLite;
import com.tangosol.io.pof.PofReader;
import com.tangosol.io.pof.PofWriter;
import com.tangosol.io.pof.PortableObject;

import com.tangosol.util.ExternalizableHelper;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

/**
 * A {@link ValueMacroExpression} is a string value potentially containing expandable macros.
 * 

* Resolving the expression performs macro expansion. The macro syntax is ${macro-parameter default-value}. * Thus, a value of near-${coherence.client direct} is macro expanded by default to near-direct. * If property coherence.client is set to remote, then the value would be expanded to near-remote. *

* As of Coherence 14.1.2.0.0, the following common shell parameter expansion capabilities have been added. *

 * ${macro-parameter ${macro-parameter-default default-value}}
 * Supports nesting of macro parameters to enable the defaulting value to be configured as a macro-parameter.
 * 
* In addition to the default delimiter of a space character, the colon character indicates a modifier for macro expansion.
* Note that word referenced below is either a nested macro parameter default or the default value. *

*

 * ${macro-parameter:-word}
 * If macro-parameter is unset or null, the expansion of wordis substituted,
 * otherwise, the value of macro-parameter is substituted.
 *
 * ${macro-parameter:+word}
 * If macro-parameter is null or unset, nothing is substituted,
 * otherwise the expansion of word is substituted.
 *
 * ${macro-parameter:offset}
 * ${macro-parameter:offset:length}
 * Note that length and offset are integer values.
 * 
* See {@link OffsetLengthSubstringExpansionProcessor#process(String, ParameterResolver, int)} for substring expansion details * and example usages. * * @author jf 2015.05.18 * @since Coherence 12.2.1 */ public class ValueMacroExpression implements Expression, ExternalizableLite, PortableObject { // ----- constructors --------------------------------------------------- /** * Default constructor needed for serialization. */ public ValueMacroExpression() { } /** * Construct a {@link ValueMacroExpression}. * * @param value the value that potentially contains a macro expression. */ public ValueMacroExpression(String value) { m_sValue = value; } // ----- Expression interface ------------------------------------------- /** * {@inheritDoc} */ @Override public String evaluate(ParameterResolver resolver) { String sValue = m_sValue; try { return substitute(sValue, resolver, 1).toString(); } catch (IllegalStateException e) { Logger.config("macro parameter value expansion failure expanding " + sValue + " cause: " + e.getMessage()); return sValue; } catch (StringIndexOutOfBoundsException e) { Logger.config("macro parameter value expansion failure expanding " + sValue + " cause:" + e.getMessage()); return sValue; } } // ----- ValueMacroExpression methods ------------------------------------ /** * Return a string value containing no outstanding macro parameters. * * @param sValue a value containing 0 or more macro parameters * @param resolver a {@link ParameterResolver macro parameter resolver} * @param cDepth current number of macro parameter resolutions * * @return a string value containing no outstanding macro parameters */ protected String substitute(String sValue, ParameterResolver resolver, int cDepth) { boolean fLogged = false; if (sValue != null) { // replace in-lined properties i.e. ${prop-name default-value} using resolver for (int ofStart = sValue.indexOf(PARAMETER_PREFIX); ofStart >= 0; ofStart = sValue.indexOf(PARAMETER_PREFIX)) { int ofEnd = sValue.indexOf(PARAMETER_SUFFIX, ofStart); if (ofEnd == -1) { // missing closing PARAMETER_SUFFIX so no macro to process here break; } // check for nested property int ofNext = sValue.indexOf(PARAMETER_PREFIX, ofStart + 1); if (ofNext != -1 && ofNext < ofEnd) { // nested property // find balanced PARAMETER_SUFFIX from ofNext position. int ofNextEnd = indexOfMatchingPropertyClose(sValue, ofNext); if (ofNextEnd == -1) { // missing PARAMETER_CLOSE so no macro to process here break; } String sNestedMacro = substitute(sValue.substring(ofNext, ofNextEnd + 1), resolver, cDepth + 1); sValue = replaceMacroParameter(sValue, ofNext, ofNextEnd + 1, sNestedMacro); // find next outer ofEnd ofEnd = indexOfMatchingPropertyClose(sValue, ofStart); } int ofStartMacro = ofStart; int ofEndMacro = ofEnd + 1; String sMacro = sValue.substring(ofStartMacro, ofEndMacro); String sPropValue = processRegisteredMacroExpansions(sMacro, resolver, cDepth); sValue = replaceMacroParameter(sValue, ofStartMacro, ofEndMacro, sPropValue); cDepth++; } } return sValue; } // ----- helpers ----------------------------------------------------------- /** * Process macro expansion of sMacro by {@link #s_mapRegistry registered * macro-expansion processors}. * * @param sMacro macro parameter * @param resolver resolve macro parameter within sMacro * @param cDepth count of macro parameter expansions * * @return result of macro parameter expansion processing */ protected String processRegisteredMacroExpansions(String sMacro, ParameterResolver resolver, int cDepth) { String sPropValue = sMacro; for (Entry entry : s_mapRegistry.entrySet()) { MacroExpansionProcessor processor = entry.getValue(); if (processor.canProcess(sMacro)) { return processor.process(sPropValue, resolver, cDepth); } } if (NO_DELIMITER_MACRO_EXPANSION_PROCESSOR.canProcess(sMacro)) { return NO_DELIMITER_MACRO_EXPANSION_PROCESSOR.process(sMacro, resolver, cDepth); } return sPropValue; } /** * Check if this contains a macro. * * @return true iff this contains a macro */ public boolean containsMacro() { return containsMacro(m_sValue); } // ----- static helpers ---------------------------------------------------- /** * Return sValue with macro parameter offset range expanded to * sReplacement value. * * @param sValue string containing a macro from ofStart to ofEnd * @param ofStart start offset of macro parameter within sValue * @param ofEnd end offset of macro parameter within sValue * @param sReplacement replacement string value for macro parameter from * ofStart to ofEnd * * @return sValue with its sReplacement> value */ static String replaceMacroParameter(String sValue, int ofStart, int ofEnd, String sReplacement) { return sValue.substring(0, ofStart) + sReplacement + (ofEnd + 1 > sValue.length() ? "" : sValue.substring(ofEnd)); } /** * Return the offset within string sValue for property close * for property starting at ofPropertyStart. * * @param sValue string to process * @param ofPropertyStart offset within string of {@link #PARAMETER_PREFIX} * * @return offset within sValue of matching property close or * -1 if no matching close */ static int indexOfMatchingPropertyClose(String sValue, int ofPropertyStart) { int ofCur = ofPropertyStart + 1; int nNested = 0; while (ofCur < sValue.length()) { if (sValue.regionMatches(ofCur, PARAMETER_PREFIX, 0, PARAMETER_PREFIX.length())) { nNested++; ofCur += PARAMETER_PREFIX.length(); } else if (sValue.charAt(ofCur) == PARAMETER_SUFFIX) { if (nNested == 0) { return ofCur; } else { nNested--; } ofCur++; } else { ofCur++; } } // property closing bracket not found, return -1 return -1; } /** * Check if string contains a macro. * * @param sValue string potentially containing a macro * * @return true iff the string value contains a macro */ public static boolean containsMacro(String sValue) { if (sValue == null) { return false; } int ofStart = sValue.indexOf(PARAMETER_PREFIX); return ofStart >= 0 && sValue.indexOf(PARAMETER_SUFFIX, ofStart) > 0; } /** * Return true if sMacro contains a {@link #s_mapRegistry registered delimiter}. * * @param sMacro macro parameter * * @return true if sMacro string contains a registered delimiter */ protected static boolean containsRegisteredDelimiter(String sMacro) { for (String sDelimter : s_mapRegistry.keySet()) { if (sMacro.contains(sDelimter)) { return true; } } return false; } /** * Register {@link MacroExpansionProcessor processor} by sDelimiter. * When a macro parameter contains this delimiter, the registered processor will perform macro expansion. * * @param sDelimiter macro parameter delimiter * @param processor macro parameter processor for sDelimiter */ static void register(String sDelimiter, MacroExpansionProcessor processor) { s_mapRegistry.put(sDelimiter, processor); } // ----- ExternalizableLite interface ----------------------------------- /** * {@inheritDoc} */ @Override public void readExternal(DataInput in) throws IOException { m_sValue = ExternalizableHelper.readObject(in); } /** * {@inheritDoc} */ @Override public void writeExternal(DataOutput out) throws IOException { ExternalizableHelper.writeObject(out, m_sValue); } // ----- PortableObject interface --------------------------------------- /** * {@inheritDoc} */ @Override public void readExternal(PofReader reader) throws IOException { m_sValue = reader.readObject(0); } /** * {@inheritDoc} */ @Override public void writeExternal(PofWriter writer) throws IOException { writer.writeObject(0, m_sValue); } // ----- Object methods ------------------------------------------------- /** * {@inheritDoc} */ @Override public String toString() { return String.format("ValueMacroExpression[value=%s]", m_sValue); } // ----- inner class: MacroExpansionProcessor --------------------- /** * A Macro Expansion Processor for a macro not containing any registered delimiters. */ protected static class MacroExpansionProcessor { // ----- MacroExpansionProcessor methods ---------------------- /** * Process macro parameter expansion on sMacro containing no registered delimiters. * * @param sMacro a string starting with {@link #PARAMETER_PREFIX prefix} * and ending with {@link #PARAMETER_SUFFIX suffix} * @param resolver macro parameter ParameterResolver * @param cDepth expansion depth * * @return expanded macro parameter or emptry string if macro parameter evaluates to null */ public String process(String sMacro, ParameterResolver resolver, int cDepth) { String sDefault = ""; String sProp = sMacro.substring(PARAMETER_PREFIX.length(), sMacro.length() - 1); try { Parameter param = resolver.resolve(sProp); String sPropValue = param == null ? sDefault : param.evaluate(resolver).as(String.class); return validateMacroExpansion(sProp, sPropValue, sDefault, cDepth) ? sPropValue : sDefault; } catch (Exception e) { return sDefault; } } /** * Return true iff sMacro contains the delimiter that this processor handles. * * @param sMacro the macro parameter * * @return true iff this processor can process sMacro */ public boolean canProcess(String sMacro) { return sMacro.startsWith(PARAMETER_PREFIX); } /** * Return delimiter being used by the processor. * * @return delimiter used by the processor or empty string if processor does not have a delimiter */ public String getDelimiter() { return ""; } /** * Validate macro expansion is not self referencing or contain circular references that will never complete expansion. * * @param sProp the property * @param sPropValue the expanded property value * @param cDepth count of macro expansions * * @return false if self referencing in macro expansion or exceed {#link #MAX_MACRO_EXPANSIONS}; otherwise return true */ public boolean validateMacroExpansion(String sProp, String sPropValue, String sDefault, int cDepth) { if (sPropValue.contains(PARAMETER_PREFIX + sProp) && sPropValue.contains(PARAMETER_SUFFIX.toString()) || cDepth > MAX_MACRO_EXPANSIONS) { Logger.err("SystemPropertyPreprocessor: using default value of \"" + sDefault + "\", detected " + "recursive macro definition in property " + sProp + " with the value of \"" + sPropValue + "\" "); return false; } return true; } } // ----- inner class: DefaultDelimiterExpansion ---------------------------- /** * Process macro parameter default delimiter expansion. */ protected static class DefaultDelimiterExpansionProcessor extends MacroExpansionProcessor { // ----- constructors -------------------------------------------------- /** * Perform default delimiter expansion using sDefaultDelimiter. * * @param sDefaultDelimiter default delimiter */ public DefaultDelimiterExpansionProcessor(String sDefaultDelimiter) { f_sDefaultDelimiter = sDefaultDelimiter; f_fUseDefaultIfPropertySet = f_sDefaultDelimiter.equals(":+"); } // ----- MacroExpansionProcessor methods ---------------------- @Override public String getDelimiter() { return f_sDefaultDelimiter; } @Override public boolean canProcess(String sMacro) { return super.canProcess(sMacro) && sMacro.contains(f_sDefaultDelimiter); } @Override public String process(String sMacro, ParameterResolver resolver, int cDepth) { int ofStart = sMacro.indexOf(f_sDefaultDelimiter); if (ofStart > 0) { String sDefault = sMacro.substring(ofStart + f_sDefaultDelimiter.length(), sMacro.length() - 1).trim(); String sProp = sMacro.substring(PARAMETER_PREFIX.length(), ofStart); try { Parameter param = resolver.resolve(sProp); String sPropValue = param == null ? null : param.evaluate(resolver).as(String.class); sPropValue = (sPropValue == null && !f_fUseDefaultIfPropertySet) || (sPropValue != null && f_fUseDefaultIfPropertySet) ? sDefault : sPropValue; return validateMacroExpansion(sProp, sPropValue, sDefault, cDepth) ? sPropValue : sDefault; } catch (Exception e) { return sDefault; } } // no default delimiter to process, so return macro return sMacro; } // ----- data members -------------------------------------------------- /** * Default delimiter to process in macro parameter. */ private final String f_sDefaultDelimiter; /** * Use default if property is set and does not resolve to a null value. */ private final boolean f_fUseDefaultIfPropertySet; } // ----- inner class: SpaceDefaultDelimiterExpansionProcessor -------------- /** * {@link SpaceDefaultDelimiterExpansionProcessor} performs DefaultDelimiter expansion processing * and disambiguates : - for offset and length from space for default delimiter. */ protected static class SpaceDefaultDelimiterExpansionProcessor extends DefaultDelimiterExpansionProcessor { // ----- constructors -------------------------------------------------- /** * Construct a {@link SpaceDefaultDelimiterExpansionProcessor. */ public SpaceDefaultDelimiterExpansionProcessor() { super(" "); } // ----- MacroExpansionProcessor methods ---------------------- @Override public boolean canProcess(String sMacro) { final String NEGATIVE_OFFSET_OR_LENGTH = ": -"; return super.canProcess(sMacro) && !sMacro.contains(NEGATIVE_OFFSET_OR_LENGTH) && sMacro.contains(getDelimiter()); } @Override public String process(String sMacro, ParameterResolver resolver, int cDepth) { return super.process(sMacro, resolver, cDepth); } } // ----- inner class: OffsetLengthSubstringExpansion ----------------------- /** * Process :offset and :length substring expansion. * * @see #process(String, ParameterResolver, int) */ protected static class OffsetLengthSubstringExpansionProcessor extends MacroExpansionProcessor { // ----- MacroExpansionProcessor methods ---------------------- @Override public String getDelimiter() { return SUBSTRING_OFFSET_LENGTH_EXPANSION; } @Override public boolean canProcess(String sMacro) { return super.canProcess(sMacro) && sMacro.contains(getDelimiter()) && !sMacro.contains(":-"); } /** * Perform substring expansion on sMacro. * *
         * ${macro-parameter:offset}
         * ${macro-parameter:offset:length}
         * Note that length and offset are integer values.
         * 
* Substring expansion expands to up to length characters of the value of macro-parameter * starting at the character specified by offset. *
* If length is omitted, it expands to the substring of the value of macro-parameter * starting at the character specified by offset * and extending to the end of the value. *
* If offset evaluates to a number less than zero, the value is used as an * offset in characters from the end of the value of macro-parameter. *
* If length evaluates to a number less than zero, it is interpreted as an * offset in characters from the end of the value of macro-parameter. * rather than a number of characters, and the expansion is the characters between offset and that result. *

* Note that a negative offset or length must be separated from the * colon by at least one space to avoid being confused with the macro parameter default delimiter :-. *

* Examples illustrating substring expansion of a parameter: *

         * Given property parameter of string value 01234567890abcdefgh.
         *
         * ${parameter:7} evaluates to 7890abcdefgh
         * ${parameter:7:0} evaluates to empty string
         * ${parameter:7:2} evaluates to 78
         * ${parameter:7: -2} evaluates to 7890abcdef
         * ${parameter: -7} evaluates to bcdefgh
         * ${parameter: -7:0} evaluates to empty string
         * ${parameter: -7:2} evaluates to bc
         * ${parameter: -7: -2} evaluates to bcdef
         * 
* * @param sMacro a string starting with {@link #PARAMETER_PREFIX prefix} * and ending with {@link #PARAMETER_SUFFIX suffix} * @param resolver macro parameter resolver * @param cDepth count of current macro parameter expansions * * @return the substring expanded value or the original parameter macro if it does not contain substring expansion */ @Override public String process(String sMacro, ParameterResolver resolver, int cDepth) { int sLen = sMacro.length(); int ofSuffix = sMacro.indexOf(getDelimiter()); int ofLengthSuffix; int ofMacroClose = sMacro.indexOf(PARAMETER_SUFFIX); int ofParsed = Integer.MAX_VALUE; int cLength = Integer.MAX_VALUE; int ofEnd = 0; if (ofSuffix > 0) { if (ofSuffix + 1 < sLen) { char c = sMacro.charAt(ofSuffix + 1); if (c == '+' || c == '-') { // found defaulting suffix :+ or :- so return unprocessed. return sMacro; } ofLengthSuffix = sMacro.indexOf(':', ofSuffix + 1); try { ofEnd = ofLengthSuffix > 0 ? ofLengthSuffix : ofMacroClose; ofParsed = Integer.parseInt(sMacro.substring(ofSuffix + 1, ofEnd).trim()); } catch(NumberFormatException e) { throw new IllegalStateException("parsing error processing integer offset " + sMacro.substring(ofSuffix + 1, ofEnd) + " within macro " + sMacro, e); } if (ofLengthSuffix != -1) { try { cLength = Integer.parseInt(sMacro.substring(ofLengthSuffix + 1, ofMacroClose).trim()); } catch (NumberFormatException e) { throw new IllegalStateException("parsing error processing integer length " + sMacro.substring(ofLengthSuffix + 1, ofMacroClose) + " within macro " + sMacro, e); } } String sProp = sMacro.substring(PARAMETER_PREFIX.length(), ofSuffix); Parameter param = resolver.resolve(sProp); String sPropValue = param == null ? null : param.evaluate(resolver).as(String.class); cLength = cLength == Integer.MAX_VALUE ? sPropValue.length() : cLength; ofParsed += ofParsed < 0 ? sPropValue.length() : 0; cLength += cLength < 0 ? sPropValue.length() : ofParsed; int ofComputedEnd = cLength > sPropValue.length() ? sPropValue.length() : cLength; return ofParsed < 0 ? "" : sPropValue.substring(ofParsed, ofComputedEnd); } } return sMacro; } } // ----- constants ------------------------------------------------------ /** * Avoid recursive macro expansions that never return. No need for more than 20 macro expansions on * one value. */ public static int MAX_MACRO_EXPANSIONS = 20; /** * Prefix indicating the start of a property macro. */ public final static String PARAMETER_PREFIX = "${"; /** * Suffix indicating the close of a property macro. */ public final static Character PARAMETER_SUFFIX = '}'; /** * Delimiter introducing substring expansion of optional :offset and/or :length in a macro parameter. */ public final static String SUBSTRING_OFFSET_LENGTH_EXPANSION = ":"; // ----- data members ------------------------------------------------------ /** * Registry of macro parameter delimiters to processors. */ private static Map s_mapRegistry = new HashMap(); /** * No registered delimiter macro expansion processor. */ private static final MacroExpansionProcessor NO_DELIMITER_MACRO_EXPANSION_PROCESSOR = new MacroExpansionProcessor(); /** * The String value. */ private String m_sValue; // ----- static initialization --------------------------------------------- static { register(":", new OffsetLengthSubstringExpansionProcessor()); register(":-", new DefaultDelimiterExpansionProcessor(":-")); register(":+", new DefaultDelimiterExpansionProcessor(":+")); register(" ", new SpaceDefaultDelimiterExpansionProcessor()); } }