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.web.reactive.context;
018
019import java.util.function.Supplier;
020
021import reactor.core.publisher.Mono;
022
023import org.springframework.beans.BeansException;
024import org.springframework.beans.factory.support.DefaultListableBeanFactory;
025import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext;
026import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
027import org.springframework.boot.web.server.WebServer;
028import org.springframework.context.ApplicationContextException;
029import org.springframework.http.server.reactive.HttpHandler;
030import org.springframework.http.server.reactive.ServerHttpRequest;
031import org.springframework.http.server.reactive.ServerHttpResponse;
032import org.springframework.util.StringUtils;
033
034/**
035 * A {@link GenericReactiveWebApplicationContext} that can be used to bootstrap itself
036 * from a contained {@link ReactiveWebServerFactory} bean.
037 *
038 * @author Brian Clozel
039 * @since 2.0.0
040 */
041public class ReactiveWebServerApplicationContext
042                extends GenericReactiveWebApplicationContext
043                implements ConfigurableWebServerApplicationContext {
044
045        private volatile ServerManager serverManager;
046
047        private String serverNamespace;
048
049        /**
050         * Create a new {@link ReactiveWebServerApplicationContext}.
051         */
052        public ReactiveWebServerApplicationContext() {
053        }
054
055        /**
056         * Create a new {@link ReactiveWebServerApplicationContext} with the given
057         * {@code DefaultListableBeanFactory}.
058         * @param beanFactory the DefaultListableBeanFactory instance to use for this context
059         */
060        public ReactiveWebServerApplicationContext(DefaultListableBeanFactory beanFactory) {
061                super(beanFactory);
062        }
063
064        @Override
065        public final void refresh() throws BeansException, IllegalStateException {
066                try {
067                        super.refresh();
068                }
069                catch (RuntimeException ex) {
070                        stopAndReleaseReactiveWebServer();
071                        throw ex;
072                }
073        }
074
075        @Override
076        protected void onRefresh() {
077                super.onRefresh();
078                try {
079                        createWebServer();
080                }
081                catch (Throwable ex) {
082                        throw new ApplicationContextException("Unable to start reactive web server",
083                                        ex);
084                }
085        }
086
087        private void createWebServer() {
088                ServerManager serverManager = this.serverManager;
089                if (serverManager == null) {
090                        this.serverManager = ServerManager.get(getWebServerFactory());
091                }
092                initPropertySources();
093        }
094
095        /**
096         * Return the {@link ReactiveWebServerFactory} that should be used to create the
097         * reactive web server. By default this method searches for a suitable bean in the
098         * context itself.
099         * @return a {@link ReactiveWebServerFactory} (never {@code null})
100         */
101        protected ReactiveWebServerFactory getWebServerFactory() {
102                // Use bean names so that we don't consider the hierarchy
103                String[] beanNames = getBeanFactory()
104                                .getBeanNamesForType(ReactiveWebServerFactory.class);
105                if (beanNames.length == 0) {
106                        throw new ApplicationContextException(
107                                        "Unable to start ReactiveWebApplicationContext due to missing "
108                                                        + "ReactiveWebServerFactory bean.");
109                }
110                if (beanNames.length > 1) {
111                        throw new ApplicationContextException(
112                                        "Unable to start ReactiveWebApplicationContext due to multiple "
113                                                        + "ReactiveWebServerFactory beans : "
114                                                        + StringUtils.arrayToCommaDelimitedString(beanNames));
115                }
116                return getBeanFactory().getBean(beanNames[0], ReactiveWebServerFactory.class);
117        }
118
119        @Override
120        protected void finishRefresh() {
121                super.finishRefresh();
122                WebServer webServer = startReactiveWebServer();
123                if (webServer != null) {
124                        publishEvent(new ReactiveWebServerInitializedEvent(webServer, this));
125                }
126        }
127
128        private WebServer startReactiveWebServer() {
129                ServerManager serverManager = this.serverManager;
130                ServerManager.start(serverManager, this::getHttpHandler);
131                return ServerManager.getWebServer(serverManager);
132        }
133
134        /**
135         * Return the {@link HttpHandler} that should be used to process the reactive web
136         * server. By default this method searches for a suitable bean in the context itself.
137         * @return a {@link HttpHandler} (never {@code null}
138         */
139        protected HttpHandler getHttpHandler() {
140                // Use bean names so that we don't consider the hierarchy
141                String[] beanNames = getBeanFactory().getBeanNamesForType(HttpHandler.class);
142                if (beanNames.length == 0) {
143                        throw new ApplicationContextException(
144                                        "Unable to start ReactiveWebApplicationContext due to missing HttpHandler bean.");
145                }
146                if (beanNames.length > 1) {
147                        throw new ApplicationContextException(
148                                        "Unable to start ReactiveWebApplicationContext due to multiple HttpHandler beans : "
149                                                        + StringUtils.arrayToCommaDelimitedString(beanNames));
150                }
151                return getBeanFactory().getBean(beanNames[0], HttpHandler.class);
152        }
153
154        @Override
155        protected void onClose() {
156                super.onClose();
157                stopAndReleaseReactiveWebServer();
158        }
159
160        private void stopAndReleaseReactiveWebServer() {
161                ServerManager serverManager = this.serverManager;
162                try {
163                        ServerManager.stop(serverManager);
164                }
165                finally {
166                        this.serverManager = null;
167                }
168        }
169
170        /**
171         * Returns the {@link WebServer} that was created by the context or {@code null} if
172         * the server has not yet been created.
173         * @return the web server
174         */
175        @Override
176        public WebServer getWebServer() {
177                return ServerManager.getWebServer(this.serverManager);
178        }
179
180        @Override
181        public String getServerNamespace() {
182                return this.serverNamespace;
183        }
184
185        @Override
186        public void setServerNamespace(String serverNamespace) {
187                this.serverNamespace = serverNamespace;
188        }
189
190        /**
191         * Internal class used to manage the server and the {@link HttpHandler}, taking care
192         * not to initialize the handler too early.
193         */
194        static final class ServerManager implements HttpHandler {
195
196                private final WebServer server;
197
198                private volatile HttpHandler handler;
199
200                private ServerManager(ReactiveWebServerFactory factory) {
201                        this.handler = this::handleUninitialized;
202                        this.server = factory.getWebServer(this);
203                }
204
205                private Mono<Void> handleUninitialized(ServerHttpRequest request,
206                                ServerHttpResponse response) {
207                        throw new IllegalStateException(
208                                        "The HttpHandler has not yet been initialized");
209                }
210
211                @Override
212                public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
213                        return this.handler.handle(request, response);
214                }
215
216                public HttpHandler getHandler() {
217                        return this.handler;
218                }
219
220                public static ServerManager get(ReactiveWebServerFactory factory) {
221                        return new ServerManager(factory);
222                }
223
224                public static WebServer getWebServer(ServerManager manager) {
225                        return (manager != null) ? manager.server : null;
226                }
227
228                public static void start(ServerManager manager,
229                                Supplier<HttpHandler> handlerSupplier) {
230                        if (manager != null && manager.server != null) {
231                                manager.handler = handlerSupplier.get();
232                                manager.server.start();
233                        }
234                }
235
236                public static void stop(ServerManager manager) {
237                        if (manager != null && manager.server != null) {
238                                try {
239                                        manager.server.stop();
240                                }
241                                catch (Exception ex) {
242                                        throw new IllegalStateException(ex);
243                                }
244                        }
245                }
246
247        }
248
249}