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 * <filter> 043 * <filter-name>MyApplication</filter-name> 044 * <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class> 045 * <init-param> 046 * <param-name>applicationFactoryClassName</param-name> 047 * <param-value>org.apache.wicket.spring.SpringWebApplicationFactory</param-value> 048 * </init-param> 049 * </filter> 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 * <filter> 059 * <filter-name>MyApplication</filter-name> 060 * <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class> 061 * <init-param> 062 * <param-name>applicationFactoryClassName</param-name> 063 * <param-value>org.apache.wicket.spring.SpringWebApplicationFactory</param-value> 064 * </init-param> 065 * <init-param> 066 * <param-name>applicationBean</param-name> 067 * <param-value>phonebookApplication</param-value> 068 * </init-param> 069 * </filter> 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 * <filter> 080 * <filter-name>MyApplication</filter-name> 081 * <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class> 082 * <init-param> 083 * <param-name>applicationFactoryClassName</param-name> 084 * <param-value>org.apache.wicket.spring.SpringWebApplicationFactory</param-value> 085 * </init-param> 086 * <init-param> 087 * <param-name>contextConfigLocation</param-name> 088 * <param-value>classpath:com/myapplication/customers-app/context.xml</param-value> 089 * </init-param> 090 * </filter> 091 * </pre> 092 * 093 * @author Igor Vaynberg (ivaynberg) 094 * @author Janne Hietamä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}