package org.ff4j.spring.placeholder;
/*
* #%L
* ff4j-aop
* %%
* Copyright (C) 2013 - 2015 Ff4J
* %%
* Licensed 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.
* #L%
*/
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.ff4j.FF4j;
import org.ff4j.core.Feature;
import org.ff4j.exception.FeatureNotFoundException;
import org.ff4j.exception.PropertyNotFoundException;
import org.ff4j.property.Property;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.BeanDefinitionVisitor;
/**
* Pattern Bean Visitor.
*
* @author <a href="mailto:cedrick.lunven@gmail.com">Cedrick LUNVEN</a>
*/
public class PropertiesPlaceHolderBeanDefinitionVisitor extends BeanDefinitionVisitor {
/** Static commons-log LOG for this class. * */
private static final Logger LOGGER = LoggerFactory.getLogger(PropertiesPlaceHolderBeanDefinitionVisitor.class);
/** Properties Map from store **/
private Map<String, Property<?>> propertiesMap = new HashMap<String, Property<?>>();
/** Properties Map from store **/
private Map<String, Feature > featuresMap = new HashMap<String, Feature >();
/** Prefix to every registry stored adress. * */
public static final String PLACEHOLDER_PROPERTY_PREFIX = "@ff4jProperty{";
/** Prefix to every registry stored adress. * */
public static final String PLACEHOLDER_FEATURE_PREFIX = "@ff4jFeature{";
/** Prefix to every registry stored adress. * */
public static final String PLACEHOLDER_SUFFIX = "}";
/**
* Instanciate visitor.
* @param serviceMap
* parameter service map
*/
public PropertiesPlaceHolderBeanDefinitionVisitor(FF4j ff4j) {
if (ff4j == null) {
throw new IllegalArgumentException("Cannot initialize placeholding 'ff4j' is null");
}
this.propertiesMap = ff4j.getProperties();
this.featuresMap = ff4j.getFeatures();
}
/** {@inheritDoc} */
protected String resolveStringValue(String strVal) throws BeansException {
return parseStringValue(strVal, propertiesMap, featuresMap, new HashSet<String>());
}
/**
* Parsing value to handle
* @param strVal
* @param uriMap
* @param visitedPlaceholders
* @return
* @throws BeanDefinitionStoreException
*/
protected String parseStringValue(String strVal, Map<String, Property<?>> propertiesMap, Map<String, Feature> featureMap, Set<String> visitedPlaceholders)
throws BeanDefinitionStoreException {
StringBuilder builder = new StringBuilder(strVal);
// @ff4jProperty{}
int startIndex = strVal.indexOf(PLACEHOLDER_PROPERTY_PREFIX);
while (startIndex != -1) {
int endIndex = builder.toString().indexOf(PLACEHOLDER_SUFFIX, startIndex + PLACEHOLDER_PROPERTY_PREFIX.length());
if (endIndex != -1) {
String placeholder = builder.substring(startIndex + PLACEHOLDER_PROPERTY_PREFIX.length(), endIndex);
if (!visitedPlaceholders.add(placeholder)) {
throw new BeanDefinitionStoreException("Circular placeholder reference '" + placeholder + "' in property definitions");
}
if (propertiesMap==null || !propertiesMap.containsKey(placeholder)) {
throw new PropertyNotFoundException(
PLACEHOLDER_PROPERTY_PREFIX + ": Cannot perform placeholding on " + placeholder);
}
String propVal = propertiesMap.get(placeholder).asString();
if (propVal != null) {
propVal = parseStringValue(propVal, propertiesMap, featureMap, visitedPlaceholders);
builder.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), propVal);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Resolved placeholder '{}' to value '{}'", placeholder, propVal);
}
startIndex = builder.toString().indexOf(PLACEHOLDER_PROPERTY_PREFIX, startIndex + propVal.length());
} else {
throw new BeanDefinitionStoreException("Could not resolve placeholder '" + placeholder + "'");
}
visitedPlaceholders.remove(placeholder);
} else {
startIndex = -1;
}
}
// @ff4jFeature{}
startIndex = strVal.indexOf(PLACEHOLDER_FEATURE_PREFIX);
while (startIndex != -1) {
int endIndex = builder.toString().indexOf(PLACEHOLDER_SUFFIX, startIndex + PLACEHOLDER_FEATURE_PREFIX.length());
if (endIndex != -1) {
String placeholder = builder.substring(startIndex + PLACEHOLDER_FEATURE_PREFIX.length(), endIndex);
if (!visitedPlaceholders.add(placeholder)) {
throw new BeanDefinitionStoreException("Circular placeholder reference '" + placeholder + "' in property definitions");
}
if (featureMap==null || !featureMap.containsKey(placeholder)) {
throw new FeatureNotFoundException(
PLACEHOLDER_FEATURE_PREFIX + ": Cannot perform placeholding on " + placeholder);
}
String propVal = String.valueOf(featureMap.get(placeholder).isEnable());
if (propVal != null) {
propVal = parseStringValue(propVal, propertiesMap, featureMap, visitedPlaceholders);
builder.replace(startIndex, endIndex + PLACEHOLDER_FEATURE_PREFIX.length(), propVal);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Resolved placeholder '{}' to value '{}'", placeholder, propVal);
}
startIndex = builder.toString().indexOf(PLACEHOLDER_FEATURE_PREFIX, startIndex + propVal.length());
} else {
throw new BeanDefinitionStoreException("Could not resolve placeholder '" + placeholder + "'");
}
visitedPlaceholders.remove(placeholder);
} else {
startIndex = -1;
}
}
return builder.toString();
}
}