content: /*
   * Copyright 2013-2023 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 com.alibaba.cloud.nacos.refresh;
  
  import java.lang.reflect.Field;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Map;
  import java.util.Optional;
  import java.util.Set;
  
  import org.springframework.beans.BeansException;
  import org.springframework.boot.context.properties.ConfigurationPropertiesBean;
  import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
  import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans;
  import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder;
  import org.springframework.context.ApplicationContext;
  import org.springframework.core.annotation.AnnotationUtils;
  import org.springframework.util.ReflectionUtils;
  
  /**
   * Extend {@link ConfigurationPropertiesRebinder}.
   * <p>
   * Spring team doesn't seem to support single {@link ConfigurationPropertiesBean} refresh.
   * <p>
   * SmartConfigurationPropertiesRebinder can refresh specific
   * {@link ConfigurationPropertiesBean} base on the change keys.
   * <p>
   * <strong> NOTE: We still use Spring's default behavior (full refresh) as default
   * behavior, This feature can be considered an advanced feature, it may not be as stable
   * as the default behavior. </strong>
   *
   * @author freeman
   * @since 2021.0.1.1
   */
  public class SmartConfigurationPropertiesRebinder
  		extends ConfigurationPropertiesRebinder {
  
  	private Map<String, ConfigurationPropertiesBean> beanMap;
  
  	private ApplicationContext applicationContext;
  
  	private RefreshBehavior refreshBehavior;
  
  	public SmartConfigurationPropertiesRebinder(ConfigurationPropertiesBeans beans) {
  		super(beans);
  		fillBeanMap(beans);
  	}
  
  	@SuppressWarnings("unchecked")
  	private void fillBeanMap(ConfigurationPropertiesBeans beans) {
  		this.beanMap = new HashMap<>();
  		Field field = ReflectionUtils.findField(beans.getClass(), "beans");
  		if (field != null) {
  			field.setAccessible(true);
  			this.beanMap.putAll((Map<String, ConfigurationPropertiesBean>) Optional
  					.ofNullable(ReflectionUtils.getField(field, beans))
  					.orElse(Collections.emptyMap()));
  		}
  	}
  
  	@Override
  	public void setApplicationContext(ApplicationContext applicationContext)
  			throws BeansException {
  		super.setApplicationContext(applicationContext);
  		this.applicationContext = applicationContext;
  		this.refreshBehavior = this.applicationContext.getEnvironment().getProperty(
  				"spring.cloud.nacos.config.refresh-behavior", RefreshBehavior.class,
  				RefreshBehavior.ALL_BEANS);
  	}
  
  	@Override
  	public void onApplicationEvent(EnvironmentChangeEvent event) {
  		if (this.applicationContext.equals(event.getSource())
  				// Backwards compatible
  				|| event.getKeys().equals(event.getSource())) {
  			switch (refreshBehavior) {
  			case SPECIFIC_BEAN -> rebindSpecificBean(event);
  			default -> rebind();
  			}
  		}
  	}
  
  	private void rebindSpecificBean(EnvironmentChangeEvent event) {
  		Set<String> refreshedSet = new HashSet<>();
  		beanMap.forEach((name, bean) -> event.getKeys().forEach(changeKey -> {
  			String prefix = AnnotationUtils.getValue(bean.getAnnotation()).toString();
  			// prevent multiple refresh one ConfigurationPropertiesBean.
  			if (changeKey.startsWith(prefix) && refreshedSet.add(name)) {
  				rebind(name);
  			}
  		}));
  	}
  
  }
  