Initial situation
My web application consists of the Maven modules myapp-persistence(.jar), myapp-model(.jar), myapp-service(.jar) and myapp-web(.war) to get a conventional, loosely coupled, multi-tiered architecture. All modules are joined together by a parent Maven module, which only contains the parent POM with general definitions for all sub modules.
Especially myapp-service(.jar) and myapp-persistence(.jar) hold their own configurable (!) application context parts with the needed objects. Both jars must be deployable with the containing variable definitions, in other words the jars must not have concrete values for the variables.
myapp-service-context.xml declares a solrServer bean with the variable of the server URL:
<bean id="solrServer" class="org.apache.solr.client.solrj.impl.HttpSolrServer">
<constructor-arg value="${solr.serverUrl}" />
<property name="connectionTimeout" value="60000"/>
<property name="defaultMaxConnectionsPerHost" value="40"/>
<property name="maxTotalConnections" value="40"/>
</bean>
myapp-persistence-context.xml defines a dataSource with connection variables:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
...
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
...
</bean>
myapp-web(.war) references myapp-service(.jar) and myapp-persistence(.jar). In myapp-servlet.xml it includes their application context parts and provides the property values for configuration of the declared beans by a property file. By context:property-placeholder Spring initializes all the variables with the concrete values when it creates the application context in memory.
<context:property-placeholder location="classpath*:myapp-configuration.properties" />
<import resource="classpath*:myapp-persistence-context.xml"/>
<import resource="classpath*:myapp-service-context.xml"/>
For the development profile the concrete myapp-configuration.properties may look like:
solr.serverUrl=http://localhost:8983/solr
jdbc.dialect=org.hibernate.dialect.HSQLDialect
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:mem:myapp
jdbc.username=sa
jdbc.password=
This configuration is imo straight forward and works - without view. The problem arises when org.springframework.orm.hibernate3.support.OpenSessionInViewFilter comes into play.
Problem description
OpenSessionInViewFilter ensures that instances in the object graph which are not loaded within an open transaction during the controller processing can be lazily loaded, if the view tries to display these objects´ content (see [1]). As often described this filter is declared in the delpoyment descriptor web.xml (see [2]):
<filter>
<filter-name>OpenSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>OpenSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
If myapp-persistence-context.xml is included in myapp-servlet.xml like above so that context:property-placeholder works, OpenSessionInViewFilter does not find the necessary sessionFactory. The reason seems to be that Spring first processes web.xml and then myapp-servlet.xml, which imports myapp-persistence-context.xml. Unfourtunately I can´t proof this guess by a reference. Following exception is thrown:
GRAVE: Servlet.service() for servlet [myapp] in context with path [/myapp] threw exception org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'sessionFactory' is defined at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:529) at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1095) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:277) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1097) at org.springframework.orm.hibernate3.support.OpenSessionInViewFilter.lookupSessionFactory(OpenSessionInViewFilter.java:242) at org.springframework.orm.hibernate3.support.OpenSessionInViewFilter.lookupSessionFactory(OpenSessionInViewFilter.java:227) at org.springframework.orm.hibernate3.support.OpenSessionInViewFilter.doFilterInternal(OpenSessionInViewFilter.java:171) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99) at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:936) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1004) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589) at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) at java.lang.Thread.run(Thread.java:662)
Different application context parts are usually included by the deployment descriptor with a ContextLoaderListener and not by myapp-servlet.xml:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:myapp-service-context.xml,
classpath*:myapp-persistence-context.xml
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
With this configuration unfourtunately, Spring´ s context:property-placeholder mechanism seems not to work any more.
Objective and Question
Modules like myapp-persistence(.jar) and myapp-service(.jar) must be configurable at run-time with a property file by the referencing context, e.g. the application context of myapp-web(.war).
The question is: Is it possible to configure OpenSessionInViewFilter in Spring application context so that context:property-placeholder is still useable?
Or alternatively: How can variables in applicaton contexts be initialized by Spring at run-time, if application context parts are included in the deployment descriptor web.xml?
Fundamentally: Why is actually OpenSessionInViewFilter necessary to be configured, why Spring MVC does not transparently support view lazy loading out-of-the-box?
Anticipating Remarks
Property replacement at compile-time is not the point here. The profile dependent property file is already created with Maven Filtering.
Moving dataSource and solrServer declarations into myapp-servlet.xml as already proposed (see [3], [4]) is not an acceptable solution, because it destroyes modularity and independent testability of myapp-persistence(.jar) and myapp-service(.jar) - actually the spirit of dependency injection!
Talking to a colleague brought the solution: context:property-placeholder
remains useable if I use Spring´s interceptor instead of the Servlet-API´s filter mechanism. I removed the reference of myapp-persistence-context.xml in contextConfigLocation
and the OpenSessionInViewFilter
from web.xml and declared an OpenSessionInViewInterceptor
in myapp-persistence-context.xml:
<mvc:interceptors>
<bean id="openSessionInViewInterceptor" class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</mvc:interceptors>
With myapp-persistence-context.xml in the classpath of myapp-persistence(.jar) and the import statement in myapp-servlet.xml as described above, Spring replaces all property variables with the values in myapp-configuration.properties at run-time as intended. Modularity is saved at its best! Will Keeling´s externalization of the property file completes the project setup.
See also the discussion Spring HandlerInterceptor vs Servlet Filters and the Spring doc.