/* * 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.geode.internal.cache.xmlcache; import java.util.HashMap; import java.util.Set; import org.apache.logging.log4j.Logger; import org.apache.geode.internal.i18n.LocalizedStrings; import org.apache.geode.internal.logging.LogService; import org.apache.geode.internal.logging.log4j.LocalizedMessage; /** * Helper class for CacheXmlPropertyResolver. Helps in parsing ${...${}..}..${} strings. * * @since GemFire 6.6 */ public class CacheXmlPropertyResolverHelper { private static final Logger logger = LogService.getLogger(); public static final String DEFAULT_PROPERTY_STRING_PREFIX = "${"; public static final String DEFAULT_PROPERTY_STRING_SUFFIX = "}"; public static final String DEFAULT_PREFIX_FOR_SUFFIX = "{"; /** * This <code>HashMap </code> contains valid suffixes and prefixes to be parsed by * {@link CacheXmlPropertyResolverHelper} like {}, [] or (). */ private static HashMap<String, String> validSuffixAndPrefixes = new HashMap<String, String>(); static { validSuffixAndPrefixes.put("}", "{"); validSuffixAndPrefixes.put("]", "["); validSuffixAndPrefixes.put(")", "("); } /* String specifying the suffice for property key prefix */ private String propertyPrefix = DEFAULT_PROPERTY_STRING_PREFIX; /* String specifying the suffice for property key suffix */ private String propertySuffix = DEFAULT_PROPERTY_STRING_SUFFIX; private String prefixForSuffix = DEFAULT_PREFIX_FOR_SUFFIX; /** * @param propPrefix * @param propSuffix */ public CacheXmlPropertyResolverHelper(String propPrefix, String propSuffix) { if (propPrefix != null && propSuffix != null) { String validPrefix = validSuffixAndPrefixes.get(propSuffix); if (validPrefix != null && propPrefix.endsWith(validPrefix)) { this.prefixForSuffix = validPrefix; } else { this.prefixForSuffix = propPrefix; } this.propertyPrefix = propPrefix; this.propertySuffix = propSuffix; } } /** * Parses the given string which are supposed to be like ${} for system and/or Gemfire properties * to be replaced. This will return property.name from ${property.name}. * * @param unparsedString * @return parsedString */ protected String parseResolvablePropString(String unparsedString, PropertyResolver resolver, Set<String> visitedReplaceableStrings) { StringBuilder buf = new StringBuilder(unparsedString); int prefixIndex = buf.indexOf(propertyPrefix); while (prefixIndex != -1) { int suffixIndex = findSuffixIndex(buf, prefixIndex + propertyPrefix.length()); if (suffixIndex != -1) { String replaceableString = buf.substring(prefixIndex + propertyPrefix.length(), suffixIndex); // Check for circular references if (!visitedReplaceableStrings.add(replaceableString)) { logger.info(LocalizedMessage.create( LocalizedStrings.CacheXmlPropertyResolverHelper_SOME_UNRESOLVED_STRING_REPLACED_CIRCULAR_ERROR__0, replaceableString)); throw new IllegalArgumentException("Some still unresolved string " + replaceableString + " was replaced by resolver, leading to circular references."); } /** Find the replacement using given <code>resolver</code> */ replaceableString = parseResolvablePropString(replaceableString, resolver, visitedReplaceableStrings); String replacement = resolver.resolveReplaceString(replaceableString); if (replacement != null) { /** * put replacement in <code>unparsedString</code> and call * <code>parseResolvablePropString</code> recursively to find more unparsedStrings in the * replaced value of given unparsedString. */ replacement = parseResolvablePropString(replacement, resolver, visitedReplaceableStrings); buf.replace(prefixIndex, suffixIndex + propertySuffix.length(), replacement); prefixIndex = buf.indexOf(propertyPrefix, prefixIndex + replacement.length()); } else if (resolver.isIgnoreUnresolvedProperties()) { /** Look for more replaceable strings in given <code>unparsedString</code>. */ prefixIndex = buf.indexOf(propertyPrefix, suffixIndex + propertySuffix.length()); } else { throw new IllegalArgumentException( "No replacement found for property : " + replaceableString); } // Before iterating again remove replaceable string from visitedReplaceableStrings as it can // appear again. visitedReplaceableStrings.remove(replaceableString); } else { prefixIndex = -1; } } return buf.toString(); } /** * Finds index of suffix in a string from a specified index. Like finds index of "}" in string * "${my.prop.name}" starting from index 2, which is 14. * * @param buf * @param index * @return suffix */ private int findSuffixIndex(StringBuilder buf, int index) { int inNestedProperty = 0; while (index < buf.length()) { if (buf.substring(index, index + this.propertySuffix.length()) .equalsIgnoreCase(this.propertySuffix)) { if (inNestedProperty > 0) { inNestedProperty--; index = index + this.propertySuffix.length(); } else { return index; } } else if (buf.substring(index, index + this.prefixForSuffix.length()) .equalsIgnoreCase(this.prefixForSuffix)) { inNestedProperty++; index = index + this.prefixForSuffix.length(); } else { index++; } } return -1; } }