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 javax.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 o {@link XmlWebApplicationContext} will be created.
118         * 
119         * @return application context instance
120         */
121        protected ConfigurableWebApplicationContext newApplicationContext()
122        {
123                return new XmlWebApplicationContext();
124        }
125
126        /**
127         * @see IWebApplicationFactory#createApplication(WicketFilter)
128         */
129        @Override
130        public WebApplication createApplication(final WicketFilter filter)
131        {
132                ServletContext servletContext = filter.getFilterConfig().getServletContext();
133
134                WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
135
136                if (getContextConfigLocation(filter) != null)
137                {
138                        applicationContext = createWebApplicationContext(applicationContext, filter);
139                }
140
141                String beanName = filter.getFilterConfig().getInitParameter("applicationBean");
142                return createApplication(applicationContext, beanName);
143        }
144
145        private WebApplication createApplication(final ApplicationContext applicationContext,
146                final String beanName)
147        {
148                WebApplication application;
149
150                if (beanName != null)
151                {
152                        application = (WebApplication)applicationContext.getBean(beanName);
153                        if (application == null)
154                        {
155                                throw new IllegalArgumentException(
156                                        "Unable to find WebApplication bean with name [" + beanName + "]");
157                        }
158                }
159                else
160                {
161                        Map<?, ?> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext,
162                                WebApplication.class, false, false);
163                        if (beans.size() == 0)
164                        {
165                                throw new IllegalStateException("bean of type [" + WebApplication.class.getName() +
166                                        "] not found");
167                        }
168                        if (beans.size() > 1)
169                        {
170                                throw new IllegalStateException("More than one bean of type [" +
171                                        WebApplication.class.getName() + "] found, must have only one");
172                        }
173                        application = (WebApplication)beans.values().iterator().next();
174                }
175
176                // make the application context default for SpringComponentInjectors
177                SpringComponentInjector.setDefaultContext(application, applicationContext);
178
179                return application;
180        }
181
182        /**
183         * Creates and initializes a new {@link WebApplicationContext}, with the given context as the
184         * parent. Based on the logic in Spring's FrameworkServlet#createWebApplicationContext()
185         * 
186         * @param parent
187         *            parent application context
188         * @param filter
189         *            wicket filter
190         * @return instance of web application context
191         * @throws BeansException
192         */
193        protected final ConfigurableWebApplicationContext createWebApplicationContext(
194                final WebApplicationContext parent, final WicketFilter filter) throws BeansException
195        {
196                webApplicationContext = newApplicationContext();
197                webApplicationContext.setParent(parent);
198                webApplicationContext.setServletContext(filter.getFilterConfig().getServletContext());
199                webApplicationContext.setConfigLocation(getContextConfigLocation(filter));
200
201                postProcessWebApplicationContext(webApplicationContext, filter);
202                webApplicationContext.refresh();
203
204                return webApplicationContext;
205        }
206
207        /**
208         * This is a hook for potential subclasses to perform additional processing on the context.
209         * Based on the logic in Spring's FrameworkServlet#postProcessWebApplicationContext()
210         * 
211         * @param wac
212         *            additional application context
213         * @param filter
214         *            wicket filter
215         */
216        protected void postProcessWebApplicationContext(final ConfigurableWebApplicationContext wac,
217                final WicketFilter filter)
218        {
219                // noop
220        }
221
222        /** {@inheritDoc} */
223        @Override
224        public void destroy(final WicketFilter filter)
225        {
226                if (webApplicationContext != null)
227                {
228                        webApplicationContext.close();
229                }
230        }
231}