Configurando Datasource de Oracle en Tomcat para Spring-Boot por JNDI

En alguna ocasión nos encontramos con un datasource configurado a nivel de contenedor y no de forma "interna" a nuestra aplicación. En esto casos al "recurso" hay que acceder por JNDI, (Java Naming and Directory Interface) en vez de crearlo nosotros.
Por crear un caso un poco más complejo voy a detallar la configuración (un de las maneras) para Spring-boot. ¿Por qué es un poco más complejo? Por que en la fase de desarrollo se suele desplegar sobre un tomcat embebido y dicho contenedor tiene que crear el mismo datasource que proporcionará el tomcat de producción.
Entorno: Eclipse Mars, OracleXE 11G, Spring-Boot 1.2.6, Maven 3.3, JVM 1.7 (jdk), Tomcat 7.
Para configurar un datasource como recurso del tomcat hay que editar el archivo "context.xml" de la carpeta conf del tomcat:
Ejemplo del context.xml:
<?xml version='1.0' encoding='utf-8'?>
<Context>
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<Resource name="jdbc/myDataSource" auth="Container" type="javax.sql.DataSource"
     driverClassName="oracle.jdbc.OracleDriver" url="jdbc:oracle:thin:@192.168.150.120:1521:xe"
     username="andreshp" password="andreshp" maxActive="100" maxIdle="30"
     maxWait="10000" />
</Context>

Los datos releventas para lo que nos ocupa es la cadena de conexión, el nombre de usuario y la contraseña y el nombre del "datasource".
Además de la configuración de dicho archivo es necesario incluir en la carpeta lib del tomcat el archivo jar con el driver de oracle.
Una vez arranquemos el tomcat ya está disponible para recuperarlo por jndi.
¿Como le indicamos a nuestra aplicación Spring-boot que utilice dicho recurso?
En nuestra clase de configuración principal, la anotada con "@SpringBootApplication" , debemos instanciar el datasource:
@Bean (destroyMethod="")
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();
}
Eso es todo, debe recuperar el recurso con el mismo nombre que le hemos dado al configurarlo en el tomcat.
Para producción eso ya funcionaría, cierto. Pero para desarrollo es normal lanzar la aplicación con "mvn spring-boot:run" y el tomcat embebido no tiene configurado un recurso.
Spring-boot utiliza "factories" para crear lo que necesitamos y tenemos que sobre escribir el comportamiento "básico".
En la misma clase del anterior bean añadimos este otro bean:
@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(OracleDataSource.class.getName());
resource.setAuth("Container");
resource.setType(DataSource.class.getName());
resource.setProperty("driverClassName",
"oracle.jdbc.OracleDriver");
resource.setProperty("username", "andreshp");
resource.setProperty("password", "andreshp");
resource.setProperty("url",
"jdbc:oracle:thin:@192.168.1.104:1521:xe");
context.getNamingResources().addResource(resource);
}
};
}

Que hace un override de algunos métodos para que se cree el recurso en el tomcat embebido. Es la misma configuración que hacíamos sobre el tomcat, para que "exista" en el de desarrollo.
Para que termine de funcionar se necesitan añadir una dependencia más con el scope de provided. el pom.xml completo es este:

 <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.5.RELEASE</version>
</parent>
<artifactId>cerdaadmin</artifactId>
<groupId>es.sinjava</groupId>
<name>admin</name>
<description>Backend Administracion Module</description>
<version>1.0</version>

<url>http://sinjava.blogspot.com</url>
<organization>
<name>SinJava Labs</name>
<url>http://sinjava.blogspot.com</url>
</organization>

<properties>
<java.version>1.7</java.version>
<tomcat.version>7.0.59</tomcat.version>
<main.basedir>${basedir}/../..</main.basedir>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>${tomcat.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>oracle.driver</groupId>
<artifactId>11G</artifactId>
<version>11</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>admin</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>

Por si me he dejado algo pongo el proyecto restdatasource  completo en https://github.com/andgau/sinjava.

Obviamente el archivo context.xml de mi tomcat no está en el proyecto.

Si no queremos (o tenemos la obligación de utilizar jndi) podemos crear un datasource en la clase principal de Spring-Boot declarando un bean:

@Bean
DataSource dataSource() throws SQLException {
  OracleDataSource dataSource = new OracleDataSource();
  dataSource.setUser(username);
  dataSource.setPassword(password);
  dataSource.setURL(url);
  dataSource.setImplicitCachingEnabled(true);
  dataSource.setFastConnectionFailoverEnabled(true);
   return dataSource;
}
Aparte de alguna forma más que hay de hacerlo con spring-boot.

Comentarios