Search code examples
javamavenspring-bootspring-datajndi

Run a Spring Boot app with embedded container and JNDI


I have a Spring Boot app with a spring.datasource.jndi-name=java:/foo property and it works well under WildFly.

I'd like to run the same app with an embedded container, i.e. mvn spring-boot:run, but while WildFly has the JNDI datasource configured in its configuration, an embedded container does not i.e. I' getting:

org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException:
Failed to look up JNDI DataSource with name 'java:/foo'

I suppose I have to include an XML file somewhere to configure the JNDI datasource for the embedded container, but I couldn't find documentation about this. I've just found tutorials on how to create the JNDI datasource in the Java source code, but I'd like to avoid this so that same app can run within external and embedded container both.

How can I achieve this?

EDIT This answer shows how to create the JNDI context in Tomcat in a way that would break running the same app in other containers (e.g. WildFly). I'm looking for an answer which lets the app run with the same sources in different containers, e.g. just configuring the embedded container with the same JNDI resources configured in WildFly.


Solution

  • To make the app also deployable in other jndi enabled containers do the following;

    1. Extend TomcatEmbeddedServletContainerFactory and enable the jndi naming and add the resource
    2. Create a configuration class with Profile annotation which exposes the extended TomcatEmbeddedServletContainerFactory bean

    See the code below;

    Extending TomcatEmbeddedServletContainerFactory

    class EmbeddedServletContainerFactory extends TomcatEmbeddedServletContainerFactory {
        @Override
        protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {
            tomcat.enableNaming(); // This is essential. Naming is disabled by default which needs enabling
            return super.getTomcatEmbeddedServletContainer(tomcat);
        }
    
        @Override
        protected void postProcessContext(Context context) {
            ContextResource resource = new ContextResource();
            // All the below properties you can retrieve via preferred method
            resource.setName("jdbc/test");
            resource.setAuth("Container");
            resource.setType(DataSource.class.getName());
            resource.setProperty("driverClassName", driverClass);
            resource.setProperty("factory", "org.apache.commons.dbcp2.BasicDataSourceFactory");
            resource.setProperty("url", dbUrl);
            resource.setProperty("username", username);
            resource.setProperty("password", password);
            context.getNamingResources().addResource(resource);
        }
    }
    

    Config class exposing the bean

    @Profile("embedded")
    @Configuration
    public class EmbeddedConfig {
    
        @Bean
        public TomcatEmbeddedServletContainerFactory tomcatFactory() {
            return new EmbeddedServletContainerFactory();
        }
    }
    

    If you don't like this in java config you can do the same in xml way

    <beans profile="embedded">
        <bean id="TomcatEmbeddedServletContainerFactory" class="EmbeddedServletContainerFactory" />
    </bean>
    

    Now you can hardcode the profile name in your pom or add it via jvm arguments;

    mvn spring-boot:run -Drun.profiles=embedded
    

    You other code remains the same and behaves the same in other containers. The lookup of datasource via jndi also remains same. This code ensures that there is actually a datasource bound to that jndi in embedded container.