001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.actuate.autoconfigure.web.server;
018
019import org.apache.commons.logging.Log;
020import org.apache.commons.logging.LogFactory;
021
022import org.springframework.beans.factory.SmartInitializingSingleton;
023import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
024import org.springframework.boot.actuate.autoconfigure.web.ManagementContextFactory;
025import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType;
026import org.springframework.boot.autoconfigure.AutoConfigureOrder;
027import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
028import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
029import org.springframework.boot.context.event.ApplicationFailedEvent;
030import org.springframework.boot.context.properties.EnableConfigurationProperties;
031import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext;
032import org.springframework.boot.web.context.WebServerApplicationContext;
033import org.springframework.context.ApplicationContext;
034import org.springframework.context.ApplicationEvent;
035import org.springframework.context.ApplicationListener;
036import org.springframework.context.ConfigurableApplicationContext;
037import org.springframework.context.annotation.Configuration;
038import org.springframework.context.event.ContextClosedEvent;
039import org.springframework.core.Ordered;
040import org.springframework.core.env.ConfigurableEnvironment;
041import org.springframework.core.env.Environment;
042import org.springframework.core.env.PropertySource;
043import org.springframework.core.io.DefaultResourceLoader;
044import org.springframework.util.Assert;
045
046/**
047 * {@link EnableAutoConfiguration Auto-configuration} for the management context. If the
048 * {@code management.server.port} is the same as the {@code server.port} the management
049 * context will be the same as the main application context. If the
050 * {@code management.server.port} is different to the {@code server.port} the management
051 * context will be a separate context that has the main application context as its parent.
052 *
053 * @author Andy Wilkinson
054 * @since 2.0.0
055 */
056@Configuration
057@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
058@EnableConfigurationProperties({ WebEndpointProperties.class,
059                ManagementServerProperties.class })
060public class ManagementContextAutoConfiguration {
061
062        private static final Log logger = LogFactory
063                        .getLog(ManagementContextAutoConfiguration.class);
064
065        @Configuration
066        @ConditionalOnManagementPort(ManagementPortType.SAME)
067        static class SameManagementContextConfiguration
068                        implements SmartInitializingSingleton {
069
070                private final Environment environment;
071
072                SameManagementContextConfiguration(Environment environment) {
073                        this.environment = environment;
074                }
075
076                @Override
077                public void afterSingletonsInstantiated() {
078                        verifySslConfiguration();
079                        if (this.environment instanceof ConfigurableEnvironment) {
080                                addLocalManagementPortPropertyAlias(
081                                                (ConfigurableEnvironment) this.environment);
082                        }
083                }
084
085                private void verifySslConfiguration() {
086                        Boolean enabled = this.environment
087                                        .getProperty("management.server.ssl.enabled", Boolean.class, false);
088                        Assert.state(!enabled,
089                                        "Management-specific SSL cannot be configured as the management "
090                                                        + "server is not listening on a separate port");
091                }
092
093                /**
094                 * Add an alias for 'local.management.port' that actually resolves using
095                 * 'local.server.port'.
096                 * @param environment the environment
097                 */
098                private void addLocalManagementPortPropertyAlias(
099                                ConfigurableEnvironment environment) {
100                        environment.getPropertySources()
101                                        .addLast(new PropertySource<Object>("Management Server") {
102
103                                                @Override
104                                                public Object getProperty(String name) {
105                                                        if ("local.management.port".equals(name)) {
106                                                                return environment.getProperty("local.server.port");
107                                                        }
108                                                        return null;
109                                                }
110
111                                        });
112                }
113
114                @Configuration
115                @EnableManagementContext(ManagementContextType.SAME)
116                static class EnableSameManagementContextConfiguration {
117
118                }
119
120        }
121
122        @Configuration
123        @ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
124        static class DifferentManagementContextConfiguration
125                        implements SmartInitializingSingleton {
126
127                private final ApplicationContext applicationContext;
128
129                private final ManagementContextFactory managementContextFactory;
130
131                DifferentManagementContextConfiguration(ApplicationContext applicationContext,
132                                ManagementContextFactory managementContextFactory) {
133                        this.applicationContext = applicationContext;
134                        this.managementContextFactory = managementContextFactory;
135                }
136
137                @Override
138                public void afterSingletonsInstantiated() {
139                        if (this.applicationContext instanceof WebServerApplicationContext
140                                        && ((WebServerApplicationContext) this.applicationContext)
141                                                        .getWebServer() != null) {
142                                ConfigurableWebServerApplicationContext managementContext = this.managementContextFactory
143                                                .createManagementContext(this.applicationContext,
144                                                                EnableChildManagementContextConfiguration.class,
145                                                                PropertyPlaceholderAutoConfiguration.class);
146                                managementContext.setServerNamespace("management");
147                                managementContext.setId(this.applicationContext.getId() + ":management");
148                                setClassLoaderIfPossible(managementContext);
149                                CloseManagementContextListener.addIfPossible(this.applicationContext,
150                                                managementContext);
151                                managementContext.refresh();
152                        }
153                        else {
154                                logger.warn("Could not start embedded management container on "
155                                                + "different port (management endpoints are still available "
156                                                + "through JMX)");
157                        }
158                }
159
160                private void setClassLoaderIfPossible(ConfigurableApplicationContext child) {
161                        if (child instanceof DefaultResourceLoader) {
162                                ((DefaultResourceLoader) child)
163                                                .setClassLoader(this.applicationContext.getClassLoader());
164                        }
165                }
166
167        }
168
169        /**
170         * {@link ApplicationListener} to propagate the {@link ContextClosedEvent} and
171         * {@link ApplicationFailedEvent} from a parent to a child.
172         */
173        private static class CloseManagementContextListener
174                        implements ApplicationListener<ApplicationEvent> {
175
176                private final ApplicationContext parentContext;
177
178                private final ConfigurableApplicationContext childContext;
179
180                CloseManagementContextListener(ApplicationContext parentContext,
181                                ConfigurableApplicationContext childContext) {
182                        this.parentContext = parentContext;
183                        this.childContext = childContext;
184                }
185
186                @Override
187                public void onApplicationEvent(ApplicationEvent event) {
188                        if (event instanceof ContextClosedEvent) {
189                                onContextClosedEvent((ContextClosedEvent) event);
190                        }
191                        if (event instanceof ApplicationFailedEvent) {
192                                onApplicationFailedEvent((ApplicationFailedEvent) event);
193                        }
194                }
195
196                private void onContextClosedEvent(ContextClosedEvent event) {
197                        propagateCloseIfNecessary(event.getApplicationContext());
198                }
199
200                private void onApplicationFailedEvent(ApplicationFailedEvent event) {
201                        propagateCloseIfNecessary(event.getApplicationContext());
202                }
203
204                private void propagateCloseIfNecessary(ApplicationContext applicationContext) {
205                        if (applicationContext == this.parentContext) {
206                                this.childContext.close();
207                        }
208                }
209
210                public static void addIfPossible(ApplicationContext parentContext,
211                                ConfigurableApplicationContext childContext) {
212                        if (parentContext instanceof ConfigurableApplicationContext) {
213                                add((ConfigurableApplicationContext) parentContext, childContext);
214                        }
215                }
216
217                private static void add(ConfigurableApplicationContext parentContext,
218                                ConfigurableApplicationContext childContext) {
219                        parentContext.addApplicationListener(
220                                        new CloseManagementContextListener(parentContext, childContext));
221                }
222
223        }
224
225}