001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.spring;
018
019import java.util.Map;
020
021import jakarta.servlet.ServletContext;
022
023import org.apache.wicket.protocol.http.IWebApplicationFactory;
024import org.apache.wicket.protocol.http.WebApplication;
025import org.apache.wicket.protocol.http.WicketFilter;
026import org.apache.wicket.spring.injection.annot.SpringComponentInjector;
027import org.springframework.beans.BeansException;
028import org.springframework.beans.factory.BeanFactoryUtils;
029import org.springframework.context.ApplicationContext;
030import org.springframework.web.context.ConfigurableWebApplicationContext;
031import org.springframework.web.context.WebApplicationContext;
032import org.springframework.web.context.support.WebApplicationContextUtils;
033import org.springframework.web.context.support.XmlWebApplicationContext;
034
035/**
036 * Implementation of IWebApplicationFactory that pulls the WebApplication object out of spring
037 * application context.
038 *
039 * Configuration example:
040 *
041 * <pre>
042 * &lt;filter&gt;
043 *   &lt;filter-name&gt;MyApplication&lt;/filter-name&gt;
044 *   &lt;filter-class&gt;org.apache.wicket.protocol.http.WicketFilter&lt;/filter-class&gt;
045 *   &lt;init-param&gt;
046 *     &lt;param-name&gt;applicationFactoryClassName&lt;/param-name&gt;
047 *     &lt;param-value&gt;org.apache.wicket.spring.SpringWebApplicationFactory&lt;/param-value&gt;
048 *   &lt;/init-param&gt;
049 * &lt;/filter&gt;
050 * </pre>
051 *
052 * <code>applicationBean</code> init parameter can be used if there are multiple WebApplications
053 * defined on the spring application context.
054 *
055 * Example:
056 *
057 * <pre>
058 * &lt;filter&gt;
059 *   &lt;filter-name&gt;MyApplication&lt;/filter-name&gt;
060 *   &lt;filter-class&gt;org.apache.wicket.protocol.http.WicketFilter&lt;/filter-class&gt;
061 *   &lt;init-param&gt;
062 *     &lt;param-name&gt;applicationFactoryClassName&lt;/param-name&gt;
063 *     &lt;param-value&gt;org.apache.wicket.spring.SpringWebApplicationFactory&lt;/param-value&gt;
064 *   &lt;/init-param&gt;
065 *   &lt;init-param&gt;
066 *     &lt;param-name&gt;applicationBean&lt;/param-name&gt;
067 *     &lt;param-value&gt;phonebookApplication&lt;/param-value&gt;
068 *   &lt;/init-param&gt;
069 * &lt;/filter&gt;
070 * </pre>
071 *
072 * <p>
073 * This factory is also capable of creating a {@link WebApplication}-specific application context
074 * (path to which is specified via the {@code contextConfigLocation} filter param) and chaining it
075 * to the global one
076 * </p>
077 *
078 * <pre>
079 * &lt;filter&gt;
080 *   &lt;filter-name&gt;MyApplication&lt;/filter-name&gt;
081 *   &lt;filter-class&gt;org.apache.wicket.protocol.http.WicketFilter&lt;/filter-class&gt;
082 *   &lt;init-param&gt;
083 *     &lt;param-name&gt;applicationFactoryClassName&lt;/param-name&gt;
084 *     &lt;param-value&gt;org.apache.wicket.spring.SpringWebApplicationFactory&lt;/param-value&gt;
085 *   &lt;/init-param&gt;
086 *   &lt;init-param&gt;
087 *     &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
088 *     &lt;param-value&gt;classpath:com/myapplication/customers-app/context.xml&lt;/param-value&gt;
089 *   &lt;/init-param&gt;
090 * &lt;/filter&gt;
091 * </pre>
092 *
093 * @author Igor Vaynberg (ivaynberg)
094 * @author Janne Hietam&auml;ki (jannehietamaki)
095 *
096 */
097public class SpringWebApplicationFactory implements IWebApplicationFactory
098{
099
100        /** web application context created for this filter, if any */
101        private ConfigurableWebApplicationContext webApplicationContext;
102
103        /**
104         * Returns location of context config that will be used to create a {@link WebApplication}
105         * -specific application context.
106         *
107         * @param filter
108         * @return location of context config
109         */
110        protected final String getContextConfigLocation(final WicketFilter filter)
111        {
112                return filter.getFilterConfig().getInitParameter("contextConfigLocation");
113        }
114
115        /**
116         * Factory method used to create a new instance of the web application context, by default an
117         * instance of {@link XmlWebApplicationContext} will be created.
118         *
119         * @return application context instance
120         */
121        protected ConfigurableWebApplicationContext newApplicationContext()
122        {
123                return new XmlWebApplicationContext();
124        }
125
126        @Override
127        public WebApplication createApplication(final WicketFilter filter)
128        {
129                ServletContext servletContext = filter.getFilterConfig().getServletContext();
130
131                WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
132
133                if (getContextConfigLocation(filter) != null)
134                {
135                        applicationContext = createWebApplicationContext(applicationContext, filter);
136                }
137
138                String beanName = filter.getFilterConfig().getInitParameter("applicationBean");
139                return createApplication(applicationContext, beanName);
140        }
141
142        private WebApplication createApplication(final ApplicationContext applicationContext,
143                final String beanName)
144        {
145                WebApplication application;
146
147                if (beanName != null)
148                {
149                        application = (WebApplication)applicationContext.getBean(beanName);
150                        if (application == null)
151                        {
152                                throw new IllegalArgumentException(
153                                        "Unable to find WebApplication bean with name [" + beanName + "]");
154                        }
155                }
156                else
157                {
158                        Map<?, ?> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext,
159                                WebApplication.class, false, false);
160                        if (beans.size() == 0)
161                        {
162                                throw new IllegalStateException("bean of type [" + WebApplication.class.getName() +
163                                        "] not found");
164                        }
165                        if (beans.size() > 1)
166                        {
167                                throw new IllegalStateException("More than one bean of type [" +
168                                        WebApplication.class.getName() + "] found, must have only one");
169                        }
170                        application = (WebApplication)beans.values().iterator().next();
171                }
172
173                // make the application context default for SpringComponentInjectors
174                SpringComponentInjector.setDefaultContext(application, applicationContext);
175
176                return application;
177        }
178
179        /**
180         * Creates and initializes a new {@link WebApplicationContext}, with the given context as the
181         * parent. Based on the logic in Spring's FrameworkServlet#createWebApplicationContext()
182         *
183         * @param parent
184         *            parent application context
185         * @param filter
186         *            wicket filter
187         * @return instance of web application context
188         * @throws BeansException
189         */
190        protected final ConfigurableWebApplicationContext createWebApplicationContext(
191                final WebApplicationContext parent, final WicketFilter filter) throws BeansException
192        {
193                webApplicationContext = newApplicationContext();
194                webApplicationContext.setParent(parent);
195                webApplicationContext.setServletContext(filter.getFilterConfig().getServletContext());
196                webApplicationContext.setConfigLocation(getContextConfigLocation(filter));
197
198                postProcessWebApplicationContext(webApplicationContext, filter);
199                webApplicationContext.refresh();
200
201                return webApplicationContext;
202        }
203
204        /**
205         * This is a hook for potential subclasses to perform additional processing on the context.
206         * Based on the logic in Spring's FrameworkServlet#postProcessWebApplicationContext()
207         *
208         * @param wac
209         *            additional application context
210         * @param filter
211         *            wicket filter
212         */
213        protected void postProcessWebApplicationContext(final ConfigurableWebApplicationContext wac,
214                final WicketFilter filter)
215        {
216                // noop
217        }
218
219        @Override
220        public void destroy(final WicketFilter filter)
221        {
222                if (webApplicationContext != null)
223                {
224                        webApplicationContext.close();
225                }
226        }
227}