0

At our project we experience, that our default Hibernate session seems to be lost, when adding another existing war file, to the embedded tomcat container in Grails/Spring boot.

As soon as we comment the statement, to add the war file, everything works as expected. When we deploy the application to production, this problem is not existing, anyway the war file we try to add at development, also starts up at production.

We are using Grails 3.3.1, that's build up upon Spring boot, and add the existing war file to the emnbedded tomcat this way, through the Applications.groovy file.

class Application extends GrailsAutoConfiguration implements EnvironmentAware {
static void main(String[] args) {
    GrailsApp.run(Application, args)
}

@Bean
EmbeddedServletContainerFactory servletContainerFactory() {
     return new TomcatEmbeddedServletContainerFactory() {

        @Override
        protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {
              try {
                    // Ensure that the folder exists
                    tomcat.getHost().getAppBaseFile().mkdir()
                    String projektrod = System.getProperty('user.dir')
                    // Add external Orbeon war file
                    Context context = tomcat.addWebapp("/blanketdesigner/orbeon", "${projektrod}\\..\\blanket-orbeon-plugin\\orbeon\\orbeon.war")
                    context.setParentClassLoader(getClass().getClassLoader())
              } catch (ServletException ex) {
                    throw new IllegalStateException("Failed to add webapp", ex)
              }
              return super.getTomcatEmbeddedServletContainer(tomcat)
        }
    }
}

When the above is set, both the application, and the external war file is starting up, and works as expected, but we have to call .withTransaction, .withNewTransaction, .withNewSession or something like that on domain objects.

This is also happening for services, that is annotated with @Transactional. If we are not doing this, we experience the following exception:

No Session found for current thread. Stacktrace follows:
java.lang.reflect.InvocationTargetException: null
    at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:211)
    at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:188)
    at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:90)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
    at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55)
    at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:77)
    at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:67)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: org.hibernate.HibernateException: No Session found for current thread
    at org.grails.orm.hibernate.GrailsSessionContext.currentSession(GrailsSessionContext.java:117)
    at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:688)
    at org.grails.orm.hibernate.HibernateSession.createQuery(HibernateSession.java:177)
    at org.grails.orm.hibernate.HibernateSession.createQuery(HibernateSession.java:170)
    at org.grails.datastore.gorm.finders.FindAllByFinder.buildQuery(FindAllByFinder.java:63)

And if we remove the line that adds the war file

 tomcat.addWebapp("....")

Everything works as expected, and we can work with domain objects without have to call .with...

As mentioned earlier, this problem seems to be a problem with the embedded tomcat server, and the function to add a war file, on beside.

We do not want to call .with... all over the code, as these is not the right solution.

Why we have to add the war file to the embedded tomcat at development, is because our main application depends on the other war for all function to work. On the production server it is running as a separate web-app too.

We can live with a fix to attach a default hibernate session when environment is development, but I cant figure out how to do this.

Thinking of something like this

if(grails.util.Environment.current == grails.util.Environment.DEVELOPMENT) {
    // Then bind new default session to current thread 
}

It should be easy to reproduce.

Will appreciate any help or suggestion

Thanks

  • Have you tried debugging? Seems to me that Grails fails to get a session to your database. – Wesley De Keirsmaeker Nov 10 '17 at 17:02
  • Hi Wesley, I think you are right. I have tried running (grails.util.Holders.applicationContext.getBean("sessionFactory") as org.hibernate.SessionFactory).currentSession This gives me the same exception: No Session found for current thread Can you be more specific how I can debug this? Or something I can try to find for you to lead me in the right directions? Thanks. – Daniel Knudsen Nov 10 '17 at 17:18

1 Answers1

1

@WesleyDeKeirsmaeker, by debugging further, I found out that the whole Spring Boot environment is started twice, when I add a external war file by calling ".addWebapp(...)".

These means the following is set twice:

public GrailsSessionContext(SessionFactoryImplementor sessionFactory) {
    this.sessionFactory = sessionFactory;
}

When Grails calls getCurrentSession(), the above seems to causing the problem, because it's set by another thread.

When working with domain objects, the TransactionSynchronizationManager.getResource(sessionFactory); returns null.

public Session currentSession() throws HibernateException {
    Object value = TransactionSynchronizationManager.getResource(sessionFactory);
    if (value instanceof Session) {
        return (Session) value;
    }

    if (value instanceof SessionHolder) {
        SessionHolder sessionHolder = (SessionHolder) value;
        Session session = sessionHolder.getSession();
        if (TransactionSynchronizationManager.isSynchronizationActive() && !sessionHolder.isSynchronizedWithTransaction()) {
            TransactionSynchronizationManager.registerSynchronization(createSpringSessionSynchronization(sessionHolder));
            sessionHolder.setSynchronizedWithTransaction(true);
            // Switch to FlushMode.AUTO, as we have to assume a thread-bound Session
            // with FlushMode.MANUAL, which needs to allow flushing within the transaction.
            FlushMode flushMode = HibernateVersionSupport.getFlushMode(session);
            if (flushMode.equals(FlushMode.MANUAL) && !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
                HibernateVersionSupport.setFlushMode(session, FlushMode.AUTO);
                sessionHolder.setPreviousFlushMode(flushMode);
            }
        }
        return session;
    }

    if (jtaSessionContext != null) {
        Session session = jtaSessionContext.currentSession();
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronizationManager.registerSynchronization(createSpringFlushSynchronization(session));
        }
        return session;
    }

    if (allowCreate) {
        // be consistent with older HibernateTemplate behavior
        return createSession(value);
    }

    throw new HibernateException("No Session found for current thread");
} 

I'm thinking if there is a way, to pass args to the child container that's starts my external war file. If i'm able to pass arguments, possible I will be able to disable database related beans/auto configurations at startup, and the GrailsSessionContext will not be called twice.

Found arguments here Disable related auto configurations.

Is there a way to pass arguments to child container? I'm looking for a method to hook into or something.