/* * Copyright 2012-2020 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.context.restart; import java.io.Closeable; import java.io.IOException; import java.util.Collections; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.SpringApplication; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; import org.springframework.boot.context.event.ApplicationPreparedEvent; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.springframework.integration.monitor.IntegrationMBeanExporter; import org.springframework.util.ClassUtils; /** * An endpoint that restarts the application context. Install as a bean and also register * a {@link RestartListener} with the {@link SpringApplication} that starts the context. * Those two components communicate via an {@link ApplicationEvent} and set up the state * needed to doRestart the context. * * @author Dave Syer * */ @Endpoint(id = "restart", enableByDefault = false) public class RestartEndpoint implements ApplicationListener<ApplicationPreparedEvent> { private static Log logger = LogFactory.getLog(RestartEndpoint.class); private ConfigurableApplicationContext context; private SpringApplication application; private String[] args; private ApplicationPreparedEvent event; private IntegrationShutdown integrationShutdown; private long timeout; // @ManagedAttribute public long getTimeout() { return this.timeout; } public void setTimeout(long timeout) { this.timeout = timeout; } public void setIntegrationMBeanExporter(Object exporter) { if (exporter != null) { this.integrationShutdown = new IntegrationShutdown(exporter); } } @Override public void onApplicationEvent(ApplicationPreparedEvent input) { this.event = input; if (this.context == null) { this.context = this.event.getApplicationContext(); this.args = this.event.getArgs(); this.application = this.event.getSpringApplication(); this.application.addInitializers(new PostProcessorInitializer()); } } @WriteOperation public Object restart() { Thread thread = new Thread(this::safeRestart); thread.setDaemon(false); thread.start(); return Collections.singletonMap("message", "Restarting"); } private Boolean safeRestart() { try { doRestart(); logger.info("Restarted"); return true; } catch (Exception e) { if (logger.isDebugEnabled()) { logger.info("Could not doRestart", e); } else { logger.info("Could not doRestart: " + e.getMessage()); } return false; } } public PauseEndpoint getPauseEndpoint() { return new PauseEndpoint(); } public ResumeEndpoint getResumeEndpoint() { return new ResumeEndpoint(); } // @ManagedOperation public synchronized ConfigurableApplicationContext doRestart() { if (this.context != null) { if (this.integrationShutdown != null) { this.integrationShutdown.stop(this.timeout); } this.application.setEnvironment(this.context.getEnvironment()); close(); // If running in a webapp then the context classloader is probably going to // die so we need to revert to a safe place before starting again overrideClassLoaderForRestart(); this.context = this.application.run(this.args); } return this.context; } private void close() { ApplicationContext context = this.context; while (context instanceof Closeable) { try { ((Closeable) context).close(); } catch (IOException e) { logger.error("Cannot close context: " + context.getId(), e); } context = context.getParent(); } } // @ManagedAttribute public boolean isRunning() { if (this.context != null) { return this.context.isRunning(); } return false; } // @ManagedOperation public synchronized void doPause() { if (this.context != null) { this.context.stop(); } } // @ManagedOperation public synchronized void doResume() { if (this.context != null) { this.context.start(); } } private void overrideClassLoaderForRestart() { ClassUtils.overrideThreadContextClassLoader( this.application.getClass().getClassLoader()); } class PostProcessorInitializer implements ApplicationContextInitializer<GenericApplicationContext> { @Override public void initialize(GenericApplicationContext context) { context.registerBean(PostProcessor.class, () -> new PostProcessor()); } } class PostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof RestartEndpoint) { return RestartEndpoint.this; } return bean; } } /** * Pause endpoint configuration. */ @Endpoint(id = "pause") public class PauseEndpoint { @WriteOperation public Boolean pause() { if (isRunning()) { doPause(); return true; } return false; } } /** * Resume endpoint configuration. */ @Endpoint(id = "resume") @ConfigurationProperties("management.endpoint.resume") public class ResumeEndpoint { @WriteOperation public Boolean resume() { if (!isRunning()) { doResume(); return true; } return false; } } private class IntegrationShutdown { private IntegrationMBeanExporter exporter; IntegrationShutdown(Object exporter) { this.exporter = (IntegrationMBeanExporter) exporter; } public void stop(long timeout) { this.exporter.stopActiveComponents(timeout); } } }