001/*
002 * Copyright 2002-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 *      https://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.web.server.adapter;
018
019import javax.servlet.ServletContext;
020import javax.servlet.ServletContextEvent;
021import javax.servlet.ServletContextListener;
022import javax.servlet.ServletException;
023import javax.servlet.ServletRegistration;
024
025import org.springframework.context.ApplicationContext;
026import org.springframework.context.ConfigurableApplicationContext;
027import org.springframework.context.annotation.AnnotationConfigApplicationContext;
028import org.springframework.http.server.reactive.HttpHandler;
029import org.springframework.http.server.reactive.ServletHttpHandlerAdapter;
030import org.springframework.util.Assert;
031import org.springframework.web.WebApplicationInitializer;
032
033/**
034 * Base class for a {@link org.springframework.web.WebApplicationInitializer}
035 * that installs a Spring Reactive Web Application on a Servlet container.
036 *
037 * <p>Spring configuration is loaded and given to
038 * {@link WebHttpHandlerBuilder#applicationContext WebHttpHandlerBuilder}
039 * which scans the context looking for specific beans and creates a reactive
040 * {@link HttpHandler}. The resulting handler is installed as a Servlet through
041 * the {@link ServletHttpHandlerAdapter}.
042 *
043 * @author Rossen Stoyanchev
044 * @since 5.0.2
045 */
046public abstract class AbstractReactiveWebInitializer implements WebApplicationInitializer {
047
048        /**
049         * The default servlet name to use. See {@link #getServletName}.
050         */
051        public static final String DEFAULT_SERVLET_NAME = "http-handler-adapter";
052
053
054        @Override
055        public void onStartup(ServletContext servletContext) throws ServletException {
056                String servletName = getServletName();
057                Assert.hasLength(servletName, "getServletName() must not return null or empty");
058
059                ApplicationContext applicationContext = createApplicationContext();
060                Assert.notNull(applicationContext, "createApplicationContext() must not return null");
061
062                refreshApplicationContext(applicationContext);
063                registerCloseListener(servletContext, applicationContext);
064
065                HttpHandler httpHandler = WebHttpHandlerBuilder.applicationContext(applicationContext).build();
066                ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter(httpHandler);
067
068                ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, servlet);
069                if (registration == null) {
070                        throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
071                                        "Check if there is another servlet registered under the same name.");
072                }
073
074                registration.setLoadOnStartup(1);
075                registration.addMapping(getServletMapping());
076                registration.setAsyncSupported(true);
077        }
078
079        /**
080         * Return the name to use to register the {@link ServletHttpHandlerAdapter}.
081         * <p>By default this is {@link #DEFAULT_SERVLET_NAME}.
082         */
083        protected String getServletName() {
084                return DEFAULT_SERVLET_NAME;
085        }
086
087        /**
088         * Return the Spring configuration that contains application beans including
089         * the ones detected by {@link WebHttpHandlerBuilder#applicationContext}.
090         */
091        protected ApplicationContext createApplicationContext() {
092                AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
093                Class<?>[] configClasses = getConfigClasses();
094                Assert.notEmpty(configClasses, "No Spring configuration provided through getConfigClasses()");
095                context.register(configClasses);
096                return context;
097        }
098
099        /**
100         * Specify {@link org.springframework.context.annotation.Configuration @Configuration}
101         * and/or {@link org.springframework.stereotype.Component @Component}
102         * classes that make up the application configuration. The config classes
103         * are given to {@linkplain #createApplicationContext()}.
104         */
105        protected abstract Class<?>[] getConfigClasses();
106
107        /**
108         * Refresh the given application context, if necessary.
109         */
110        protected void refreshApplicationContext(ApplicationContext context) {
111                if (context instanceof ConfigurableApplicationContext) {
112                        ConfigurableApplicationContext cac = (ConfigurableApplicationContext) context;
113                        if (!cac.isActive()) {
114                                cac.refresh();
115                        }
116                }
117        }
118
119        /**
120         * Register a {@link ServletContextListener} that closes the given
121         * application context when the servlet context is destroyed.
122         * @param servletContext the servlet context to listen to
123         * @param applicationContext the application context that is to be
124         * closed when {@code servletContext} is destroyed
125         */
126        protected void registerCloseListener(ServletContext servletContext, ApplicationContext applicationContext) {
127                if (applicationContext instanceof ConfigurableApplicationContext) {
128                        ConfigurableApplicationContext cac = (ConfigurableApplicationContext) applicationContext;
129                        ServletContextDestroyedListener listener = new ServletContextDestroyedListener(cac);
130                        servletContext.addListener(listener);
131                }
132        }
133
134        /**
135         * Return the Servlet mapping to use. Only the default Servlet mapping '/'
136         * and path-based Servlet mappings such as '/api/*' are supported.
137         * <p>By default this is set to '/'.
138         */
139        protected String getServletMapping() {
140                return "/";
141        }
142
143
144        private static class ServletContextDestroyedListener implements ServletContextListener {
145
146                private final ConfigurableApplicationContext applicationContext;
147
148                public ServletContextDestroyedListener(ConfigurableApplicationContext applicationContext) {
149                        this.applicationContext = applicationContext;
150                }
151
152                @Override
153                public void contextInitialized(ServletContextEvent sce) {
154                }
155
156                @Override
157                public void contextDestroyed(ServletContextEvent sce) {
158                        this.applicationContext.close();
159                }
160        }
161
162}