Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve handling of missing JSF session in SessionScope [SPR-12402] #17010

Closed
spring-projects-issues opened this issue Oct 31, 2014 · 9 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

Andrei Ivanov opened SPR-12402 and commented

I have some JSF beans managed by Spring using @Configurable and compile time weaving.

Then I have a session scoped bean:

<bean id="currentUser" factory-bean="authenticationUserService" factory-method="loadCurrentUser"
     scope="session" lazy-init="true" />

When Tomcat shuts down, it persists the active sessions.
When it starts it tries to restore those sessions, which triggers this exception:

SEVERE: Exception loading sessions from persistent storage
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'settings.web.bean.Users': Injection of autowired d
ependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private transient settings.model.User settings.web.bean.Users.currentUser; nested exception is java.lang.NullPointerException
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.j
ava:293)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1186)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:384)
        at org.springframework.beans.factory.wiring.BeanConfigurerSupport.configureBean(BeanConfigurerSupport.java:141)
        at org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect.configureBean(AnnotationBeanConfigurerAspect.aj:63)
        at org.springframework.beans.factory.aspectj.AbstractDependencyInjectionAspect.ajc$afterReturning$org_springframework_beans_factory_aspectj_AbstractD
ependencyInjectionAspect$3$6aa27052(AbstractDependencyInjectionAspect.aj:97)
        at org.springframework.beans.factory.aspectj.AbstractInterfaceDrivenDependencyInjectionAspect.ajc$interMethod$org_springframework_beans_factory_aspec
tj_AbstractInterfaceDrivenDependencyInjectionAspect$org_springframework_beans_factory_aspectj_AbstractInterfaceDrivenDependencyInjectionAspect$ConfigurableDe
serializationSupport$readResolve(AbstractInterfaceDrivenDependencyInjectionAspect.aj:117)
        at settings.web.bean.Users.readResolve(Users.java:1)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at java.io.ObjectStreamClass.invokeReadResolve(ObjectStreamClass.java:1104)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1807)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
        at java.util.HashMap.readObject(HashMap.java:1180)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1017)
        at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1893)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1798)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
        at java.util.HashMap.readObject(HashMap.java:1180)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1017)
        at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1893)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1798)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
        at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1990)
        at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1915)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1798)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
        at org.apache.catalina.session.StandardSession.readObject(StandardSession.java:1619)
        at org.apache.catalina.session.StandardSession.readObjectData(StandardSession.java:1084)
        at org.apache.catalina.session.StandardManager.doLoad(StandardManager.java:282)
        at org.apache.catalina.session.StandardManager.load(StandardManager.java:202)
        at org.apache.catalina.session.StandardManager.startInternal(StandardManager.java:489)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
        at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5501)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901)
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877)
        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:649)
        at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:1083)
        at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1879)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
        at java.util.concurrent.FutureTask.run(FutureTask.java:262)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private transient settings.model.User settings.web.bean.Users.currentUser; nested exception is java.lang.NullPointerException
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcesso
r.java:509)
        at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.j
ava:290)
        ... 57 more
Caused by: java.lang.NullPointerException
        at org.springframework.web.context.request.SessionScope.get(SessionScope.java:92)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:336)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1021)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:964)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:862)
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcesso
r.java:481)
        ... 59 more

As there is no current request, this exception gets thrown.
I'm hoping that a better way of handling this scenario can be implemented.


Affects: 4.0.7

Referenced from: commits 042d8d0

@spring-projects-issues
Copy link
Collaborator Author

Andrei Ivanov commented

My workaround (and possible solution):

public class CustomSessionScope extends SessionScope {

	@Override
	public Object get(String name, ObjectFactory<?> objectFactory) {
		Object mutex = RequestContextHolder.currentRequestAttributes().getSessionMutex();
		if (mutex == null) {
			// During the session restore phase at server startup, there is no session yet available.
			return null;
		}
		return super.get(name, objectFactory);
	}
}

Any thoughts?

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

How could the session mutex ever be null, actually? All our out-of-the-box implementations of getSessionMutex() never return null and would rather fail internally if there was no session...

@spring-projects-issues
Copy link
Collaborator Author

Andrei Ivanov commented

Well, I mention the scenario in the description: when Tomcat starts, it tries to restore those sessions from the serialized files on disk, which triggers this exception.

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

Sure, and I'd love to address this... but technically, even in such a scenario, I don't see how getSessionMutex() would ever return null. In the case of ServletRequestAttributes, the code path would fail with assertions in other places, leading to exceptions rather than null values. If we'd like to make it defensive towards non-existing sessions, we'd rather have to relax those downstream assumptions.

In other words, it's not obvious to me why your suggested workaround does the job for you. How exactly does it arrive at mutex == null? Am I missing something?

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

Also, returning null from the Scope.get implementation isn't meant to happen either: That produces a null value for a particular bean, then to be injected into other places. Seems rather fragile to me...

FWIW, both Scope.get and RequestAttributes.getSessionMutex are defined to never return null according to their javadoc. We can consider relaxing this, but at the moment, it seems rather tight.

@spring-projects-issues
Copy link
Collaborator Author

Andrei Ivanov commented

When starting Tomcat and the web app gets deployed, this method gets called:

@Override
	public Object getSessionMutex() {
		// Enforce presence of a session first to allow listeners
		// to create the mutex attribute, if any.
		Object session = getExternalContext().getSession(true);
		Object mutex = getExternalContext().getSessionMap().get(WebUtils.SESSION_MUTEX_ATTRIBUTE);
		if (mutex == null) {
			mutex = session;
		}
		return mutex;
	}
                            FacesRequestAttributes.getSessionMutex() line: 232	
                                    CustomSessionScope.get(String, ObjectFactory<?>) line: 11	
                                                                                    DefaultListableBeanFactory(AbstractBeanFactory).doGetBean(String, Class<T>, Object[], boolean) line: 340	
                                                    DefaultListableBeanFactory(AbstractBeanFactory).getBean(String) line: 197	
                                                                                DefaultListableBeanFactory.findAutowireCandidates(String, Class<?>, DependencyDescriptor) line: 1192	
                                                                                                DefaultListableBeanFactory.doResolveDependency(DependencyDescriptor, String, Set<String>, TypeConverter) line: 1116	
                                                                                            DefaultListableBeanFactory.resolveDependency(DependencyDescriptor, String, Set<String>, TypeConverter) line: 1014	
                                                                                        AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(Object, String, PropertyValues) line: 545	
                                            InjectionMetadata.inject(Object, String, PropertyValues) line: 88	
                                                                                                        AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(PropertyValues, PropertyDescriptor[], Object, String) line: 331	
                                                                                                            DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).populateBean(String, RootBeanDefinition, BeanWrapper) line: 1214	
                                                                                                DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).autowireBeanProperties(Object, int, boolean) line: 385	
                                BeanConfigurerSupport.configureBean(Object) line: 141	
                                        AnnotationBeanConfigurerAspect.configureBean(Object) line: 63	
                                                                                                                                                                        AnnotationBeanConfigurerAspect(AbstractDependencyInjectionAspect).ajc$afterReturning$org_springframework_beans_factory_aspectj_AbstractDependencyInjectionAspect$3$6aa27052(Object) line: 97	
                                                                                                                                                                                                                                                                                                                                                                                AbstractInterfaceDrivenDependencyInjectionAspect.ajc$interMethod$org_springframework_beans_factory_aspectj_AbstractInterfaceDrivenDependencyInjectionAspect$org_springframework_beans_factory_aspectj_AbstractInterfaceDrivenDependencyInjectionAspect$ConfigurableDeserializationSupport$readResolve(AbstractInterfaceDrivenDependencyInjectionAspect$ConfigurableDeserializationSupport) line: 117	
                    ViewPreferencesBean.readResolve() line: 1	
                                                                        NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]	
                                    NativeMethodAccessorImpl.invoke(Object, Object[]) line: 62	
                                        DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43	
                    Method.invoke(Object, Object...) line: 498	
                                ObjectStreamClass.invokeReadResolve(Object) line: 1148	
                                                            CustomObjectInputStream(ObjectInputStream).readOrdinaryObject(boolean) line: 1810	
                                                    CustomObjectInputStream(ObjectInputStream).readObject0(boolean) line: 1351	
                                            CustomObjectInputStream(ObjectInputStream).readObject() line: 371	
                                    StandardSession.doReadObject(ObjectInputStream) line: 1623	
                                        StandardSession.readObjectData(ObjectInputStream) line: 1089	
            StandardManager.doLoad() line: 218	
            StandardManager.load() line: 162	
                    StandardManager.startInternal() line: 356	
                            StandardManager(LifecycleBase).start() line: 147	
                    StandardContext.startInternal() line: 5268	
                            StandardContext(LifecycleBase).start() line: 147	
                                            StandardHost(ContainerBase).addChildInternal(Container) line: 725	
                                    StandardHost(ContainerBase).addChild(Container) line: 701	
                    StandardHost.addChild(Container) line: 717	
                            HostConfig.deployWAR(ContextName, File) line: 940	
                HostConfig$DeployWar.run() line: 1816	
                        Executors$RunnableAdapter<T>.call() line: 511	
        FutureTask<V>.run() line: 266	
                                            ThreadPoolExecutor.runWorker(ThreadPoolExecutor$Worker) line: 1142	
                    ThreadPoolExecutor$Worker.run() line: 617	
Thread.run() line: 745	

getExternalContext() returns an instance com.sun.faces.config.InitFacesContext$ServletContextAdapter (from Mojarra 2.2.13), the doc of com.sun.faces.config.InitFacesContext saying:

A special, minimal implementation of FacesContext used at application initialization time. The ExternalContext returned by this FacesContext only exposes the ApplicationMap.
getExternalContext().getSession(true) returns null in this case.
getExternalContext().getSessionMap() returns an empty map.

This leads to the mutex being null.

This is a corner case, I admit that it's not nice for these methods to return null, but that's how it happens :)

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

It seems appropriate to tighten our FacesRequestAttributes implementation then, falling back to the ExternalContext reference itself as a session mutex if the session reference is null. This would nevertheless make your scenario work, wouldn't it?

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

I've rolled this in as suggested above, with FacesRequestAttributes falling back to the ExternalContext as a session mutex. This makes SessionScope proceed with locking against the mutex, avoiding the NullPointerException and providing consistent creation/failure semantics.

@spring-projects-issues
Copy link
Collaborator Author

Andrei Ivanov commented

I ran a test, seems to work fine now :)
Thank you

@spring-projects-issues spring-projects-issues added type: enhancement A general enhancement in: web Issues in web modules (web, webmvc, webflux, websocket) labels Jan 11, 2019
@spring-projects-issues spring-projects-issues added this to the 4.3 RC2 milestone Jan 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

2 participants