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