/*
* Copyright 2013-2014 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.restart;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.integration.monitor.IntegrationMBeanExporter;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
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 restart the context.
*
* @author Dave Syer
*
*/
@ConfigurationProperties("endpoints.restart")
@ManagedResource
public class RestartEndpoint extends AbstractEndpoint<Boolean>
implements ApplicationListener<ApplicationPreparedEvent> {
private static Log logger = LogFactory.getLog(RestartEndpoint.class);
public RestartEndpoint() {
super("restart", true, false);
}
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();
}
}
@Override
public Boolean invoke() {
try {
restart();
logger.info("Restarted");
return true;
}
catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.info("Could not restart", e);
}
else {
logger.info("Could not restart: " + e.getMessage());
}
return false;
}
}
public PauseEndpoint getPauseEndpoint() {
return new PauseEndpoint();
}
public ResumeEndpoint getResumeEndpoint() {
return new ResumeEndpoint();
}
@ConfigurationProperties("endpoints")
public class PauseEndpoint extends AbstractEndpoint<Boolean> {
public PauseEndpoint() {
super("pause", true, true);
}
@Override
public Boolean invoke() {
if (isRunning()) {
pause();
return true;
}
return false;
}
}
@ConfigurationProperties("endpoints")
public class ResumeEndpoint extends AbstractEndpoint<Boolean> {
public ResumeEndpoint() {
super("resume", true, true);
}
@Override
public Boolean invoke() {
if (!isRunning()) {
resume();
return true;
}
return false;
}
}
@ManagedOperation
public synchronized ConfigurableApplicationContext restart() {
if (this.context != null) {
if (this.integrationShutdown != null) {
this.integrationShutdown.stop(this.timeout);
}
this.application.setEnvironment(this.context.getEnvironment());
this.context.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;
}
@ManagedAttribute
public boolean isRunning() {
if (this.context != null) {
return this.context.isRunning();
}
return false;
}
@ManagedOperation
public synchronized void pause() {
if (this.context != null) {
this.context.stop();
}
}
@ManagedOperation
public synchronized void resume() {
if (this.context != null) {
this.context.start();
}
}
private void overrideClassLoaderForRestart() {
ClassUtils.overrideThreadContextClassLoader(
this.application.getClass().getClassLoader());
}
private class IntegrationShutdown {
private IntegrationMBeanExporter exporter;
public IntegrationShutdown(Object exporter) {
this.exporter = (IntegrationMBeanExporter) exporter;
}
public void stop(long timeout) {
this.exporter.stopActiveComponents(timeout);
}
}
}