/*
 * Copyright 2002-2009 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
 *
 * 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.springframework.cloud.context.scope.refresh;

import java.io.Serializable;
import java.lang.reflect.Field;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
import org.springframework.cloud.context.scope.GenericScope;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.util.ReflectionUtils;

/**
 * <p>
 * A Scope implementation that allows for beans to be refreshed dynamically at runtime
 * (see {@link #refresh(String)} and {@link #refreshAll()}). If a bean is refreshed then
 * the next time the bean is accessed (i.e. a method is executed) a new instance is
 * created. All lifecycle methods are applied to the bean instances, so any destruction
 * callbacks that were registered in the bean factory are called when it is refreshed, and
 * then the initialization callbacks are invoked as normal when the new instance is
 * created. A new bean instance is created from the original bean definition, so any
 * externalized content (property placeholders or expressions in string literals) is
 * re-evaluated when it is created.
 * </p>
 *
 * <p>
 * Note that all beans in this scope are <em>only</em> initialized when first accessed, so
 * the scope forces lazy initialization semantics. The implementation involves creating a
 * proxy for every bean in the scope, so there is a flag
 * {@link #setProxyTargetClass(boolean) proxyTargetClass} which controls the proxy
 * creation, defaulting to JDK dynamic proxies and therefore only exposing the interfaces
 * implemented by a bean. If callers need access to other methods then the flag needs to
 * be set (and CGLib present on the classpath). Because this scope automatically proxies
 * all its beans, there is no need to add <code>&lt;aop:auto-proxy/&gt;</code> to any bean
 * definitions.
 * </p>
 *
 * <p>
 * The scoped proxy approach adopted here has a side benefit that bean instances are
 * automatically {@link Serializable}, and can be sent across the wire as long as the
 * receiver has an identical application context on the other side. To ensure that the two
 * contexts agree that they are identical they have to have the same serialization id. One
 * will be generated automatically by default from the bean names, so two contexts with
 * the same bean names are by default able to exchange beans by name. If you need to
 * override the default id then provide an explicit {@link #setId(String) id} when the
 * Scope is declared.
 * </p>
 *
 * @author Dave Syer
 *
 * @since 3.1
 *
 */
@ManagedResource
public class RefreshScope extends GenericScope
		implements ApplicationContextAware, Ordered {

	private ApplicationContext context;
	private BeanDefinitionRegistry registry;
	private boolean eager = true;
	private int order = Ordered.LOWEST_PRECEDENCE - 100;

	/**
	 * Create a scope instance and give it the default name: "refresh".
	 */
	public RefreshScope() {
		super.setName("refresh");
	}

	@Override
	public int getOrder() {
		return this.order;
	}

	public void setOrder(int order) {
		this.order = order;
	}

	/**
	 * Flag to determine whether all beans in refresh scope should be instantiated eagerly
	 * on startup. Default true.
	 *
	 * @param eager the flag to set
	 */
	public void setEager(boolean eager) {
		this.eager = eager;
	}

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
			throws BeansException {
		this.registry = registry;
		super.postProcessBeanDefinitionRegistry(registry);
	}

	@EventListener
	public void start(ContextRefreshedEvent event) {
		if (event.getApplicationContext() == this.context) {
			if (this.eager && this.registry != null) {
				for (String name : this.context.getBeanDefinitionNames()) {
					BeanDefinition definition = this.registry.getBeanDefinition(name);
					if (this.getName().equals(definition.getScope())
							&& !definition.isLazyInit()) {
						this.context.getBean(name).getClass();
					}
				}
			}
		}
	}

	@ManagedOperation(description = "Dispose of the current instance of bean name provided and force a refresh on next method execution.")
	public boolean refresh(String name) {
		if (!name.startsWith(SCOPED_TARGET_PREFIX)) {
			// User wants to refresh the bean with this name but that isn't the one in the
			// cache...
			name = SCOPED_TARGET_PREFIX + name;
		}
		// Ensure lifecycle is finished if bean was disposable
		if (super.destroy(name)) {
			this.context.publishEvent(new RefreshScopeRefreshedEvent(name));
			return true;
		}
		return false;
	}

	@ManagedOperation(description = "Dispose of the current instance of all beans in this scope and force a refresh on next method execution.")
	public void refreshAll() {
		super.destroy();
		if (this.context != null && this.context.getBeanNamesForType(
				ConfigurationPropertiesBindingPostProcessor.class).length == 1) {
			try {
				ConfigurationPropertiesBindingPostProcessor processor = context
						.getBean(ConfigurationPropertiesBindingPostProcessor.class);
				//TODO: replace reflection with agreed upon contract
				Field field = ReflectionUtils.findField(ConfigurationPropertiesBindingPostProcessor.class, "propertySources");
				ReflectionUtils.makeAccessible(field);
				MutablePropertySources propertySources = ((ConfigurableEnvironment) this.context.getEnvironment()).getPropertySources();
				ReflectionUtils.setField(field, processor, propertySources);
				processor.afterPropertiesSet();
			}
			catch (Exception e) {
			}
		}
		this.context.publishEvent(new RefreshScopeRefreshedEvent());
	}

	@Override
	public void setApplicationContext(ApplicationContext context) throws BeansException {
		this.context = context;
	}

}

@@ -691,40 +691,8 @@
 ble;
-%0Aimport java.lang.reflect.Field;
 %0A%0Aim
@@ -1312,129 +1312,8 @@
 ed;%0A
-import org.springframework.core.env.ConfigurableEnvironment;%0Aimport org.springframework.core.env.MutablePropertySources;%0A
 impo
@@ -1444,57 +1444,8 @@
 rce;
-%0Aimport org.springframework.util.ReflectionUtils;
 %0A%0A/*
@@ -6067,413 +6067,8 @@
 s);%0A
-%09%09%09%09//TODO: replace reflection with agreed upon contract%0A%09%09%09%09Field field = ReflectionUtils.findField(ConfigurationPropertiesBindingPostProcessor.class, %22propertySources%22);%0A%09%09%09%09ReflectionUtils.makeAccessible(field);%0A%09%09%09%09MutablePropertySources propertySources = ((ConfigurableEnvironment) this.context.getEnvironment()).getPropertySources();%0A%09%09%09%09ReflectionUtils.setField(field, processor, propertySources);%0A
 %09%09%09%09

Remove reflection hack.