AbstractExtendedBindingProperties.java
/*
* Copyright 2018-present the original author or authors.
*
* 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
*
* https://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.springframework.cloud.stream.binder;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver;
import org.springframework.boot.context.properties.bind.validation.ValidationBindHandler;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.validation.MessageInterpolatorFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.util.ClassUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
/**
* Base implementation of {@link ExtendedBindingProperties}.
*
* @param <C> - consumer properties type
* @param <P> - producer properties type
* @param <T> - type which provides the consumer and producer properties
* @author Oleg Zhurakousky
* @since 2.1
*/
@Validated
public abstract class AbstractExtendedBindingProperties<C, P, T extends BinderSpecificPropertiesProvider>
implements ExtendedBindingProperties<C, P>, ApplicationContextAware {
static Log logger = LogFactory.getLog(AbstractExtendedBindingProperties.class);
private final Map<String, T> bindings = new HashMap<>();
private ConfigurableApplicationContext applicationContext = new GenericApplicationContext();
private volatile Binder propertiesBinder;
public void setBindings(Map<String, T> bindings) {
this.bindings.putAll(bindings);
}
@SuppressWarnings("unchecked")
@Override
public C getExtendedConsumerProperties(String binding) {
this.bindIfNecessary(binding);
return (C) this.bindings.get(binding).getConsumer();
}
@SuppressWarnings("unchecked")
@Override
public P getExtendedProducerProperties(String binding) {
this.bindIfNecessary(binding);
return (P) this.bindings.get(binding).getProducer();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
GenericConversionService cs = (GenericConversionService) this.applicationContext.getBeanFactory().getConversionService();
Iterable<ConfigurationPropertySource> sources = ConfigurationPropertySources.get(this.applicationContext.getEnvironment());
PropertySourcesPlaceholdersResolver placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.applicationContext.getEnvironment());
this.propertiesBinder = new Binder(sources, placeholdersResolver, cs, null, null);
}
/*
* The "necessary" implies the scenario where only defaults are defined.
*/
private void bindIfNecessary(String bindingName) {
if (!this.bindings.containsKey(bindingName)) {
this.bindToDefault(bindingName);
}
}
@SuppressWarnings("unchecked")
private void bindToDefault(String binding) {
T extendedBindingPropertiesTarget = (T) BeanUtils
.instantiateClass(this.getExtendedPropertiesEntryClass());
if (Jsr303Validator.isJsr303Present(this.applicationContext)) {
Jsr303Validator validator = new Jsr303Validator(this.applicationContext);
this.propertiesBinder.bind(this.getDefaultsPrefix(),
Bindable.ofInstance(extendedBindingPropertiesTarget), new ValidationBindHandler(validator));
}
else {
this.propertiesBinder.bind(this.getDefaultsPrefix(),
Bindable.ofInstance(extendedBindingPropertiesTarget));
}
this.bindings.put(binding, extendedBindingPropertiesTarget);
}
protected Map<String, T> doGetBindings() {
return Collections.unmodifiableMap(this.bindings);
}
private class Jsr303Validator implements Validator {
private static final String[] VALIDATOR_CLASSES = { "jakarta.validation.Validator",
"jakarta.validation.ValidatorFactory", "jakarta.validation.bootstrap.GenericBootstrap" };
private final Delegate delegate;
Jsr303Validator(ApplicationContext applicationContext) {
this.delegate = new Delegate(applicationContext);
}
@Override
public boolean supports(Class<?> type) {
return this.delegate.supports(type);
}
@Override
public void validate(Object target, Errors errors) {
this.delegate.validate(target, errors);
}
static boolean isJsr303Present(ApplicationContext applicationContext) {
ClassLoader classLoader = applicationContext.getClassLoader();
for (String validatorClass : VALIDATOR_CLASSES) {
if (!ClassUtils.isPresent(validatorClass, classLoader)) {
return false;
}
}
return true;
}
private static class Delegate extends LocalValidatorFactoryBean {
Delegate(ApplicationContext applicationContext) {
setApplicationContext(applicationContext);
setMessageInterpolator(new MessageInterpolatorFactory(applicationContext).getObject());
try {
afterPropertiesSet();
}
catch (Exception e) {
logger.warn("Failed to execute afterPropertiesSet() on aplication context", e);
}
}
}
}
}