1

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.

Giovanni Lovato
  • 2,183
  • 2
  • 29
  • 53
  • Possible duplicate of [How to create JNDI context in Spring Boot with Embedded Tomcat Container](https://stackoverflow.com/questions/24941829/how-to-create-jndi-context-in-spring-boot-with-embedded-tomcat-container) – madteapot Jan 04 '18 at 09:21
  • Note that I'm looking for a configuration-only solution, e.g. not involving modify sources (if possible at all). – Giovanni Lovato Jan 04 '18 at 09:24
  • Add `@Profile` annotation to the TomcatEmbeddedServletContainerFactory bean definition and then just enable that profile when running via jvm argument. This ensures that nothing changes when you deploy in other containers. The embedded tomcat runs directly from the tomcat jars at runtime so there isn't any config file for the resources to be defined. – madteapot Jan 04 '18 at 09:37

2 Answers2

3

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.

madteapot
  • 2,208
  • 2
  • 19
  • 32
  • You set `"jdbc/test"` as JNDI name, shouldn't it be e.g. `"java:/jdbc/test"`? I'm asking because my JNDI name is `java:/datasources/xxx` and with your code I'm getting `Name [datasources/xxx] is not bound in this Context. Unable to find [datasources].` – Giovanni Lovato Jan 04 '18 at 10:09
  • That is the name I am exposing the resource with. My lookup code uses spring `JndiDataSourceLookup` class with full jndi name i.e. `java:/comp/env/jdbc/test`. If you don't want to change your lookup jndi name in the application.properties file then you can set the resourceRef to true before the lookup. – madteapot Jan 04 '18 at 10:18
  • I'm not sure where should I set the `resourceRef` property. I do not explicitly do any lookup, it's all done automatically by Spring AFAIK. – Giovanni Lovato Jan 04 '18 at 10:26
  • Upon further testing I think its bit complicated. The way my app works is when exposing the resource I give a name jdbc/test without colon (:) and when looking up the datasource I use the jndi name java:comp/env/jdbc/test or just jdbc/test both of which work fine. However if for test I expose my resource with colon in name i.e. java:/jdbc/test then I HAVE TO prefix my lookup jndi with java:/comp/env i.e. java:/comp/env/java:/jdbc/test. I am unable to find reason behind this currently but will update answer if I do. Your best bet is to us full jndi name for now and see if it works. – madteapot Jan 04 '18 at 10:51
2

@Setu's answer didn't work for me because the TomcatEmbeddedServletContainerFactory is created after the ApplicationContext is refreshed, but I needed JNDI to be available during the ApplicationContext refresh.

Instead, I set the following System properties before starting my Spring Boot app to enable Tomcat Naming:

System.setProperty(Context.URL_PKG_PREFIXES, "org.apache.naming");
System.setProperty(Context.INITIAL_CONTEXT_FACTORY,
                   "org.apache.naming.java.javaURLContextFactory");
Nathan
  • 1,418
  • 16
  • 32