How to create JNDI context in Spring Boot with Embedded Tomcat Container
Asked Answered
R

8

60
import org.apache.catalina.Context;
import org.apache.catalina.deploy.ContextResource;
import org.apache.catalina.startup.Tomcat;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

@Configuration
@EnableAutoConfiguration
@ComponentScan
@ImportResource("classpath:applicationContext.xml")
public class Application {

    public static void main(String[] args) throws Exception {
        new SpringApplicationBuilder()
                .showBanner(false)
                .sources(Application.class)
                .run(args);
}

@Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
    return new TomcatEmbeddedServletContainerFactory() {
        @Override
        protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
                Tomcat tomcat) {
            tomcat.enableNaming();
            return super.getTomcatEmbeddedServletContainer(tomcat);
        }
    };
}

@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {
    return new EmbeddedServletContainerCustomizer() {
        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
            if (container instanceof TomcatEmbeddedServletContainerFactory) {
                TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory = (TomcatEmbeddedServletContainerFactory) container;
                tomcatEmbeddedServletContainerFactory.addContextCustomizers(new TomcatContextCustomizer() {
                    @Override
                    public void customize(Context context) {
                        ContextResource mydatasource = new ContextResource();
                        mydatasource.setName("jdbc/mydatasource");
                        mydatasource.setAuth("Container");
                        mydatasource.setType("javax.sql.DataSource");
                        mydatasource.setScope("Sharable");
                        mydatasource.setProperty("driverClassName", "oracle.jdbc.driver.OracleDriver");
                        mydatasource.setProperty("url", "jdbc:oracle:thin:@mydomain.com:1522:myid");
                        mydatasource.setProperty("username", "myusername");
                        mydatasource.setProperty("password", "mypassword");

                        context.getNamingResources().addResource(mydatasource);

                    }
                });
            }
        }
    };
}

}

I'm using spring boot and trying to startup with an embedded tomcat that creates a JNDI context for my datasources:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
        <version>1.1.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <version>1.1.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-oracle</artifactId>
        <version>1.0.0.RELEASE</version>
    </dependency>

If I remove the @ImportResource my application starts up just fine. I can connect to the tomcat instance. I can check all of my actuator endpoints. Using JConsole, I can connect to the application I can see my datasource in the MBeans (Catalina -> Resource -> Context -> "/" -> localhost -> javax.sql.DataSource -> jdbc/mydatasource)

I also have MBeans showing up, via JConsole, here (Tomcat -> DataSource -> / -> localhost -> javax.sql.DataSource -> jdbc/mydatasource)

However, when I @ImportResource what is actually looking for mydatasource via JNDI, it's not finding it.

<bean id="myDS" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/mydatasource"/>
</bean>

The relevant part of my imported xml file

The ContextResource that I'm configuring above is with the exact same parameters that I was using in the context.xml that is getting deployed when the application is deployed to a tomcat container. My imported beans and my application are working properly when deployed to a tomcat container.

So it appears that I have a context now, but it doesn't appear that the naming is right. I've tried to various combinations of the resource name, but can't seem to generate a "comp" bound in this context.

Caused by: javax.naming.NameNotFoundException: Name [comp/env/jdbc/mydatasource] is not bound in this Context. Unable to find [comp].
    at org.apache.naming.NamingContext.lookup(NamingContext.java:819)
    at org.apache.naming.NamingContext.lookup(NamingContext.java:167)
    at org.apache.naming.SelectorContext.lookup(SelectorContext.java:156)
    at javax.naming.InitialContext.lookup(InitialContext.java:392)
    at org.springframework.jndi.JndiTemplate$1.doInContext(JndiTemplate.java:155)
    at org.springframework.jndi.JndiTemplate.execute(JndiTemplate.java:87)
    at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:152)
    at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:179)
    at org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:95)
    at org.springframework.jndi.JndiObjectLocator.lookup(JndiObjectLocator.java:106)
    at org.springframework.jndi.JndiObjectFactoryBean.lookupWithFallback(JndiObjectFactoryBean.java:231)
    at org.springframework.jndi.JndiObjectFactoryBean.afterPropertiesSet(JndiObjectFactoryBean.java:217)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1612)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1549)
    ... 30 more
Rabbit answered 24/7, 2014 at 18:57 Comment(0)
G
68

By default, JNDI is disabled in embedded Tomcat which is causing the NoInitialContextException. You need to call Tomcat.enableNaming() to enable it. The easiest way to do that is with a TomcatEmbeddedServletContainer subclass:

@Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
    return new TomcatEmbeddedServletContainerFactory() {

        @Override
        protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
                Tomcat tomcat) {
            tomcat.enableNaming();
            return super.getTomcatEmbeddedServletContainer(tomcat);
        }
    };
}

If you take this approach, you can also register the DataSource in JNDI by overriding the postProcessContext method in your TomcatEmbeddedServletContainerFactory subclass.

context.getNamingResources().addResource adds the resource to the java:comp/env context so the resource's name should be jdbc/mydatasource not java:comp/env/mydatasource.

Tomcat uses the thread context class loader to determine which JNDI context a lookup should be performed against. You're binding the resource into the web app's JNDI context so you need to ensure that the lookup is performed when the web app's class loader is the thread context class loader. You should be able to achieve this by setting lookupOnStartup to false on the jndiObjectFactoryBean. You'll also need to set expectedType to javax.sql.DataSource:

<bean class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/mydatasource"/>
    <property name="expectedType" value="javax.sql.DataSource"/>
    <property name="lookupOnStartup" value="false"/>
</bean>

This will create a proxy for the DataSource with the actual JNDI lookup being performed on first use rather than during application context startup.

The approach described above is illustrated in this Spring Boot sample.

Guava answered 24/7, 2014 at 21:55 Comment(11)
I think your tip got me closer. I'm still unable to find my resources even though they are showing up in JConsole.Rabbit
I've updated my answer with some more details. Note that you were using the wrong name to bind the resource into JNDI. I've also shown how you can call tomcat.enableNaming() rather than copying the code over.Guava
Adding the TomccatEmbeddedServletContainer bean puts "DataSource" MBeans visible via JConsole, that is great. I now getting a context, but the [comp] is not found.Rabbit
Adding the JndiObjectFactoryBean puts the above code into a stack overflow state.Rabbit
Thanks for all of the help!Rabbit
I am having a hard time implementing this, at least when using Graisl 3.0. Is there an updated way to perform this? The way you guys outline no longer seems to workConvexity
Is the JNDI context create here usable throughout the JVM, even by objects not instantiated by Spring? I'm getting a NameNotFound with the detail, "Unable to find[java:comp]"Thiol
@KCBaltz As I said in my answer, "Tomcat uses the thread context class loader to determine which JNDI context a lookup should be performed against". A JNDI lookup will only work when the TCCL is Tomcat's web app class loader.Guava
To answer my own question, it is usable throughout the JVM if you use the "hack" described here: #27823119Thiol
Instead of defining the DataSource in the postProcessContext method, is it possible to use the one Spring generates based on the spring.datasource.* properties in application.properties?Thiol
this doesn't work for me, I get javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial I have my jndi resources defined as beans in other files.Lynx
G
31

I recently had the requirement to use JNDI with an embedded Tomcat in Spring Boot.
Actual answers give some interesting hints to solve my task but it was not enough as probably not updated for Spring Boot 2.

Here is my contribution tested with Spring Boot 2.0.3.RELEASE.

Specifying a datasource available in the classpath at runtime

You have multiple choices:

  • using the DBCP 2 datasource (you don't want to use DBCP 1 that is outdated and less efficient).
  • using the Tomcat JDBC datasource.
  • using any other datasource: for example HikariCP.

If you don't specify anyone of them, with the default configuration the instantiation of the datasource will throw an exception:

Caused by: javax.naming.NamingException: Could not create resource factory instance
    at org.apache.naming.factory.ResourceFactory.getDefaultFactory(ResourceFactory.java:50)
    at org.apache.naming.factory.FactoryBase.getObjectInstance(FactoryBase.java:90)
    at javax.naming.spi.NamingManager.getObjectInstance(NamingManager.java:321)
    at org.apache.naming.NamingContext.lookup(NamingContext.java:839)
    at org.apache.naming.NamingContext.lookup(NamingContext.java:159)
    at org.apache.naming.NamingContext.lookup(NamingContext.java:827)
    at org.apache.naming.NamingContext.lookup(NamingContext.java:159)
    at org.apache.naming.NamingContext.lookup(NamingContext.java:827)
    at org.apache.naming.NamingContext.lookup(NamingContext.java:159)
    at org.apache.naming.NamingContext.lookup(NamingContext.java:827)
    at org.apache.naming.NamingContext.lookup(NamingContext.java:173)
    at org.apache.naming.SelectorContext.lookup(SelectorContext.java:163)
    at javax.naming.InitialContext.lookup(InitialContext.java:417)
    at org.springframework.jndi.JndiTemplate.lambda$lookup$0(JndiTemplate.java:156)
    at org.springframework.jndi.JndiTemplate.execute(JndiTemplate.java:91)
    at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:156)
    at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:178)
    at org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:96)
    at org.springframework.jndi.JndiObjectLocator.lookup(JndiObjectLocator.java:114)
    at org.springframework.jndi.JndiObjectTargetSource.getTarget(JndiObjectTargetSource.java:140)
    ... 39 common frames omitted
Caused by: java.lang.ClassNotFoundException: org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:264)
    at org.apache.naming.factory.ResourceFactory.getDefaultFactory(ResourceFactory.java:47)
    ... 58 common frames omitted
  • To use Apache JDBC datasource, you don't need to add any dependency but you have to change the default factory class to org.apache.tomcat.jdbc.pool.DataSourceFactory.
    You can do it in the resource declaration: resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory"); I will explain below where to add this line.

  • To use DBCP 2 datasource, a dependency is required:

<dependency>
  <groupId>org.apache.tomcat</groupId>
  <artifactId>tomcat-dbcp</artifactId>
  <version>8.5.4</version>
</dependency>

Of course, adapt the artifact version according to your Spring Boot Tomcat embedded version.

  • To use HikariCP, add the required dependency if not already present in your configuration (it may be if you rely on persistence starters of Spring Boot) such as:
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>3.1.0</version>
</dependency>

and specify the factory that goes with in the resource declaration:

resource.setProperty("factory", "com.zaxxer.hikari.HikariJNDIFactory");

Datasource configuration/declaration

You have to customize the bean that creates the TomcatServletWebServerFactory instance.
Two things to do:

  • enabling the JNDI naming which is disabled by default

  • creating and add the JNDI resource(s) in the server context

For example with PostgreSQL and a DBCP 2 datasource, do that:

@Bean
public TomcatServletWebServerFactory tomcatFactory() {
    return new TomcatServletWebServerFactory() {
        @Override
        protected TomcatWebServer getTomcatWebServer(org.apache.catalina.startup.Tomcat tomcat) {
            tomcat.enableNaming(); 
            return super.getTomcatWebServer(tomcat);
        }

        @Override 
        protected void postProcessContext(Context context) {

            // context
            ContextResource resource = new ContextResource();
            resource.setName("jdbc/myJndiResource");
            resource.setType(DataSource.class.getName());
            resource.setProperty("driverClassName", "org.postgresql.Driver");

            resource.setProperty("url", "jdbc:postgresql://hostname:port/dbname");
            resource.setProperty("username", "username");
            resource.setProperty("password", "password");
            context.getNamingResources()
                   .addResource(resource);            
        }
    };
}

Here the variants for Tomcat JDBC and HikariCP datasource.

In postProcessContext() set the factory property as explained early for Tomcat JDBC ds:

@Override 
protected void postProcessContext(Context context) {
    ContextResource resource = new ContextResource();        
    //...
    resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory");
    //...
    context.getNamingResources()
           .addResource(resource);            
}

and for HikariCP:

@Override 
protected void postProcessContext(Context context) {
    ContextResource resource = new ContextResource();        
    //...
    resource.setProperty("factory", "com.zaxxer.hikari.HikariJNDIFactory");
    //...
    context.getNamingResources()
           .addResource(resource);            
}

Using/Injecting the datasource

You should now be able to lookup the JNDI ressource anywhere by using a standard InitialContext instance:

InitialContext initialContext = new InitialContext();
DataSource datasource = (DataSource) initialContext.lookup("java:comp/env/jdbc/myJndiResource");

You can also use JndiObjectFactoryBean of Spring to lookup up the resource:

JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setJndiName("java:comp/env/jdbc/myJndiResource");
bean.afterPropertiesSet();
DataSource object = (DataSource) bean.getObject();

To take advantage of the DI container you can also make the DataSource a Spring bean:

@Bean(destroyMethod = "")
public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
    JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
    bean.setJndiName("java:comp/env/jdbc/myJndiResource");
    bean.afterPropertiesSet();
    return (DataSource) bean.getObject();
}

And so you can now inject the DataSource in any Spring beans such as:

@Autowired
private DataSource jndiDataSource;

Note that many examples on the internet seem to disable the lookup of the JNDI resource on startup:

bean.setJndiName("java:comp/env/jdbc/myJndiResource");
bean.setProxyInterface(DataSource.class);
bean.setLookupOnStartup(false);
bean.afterPropertiesSet(); 

But I think that it is helpless as it invokes just after afterPropertiesSet() that does the lookup !

Goy answered 9/7, 2018 at 15:14 Comment(4)
First off, thank you so much. This is not only a great description of how to get JNDI working w/spring boot 2's embedded tomcat - it is also one of the only resources like it I found. x100 thank you. A note - I needed the setProxyInterface/setLookupOnState calls to avoid InstanceAlreadyExistsException exceptions.Lucero
@Lucero Thanks for your good words. I have to take about 1 whole day to make it working correctly : generally legacy workarounds are not well documented... But very glad that this post can help others with this uncommon requirement. About your last point, it is interesting, I didn't get this exception by removing these two statements.Goy
For me it doesn't work with Hikari, I had to change the factory line to resource.setProperty("factory", "com.zaxxer.hikari.HikariJNDIFactory"); (instead of HikariDataSource)Benny
Thanks for the great detailed explanation of everything. This can be converted into a blog as the problem is very generic and none of the other answers helped me. Kudos to you!Dygall
C
15

After all i got the answer thanks to wikisona, first the beans:

@Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
    return new TomcatEmbeddedServletContainerFactory() {

        @Override
        protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
                Tomcat tomcat) {
            tomcat.enableNaming();
            return super.getTomcatEmbeddedServletContainer(tomcat);
        }

        @Override
        protected void postProcessContext(Context context) {
            ContextResource resource = new ContextResource();
            resource.setName("jdbc/myDataSource");
            resource.setType(DataSource.class.getName());
            resource.setProperty("driverClassName", "your.db.Driver");
            resource.setProperty("url", "jdbc:yourDb");

            context.getNamingResources().addResource(resource);
        }
    };
}

@Bean(destroyMethod="")
public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
    JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
    bean.setJndiName("java:comp/env/jdbc/myDataSource");
    bean.setProxyInterface(DataSource.class);
    bean.setLookupOnStartup(false);
    bean.afterPropertiesSet();
    return (DataSource)bean.getObject();
}

the full code it's here: https://github.com/wilkinsona/spring-boot-sample-tomcat-jndi

Crural answered 23/9, 2014 at 22:31 Comment(3)
I am getting java.lang.ClassNotFoundException: org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory when try to use above code. Everything works fine if I define tomcat db pools via application.properties. I have used spring boot dependencies but looks I am missing something for JNDI. Its for embedded tomcat version 8.5.x.Swart
I solved issue by adding line , resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory"); . My SO Question or I need to add separate dbcp dependency but that doesn't look a good idea with spring boot.Swart
@Sabir Khan Using the Tomcat jdbc DS is a fair possibility. But I don't think that using another DS is wrong. Spring Boot didn't cover this part in a out of the box way as not at all standard in Spring Boot to use directly JNDI but sometimes we don't want to be limited to the Tomcat jdbc DS or don't want to use it.Goy
B
6

In SpringBoot 2.1, I found another solution. Extend standard factory class method getTomcatWebServer. And then return it as a bean from anywhere.

public class CustomTomcatServletWebServerFactory extends TomcatServletWebServerFactory {

    @Override
    protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
        System.setProperty("catalina.useNaming", "true");
        tomcat.enableNaming();
        return new TomcatWebServer(tomcat, getPort() >= 0);
    }
}

@Component
public class TomcatConfiguration {
    @Bean
    public ConfigurableServletWebServerFactory webServerFactory() {
        TomcatServletWebServerFactory factory = new CustomTomcatServletWebServerFactory();

        return factory;
    }

Loading resources from context.xml doesn't work though. Will try to find out.

Bootless answered 24/11, 2018 at 11:48 Comment(0)
I
2

Please note instead of

public TomcatEmbeddedServletContainerFactory tomcatFactory()

I had to use the following method signature

public EmbeddedServletContainerFactory embeddedServletContainerFactory() 
Indoxyl answered 22/10, 2015 at 9:16 Comment(0)
H
2

In Spring boot v3 it seems that the previous solutions are no more possible.

With Spring documentation I came with this approach:

  1. Create a WebServerFactoryCustomizer and create your jndi resource

  2. Add in a lifecycle listener to enable naming

  3. Add tomcat jdbc dependency

Customizer:

@Component
public class MyDatasourceJndiCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

  @Value("${mydatasource.jndi.name}")
  private String jndiName;

  @Value("${mydatasource.jndi.driver-class-name}")
  private String driverClassName;

  @Value("${mydatasource.jndi.url}")
  private String url;

  @Value("${mydatasource.username}")
  private String username;

  @Value("${jndi.password}")
  private String password;

  @Override
  public void customize(TomcatServletWebServerFactory server) {
    server.addContextCustomizers(new TomcatContextCustomizer() {
      @Override
      public void customize(Context context) {
        ContextResource resource = new ContextResource();
        resource.setName(jndiName);
        resource.setType(DataSource.class.getName());
        resource.setProperty("driverClassName", driverClassName);
        resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory");
        resource.setProperty("url", url);
        resource.setProperty("username", username);
        resource.setProperty("password", password);
        context.getNamingResources()
          .addResource(resource);
      }

    });

    enableNaming(server);
  }


  private static void enableNaming(TomcatServletWebServerFactory server) {
    server.addContextLifecycleListeners(new NamingContextListener());
    
    // The following code is copied from Tomcat 
    System.setProperty("catalina.useNaming", "true");
    String value = "org.apache.naming";
    String oldValue = System.getProperty("java.naming.factory.url.pkgs");
    if (oldValue != null) {
      if (oldValue.contains(value)) {
        value = oldValue;
      } else {
        value = value + ":" + oldValue;
      }
    }

    System.setProperty("java.naming.factory.url.pkgs", value);
    value = System.getProperty("java.naming.factory.initial");
    if (value == null) {
      System.setProperty("java.naming.factory.initial", "org.apache.naming.java.javaURLContextFactory");
    }
  }

}

Dependency:

    <dependency>
      <groupId>org.apache.tomcat</groupId>
      <artifactId>tomcat-jdbc</artifactId>
      <version>10.1.9</version>
    </dependency>
Higgledypiggledy answered 1/6, 2023 at 19:55 Comment(0)
A
1

Have you tried @Lazy loading the datasource? Because you're initialising your embedded Tomcat container within the Spring context, you have to delay the initialisation of your DataSource (until the JNDI vars have been setup).

N.B. I haven't had a chance to test this code yet!

@Lazy
@Bean(destroyMethod="")
public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
    JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
    bean.setJndiName("java:comp/env/jdbc/myDataSource");
    bean.setProxyInterface(DataSource.class);
    //bean.setLookupOnStartup(false);
    bean.afterPropertiesSet();
    return (DataSource)bean.getObject();
}

You may also need to add the @Lazy annotation wherever the DataSource is being used. e.g.

@Lazy
@Autowired
private DataSource dataSource;
Amati answered 4/5, 2016 at 7:3 Comment(1)
it worked for my case.Wadlinger
S
0

Had to solve this problem from scratch, because none of the examples that I came across worked for me. Looks like it depends on the configuration of the embedded application...

Goal: legacy application as war file with JNDI resource, run with embedded server.

The snippet below works for both Spring Boot 2 and 3, but be careful because Spring Boot 3 uses Jakarta EE 9 specification (has new top-level jakarta package), which may not be compatible with your application (like in my case).

Main pain points marked with //important:

@Bean
public TomcatServletWebServerFactory servletContainerFactory() {
    return new TomcatServletWebServerFactory() {
        @Override
        protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
            //add webapp
            try {
                //...app preparation here
                final Context context = tomcat.addWebapp(contextPath, application.getURL());
                context.setParentClassLoader(getClass().getClassLoader()); //important: helps the embedded app reach spring boot dependencies
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            //configure jndi
            tomcat.enableNaming(); //important: speaks for itself
            ContextResource resource = new ContextResource();
            resource.setType(DataSource.class.getName());
            resource.setName("jdbc/JNDI_NAME_HERE");
            resource.setProperty("factory", HikariJNDIFactory.class.getName());
            resource.setProperty("jdbcUrl", getUrl());
            resource.setProperty("driverClassName", getDriverClassName());
            tomcat.getServer().getGlobalNamingResources().addResource(resource); //important: solution for successful jndi lookup

            return super.getTomcatWebServer(tomcat);
        }
    };
}

Only 3 steps in total and only 2 for jndi specifically.

Stereogram answered 1/8, 2023 at 15:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.