For the very first time an OpenMRS instance is started and has modules adding some filters and/or servlets to it’s context during initializaton, an error along the following lines occurs.
java.lang.IllegalStateException: Filters cannot be added to context [/openmrs] as the context has been initialised
at org.apache.catalina.core.ApplicationContext.addFilter(ApplicationContext.java:850)
at org.apache.catalina.core.ApplicationContext.addFilter(ApplicationContext.java:829)
at org.apache.catalina.core.ApplicationContextFacade.addFilter(ApplicationContextFacade.java:455)
at org.openmrs.web.WebComponentRegistrar.setServletContext(WebComponentRegistrar.java:39)
at org.springframework.web.context.support.ServletContextAwareProcessor.postProcessBeforeInitialization(ServletContextAwareProcessor.java:108)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:415)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1791)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:897)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:879)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551)
at org.openmrs.module.ModuleUtil.refreshApplicationContext(ModuleUtil.java:885)
at org.openmrs.module.web.WebModuleUtil.refreshWAC(WebModuleUtil.java:844)
at org.openmrs.web.Listener.performWebStartOfModules(Listener.java:661)
at org.openmrs.web.Listener.performWebStartOfModules(Listener.java:641)
at org.openmrs.web.Listener.startOpenmrs(Listener.java:307)
at org.openmrs.web.WebDaemon$1.run(WebDaemon.java:42)
I have created TRUNK-6169 with details about this. Thoughts on how this can be resolved are welcome.
Modules that need to add filters or servlets should use the ModuleFilter and Module Servlet features, which can be declared via the config.xml.
Tomcat has a check, which only allows addFilter() (and various other methods) to be called while the Tomcat ApplicationContext (this is very separate from anything Spring-related) is in the STARTING_PREP phase (see Tomcat’s overall lifecycle documentation). Basically, the upshot is that those APIs are only validly callable during the Tomcat ClassLoader’s initial scan of the classpath, so basically, these programmatic calls to addFilter() and addServlet() can only be done before the WAR itself is started.
Thanks @ibacher for providing some detail. Considering the fact that Servlets/Filters can be added using a module’s config.xml file, there’s a limitation to how fine grained a servlet’s/filter’s configuration can be unlike pragmatically where that’s seamlessly achievable.
The bug above (failure to add Filter/Servlets pragmatically) only happens at the very first time OpenMRS is starting up and ceases to occur on following restarts. This potentially means something unusual is happening at first time startup, I’ve hinted on this as potential cause on TRUNK-6169.
I hadn’t noticed that before. In any case, the module servlets and module filter servlets are the things we can guarantee will always work. We do need that Spring context your ticket pointed to…
Just to clarify - do we have evidence that something isn’t working the way it should be? I also see that error message in the logs, but when I use the system the filters and servlets that have been added by modules all seem to be operating as I’d expect.
I did not have time to look for a solution to that problem, given the fact that it happens only during the initial setup and upgrades. So i left a comment, just incase some one would like to take it up. So yes, it would be great if some one has the bandwidth to look into it.
Thanks @dkayiwa, could you help curate TRUNK-6169 and edit its description, versions, etc so that it becomes an actionable ticket for the @Platform_Team? I transitioned it to ‘WAITING ON INFORMATION’ for the time being.
I’m happy to have the ticket open if someone has the time to investigate possible solutions. Without a plan for how we can do that, though, it seems like the sort of ticket that’s going to languish…
As @ibacher mentioned, module servlets and filters are supposed to be registered via the config.xml file and that should work otherwise it means @ruhanga could be registering them via another unconventional way.
With that said, this mechanism has limitations and I guess @ruhanga could be running into these limitations, off the top of my head, these limitations were around configuring the servlets e.g to provide init params etc. I recall creating TRUNK-4673 very many years ago to address specifically this.
FYI newer servlet API and spring versions provide mechanisms to plug in servlets and filters that I think we should use instead of the module engine.
Thanks for the detail @wyclif. The issue indeed boils down to failing to provide servlet init params and not being able to take advantage of hooking into the ServletContext initialisation lifecycle when starting for the first time.
Does this happen only when these two modules are used together? In other words, can i assume that if I use any of the two modules then i do not get any errors?
TRUNK-6169 happens only when OpenMRS is started from the InitializationFilter or the UpdateFilter. For other times where things work well, OpenMRS is started from our servlet context Listener. And the reason why it works from our servlet context listener is simply because by then, the web application initialisation process, in the context of the servlet container, is just starting and hence, still allowed to dynamically register filters and servlets. When setting up a new instance of OpenMRS or when doing an upgrade, our listener does not start OpenMRS but instead just sets a flag that we need to run the set up wizard. This then triggers the InitializationFilter or UpdateFilter as needed, when anyone tries to access OpenMRS.
Based on this, it will be easier for us to just do TRUNK-4673 by adding support for init parameters for servlets such that devs can continue using our custom servlet registration mechanism.
The two modules do co-exist safely when Data Filter is inactive at the moment. Something was fixed instead, when the above issue was observed IIRC. Otherwise the idea of having to restart manually for some servlets to register, is relatively a tricky work-around for (I can think of) CI processes for example.
TRUNK-6169 needs TRUNK-4673. I’m tempted to think around having a programatic way to restart the web app during the first start and upgrade processes, a good way to have a consistently running system (If this option can be thought through).
There is also another ticket that resolves a related issue with the FHIR 2 module, TRUNK-6054, I’m just highlighting it for the record since it affects OAuth 2 Login module positively.
@mksd, I mean basically having Data Filter load successfully but not doing its job - intercepting any request(not configured to do so, I’ve not tried it when configured to intercept requests). The fix should have been part of the internal OZ-66 ticket.
Ultimately, I don’t think this is easily achievable. Fundamentally, the problem is that there is no API provided in the servlet spec to allow a web app to request the container restart itself. Spring Boot solves this by having an application that wraps the servlet container and then having per-container logic to handle Tomcat, Jetty, etc. What it basically boils down to is that there’s no object we can easily get a handle to that would allow us to tell Tomcat to restart the OpenMRS webapp. If you figure out a way, though, it’d probably be useful.
For now, however, I’ve inclined to revive TRUNK-4673 and do away with the notion of using the dynamic API to provide servlets, because that’s really only workable in core.
EDIT: There may be someway to do this with JMX, though.