Configure DataSource Using JNDI Using external Tomcat 9 Server: Spring Boot - java

I have a SpringBootApplication, packaged as war file:
#SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
on the application.properties:
spring.datasource.jndi-name=java:comp/env/jdbc/bonanza
but on the logs I see those messages when I deploy the war in the Tomcat 9:
Name [spring.datasource.jndi-name] is not bound in this Context. Unable to find [spring.datasource.jndi-name].. Returning null.
the logs:
12:37:53.989 [main] DEBUG o.springframework.jndi.JndiTemplate - Looking up JNDI object with name [java:comp/env/spring.datasource.jndi-name]
12:37:53.989 [main] DEBUG o.s.jndi.JndiLocatorDelegate - Converted JNDI name [java:comp/env/spring.datasource.jndi-name] not found - trying original name [spring.datasource.jndi-name]. javax.naming.NameNotFoundException: Name [spring.datasource.jndi-name] is not bound in this Context. Unable to find [spring.datasource.jndi-name].
12:37:53.990 [main] DEBUG o.springframework.jndi.JndiTemplate - Looking up JNDI object with name [spring.datasource.jndi-name]
12:37:53.991 [main] DEBUG o.s.jndi.JndiPropertySource - JNDI lookup for name [spring.datasource.jndi-name] threw NamingException with message: Name [spring.datasource.jndi-name] is not bound in this Context. Unable to find [spring.datasource.jndi-name].. Returning null.
12:37:53.995 [main] DEBUG o.springframework.jndi.JndiTemplate - Looking up JNDI object with name [java:comp/env/spring.datasource.jndi-name]
12:37:53.996 [main] DEBUG o.s.jndi.JndiLocatorDelegate - Converted JNDI name [java:comp/env/spring.datasource.jndi-name] not found - trying original name [spring.datasource.jndi-name]. javax.naming.NameNotFoundException: Name [spring.datasource.jndi-name] is not bound in this Context. Unable to find [spring.datasource.jndi-name].
12:37:53.996 [main] DEBUG o.springframework.jndi.JndiTemplate - Looking up JNDI object with name [spring.datasource.jndi-name]
12:37:53.997 [main] DEBUG o.s.jndi.JndiPropertySource - JNDI lookup for name [spring.datasource.jndi-name] threw NamingException with message: Name [spring.datasource.jndi-name] is not bound in this Context. Unable to find [spring.datasource.jndi-name].. Returning null.
12:37:53.998 [main] DEBUG o.s.c.e.PropertySourcesPropertyResolver - Found key 'spring.datasource.jndi-name' in PropertySource 'configurationProperties' with value of type String
on my tomcat9/conf/context.xml:
<Resource name="jdbc/bonanza"
auth="Container"
type="javax.sql.DataSource"
maxTotal="100"
maxIdle="30"
maxWaitMillis="10000"
username="a_usr"
password="Mu*7gydlcdstg100#"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://172.175.77.55:3306/a_db"
/>

As the error suggests, spring boot cannot find the key in the JNDI lookup. JNDI is disabled in Spring boot's embedded Tomcat so you would need to enable it using Tomcat#enableNaming and once that is done you would need to create a lookup entry in JNDI. You can refer to the below code which I copied from one of the spring boot project maintainers repository GitHub repo JNDI-Tomcat
#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/bonanza");
resource.setType(DataSource.class.getName());
resource.setProperty("driverClassName", "your.db.Driver");
resource.setProperty("url", "jdbc:yourDb");
context.getNamingResources().addResource(resource);
}
};
}
[Edit]
As you are not using embedded tomcat server, you can configure JNDI by configuring it using tomcat config files:
In server.xml, create a Resource under <GlobalNamingResources>
<Resource auth="Container" driverClassName="..."
maxActive="..."
maxIdle="..."
maxWait="..."
name="jdbc/bonanza"
username="..."
password="..."
type="..."
url="..."/>
In Context.xml, you can link the resource
<context>
<ResourceLink auth="Container" name="jdbc/bonanza" global="jdbc/bonanza" type="javax.sql.DataSource" />
</context>
Also, make sure you are not starting the application using the spring-boot main method. You need to build the war file using maven/gradle and then deploy it to the tomcat and test it.

Related

Can't config Java Spring Boot data session Mongodb

I've been using this guide to set up spring session data with mongodb
https://docs.spring.io/spring-session-data-mongodb/docs/2.1.1.RELEASE/reference/htmlsingle/#introduction
However I am having problems with configuration. I'm using Mongodb with Spring boot and I'm trying to config my session time and session name for Spring boot web application, but it keeps defaulting to 30 minutes and the collection name in mongodb is still 'sessions'
These are what I have tried:
Added these to application.properties:
server.session.timeout=1
spring.session.mongodb.collection-name=TestSESSIONS
and this
server.servlet.session.timeout=60s
spring.session.mongodb.collection-name=TestSESSIONS
none of those config work
I've looked over this URL for spring common application properties for mongodb but none of it help to config the session time and collection name for mongodb.
After doing hours of research it seems like spring boot uses some kind of autoconfig with this "org.springframework.boot.autoconfigure"
so then I added this in my application.properties
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration
to disable the autoconfigure.
but now it just give me this error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of method mongoSessionRepository in org.springframework.session.data.mongo.config.annotation.web.http.MongoHttpSessionConfiguration required a bean of type 'org.springframework.data.mongodb.core.MongoOperations' that could not be found.
The following candidates were found but could not be injected:
- Bean method 'mongoTemplate' in 'MongoDataAutoConfiguration' not loaded because AnyNestedCondition 0 matched 2 did not; NestedCondition on MongoDataAutoConfiguration.AnyMongoClientAvailable.FallbackClientAvailable #ConditionalOnBean (types: com.mongodb.client.MongoClient; SearchStrategy: all) did not find any beans of type com.mongodb.client.MongoClient; NestedCondition on MongoDataAutoConfiguration.AnyMongoClientAvailable.PreferredClientAvailable #ConditionalOnBean (types: com.mongodb.MongoClient; SearchStrategy: all) did not find any beans of type com.mongodb.MongoClient
Action:
Consider revisiting the entries above or defining a bean of type 'org.springframework.data.mongodb.core.MongoOperations' in your configuration.
this is the #bean from the spring.io guide 'mongoSessionConverter' from above link
this is the java file MongoHttpSessionConfiguration from spring that that's autoconfig by spring; I have tried extending "MongoHttpSessionConfiguration" and overriding the setter methods my self. Such as the "setMaxInactiveIntervalInSeconds" for sessionTime and
"setCollectionName" for mongododb database collection name.
but I've have this error:
Description:
The bean 'mongoSessionRepository', defined in class path resource [com/khatpass/app/config/SessionListenerConfig.class], could not be registered. A bean with that name has already been defined in class path resource [org/springframework/session/data/mongo/config/annotation/web/http/MongoHttpSessionConfiguration.class] and overriding is disabled.
I am stuck on trying to configure spring boot session with Mongodb. The session always defaulting to 30 minutes and the collection name is always 'sessions' in mongodb collections. Not sure how to change that serverSelectionTimeout='30000 ms' and mongodb collections name "sessions" I don't know what to do, need help.
2019-02-24 13:39:54.501 INFO 36113 --- [ main] org.mongodb.driver.cluster : Cluster created with settings {hosts=[localhost:27017], mode=MULTIPLE, requiredClusterType=UNKNOWN, serverSelectionTimeout='30000 ms', maxWaitQueueSize=500}
After doing so much research and then finally, going through the source code, I found the solution:
#EnableMongoHttpSession(maxInactiveIntervalInSeconds = 24 * 60 * 60)
public class SessionConfiguration {}
To override the default collection name, there is another annotation attribute collectionName.
This is working for Spring Boot 2.1.1
After looking over the class MongoOperationsSessionRepository from org.springframework.session.data.mongo it seems like it can't be config through application.properties because the class is using static final values
public static final int DEFAULT_INACTIVE_INTERVAL = 1800;
and
public static final String DEFAULT_COLLECTION_NAME = "sessions";
only way to change the value is intercept the object before it gets saved. No getters or setters for those fields, it can't be change in an easy way, what a joke!

How to configure JDBC Resource in Embedded Tomcat 8?

I need to set up a connection pool for an application that uses an embedded Tomcat 8 application server. Normally, I would configure a new resource in the context.xml file. But of course, such a file does not exist when using the embedded version. The definition of the resource would look like this:
<Context>
<Resource name="jdbc/dbname" auth="Container" type="javax.sql.DataSource" username="username" password="password" driverClassName="org.postgresql.Driver" description="Database" url="jdbc:postgresql://localhost:5432/dbname" maxActive="20" maxIdle="3" />
</Context>
Therefore, there must be another solution for adding resources to a context. Is it possible to add the data source resource directly to the Standardcontext in code? If yes, how? Or how else can this be done when using the embedded version?
You can write your own factory and integrate it into Tomcat, and then configure the use of this factory in the element for the web application.
1. Write A Resource Factory Class
You must write a class that implements the JNDI service provider javax.naming.spi.ObjectFactory interface. Every time your web application calls lookup() on a context entry that is bound to this factory (assuming that the factory is configured with singleton="false"), the getObjectInstance() method is called.
To create a resource factory that knows how to produce MyBean instances, you might create a class like this:
package com.mycompany;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;
public class MyBeanFactory implements ObjectFactory {
public Object getObjectInstance(Object obj,
Name name2, Context nameCtx, Hashtable environment)
throws NamingException {
// Acquire an instance of our specified bean class
MyBean bean = new MyBean();
// Customize the bean properties from our attributes
Reference ref = (Reference) obj;
Enumeration addrs = ref.getAll();
while (addrs.hasMoreElements()) {
RefAddr addr = (RefAddr) addrs.nextElement();
String name = addr.getType();
String value = (String) addr.getContent();
if (name.equals("foo")) {
bean.setFoo(value);
} else if (name.equals("bar")) {
try {
bean.setBar(Integer.parseInt(value));
} catch (NumberFormatException e) {
throw new NamingException("Invalid 'bar' value " + value);
}
}
}
// Return the customized instance
return (bean);
}
}
In this example, we are unconditionally creating a new instance of the com.mycompany.MyBean class, and populating its properties based on the parameters included in the element that configures this factory (see below). You should note that any parameter named factory should be skipped - that parameter is used to specify the name of the factory class itself (in this case, com.mycompany.MyBeanFactory) rather than a property of the bean being configured.
2. Declare Your Resource Requirements
Next, modify your web application deployment descriptor (/WEB-INF/web.xml) to declare the JNDI name under which you will request new instances of this bean. The simplest approach is to use a element, like this:
<resource-env-ref>
<description>
Object factory for MyBean instances.
</description>
<resource-env-ref-name>
bean/MyBeanFactory
</resource-env-ref-name>
<resource-env-ref-type>
com.mycompany.MyBean
</resource-env-ref-type>
</resource-env-ref>
WARNING - Be sure you respect the element ordering that is required by
the DTD for web application deployment descriptors! See the Servlet
Specification for details.
3. Code Your Application's Use Of This Resource
A typical use of this resource environment reference might look like this:
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
MyBean bean = (MyBean) envCtx.lookup("bean/MyBeanFactory");
writer.println("foo = " + bean.getFoo() + ", bar = " +
bean.getBar());
4. Configure Tomcat's Resource Factory
To configure Tomcat's resource factory, add an elements like this to the element for this web application.
<Context ...>
...
<Resource name="bean/MyBeanFactory" auth="Container"
type="com.mycompany.MyBean"
factory="com.mycompany.MyBeanFactory"
singleton="false"
bar="23"/>
...
</Context>
Resource Link:
Adding Custom Resource Factories
How to Configure JNDI DataSource in Tomcat 8 with Java Configuration:
For adding external resource in tomcat 8, you can follow this link: Adding external resources to class-path in Tomcat 8
The question was about embedded tomcat.

TomEE Exception when trying to access datasource from JAX-WS

I have class called ConnectionManager
public class ConnectionManager{
public static getDBConnection()
{
Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/mydb");
return ds.getConnection();
}
}
When it tried to call this class from jsp page, servlet, jax-rs restful service, it just works fine.
However, I go the following exception when i try to call ConnectionManager.getDBConnection() from JAX-WS web service:
Name [comp/env/jdbc/mydb] is not bound in this Context. Unable to find [comp].
Please note that i defined the Resource inside TomEE_HOME/conf/context.xml:
<Resource name="jdbc/mydb" auth="Container" type="javax.sql.DataSource"
username="user" password="pass"
driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver"
url="jdbc:sqlserver://10.x.x.x:xxxx;databaseName=MyDBName"/>
Please advice.

JNDI path Tomcat vs. Jboss

I have DataSource which is configured on Tomcat 6 in context.xml as MyDataSource.
And I'm fetching it the following way:
DataSource dataSource;
try {
dataSource = (DataSource) new InitialContext().lookup("java:comp/env/MyDataSource");
} catch (NamingException e) {
throw new DaoConfigurationException(
"DataSource '" + url + "' is missing in JNDI.", e);
}
Everything works fine. Now I'm exporting this code to Jboss AP 6. and I configured my dataSource and its connection pool as local-tx dataSource under the same name.
When I'm executing the code above, I'm getting NamingException exception. after some investigation I've found that correct way to call my DataSource under Jboss is
dataSource = (DataSource) new InitialContext().lookup("java:/MyDataSource");
Can anybody explain me why should I omit "comp/env" in my JNDI path under Jboss?
The portable approach for defining data sources is to use a resource reference. Resource references enable you to define the JNDI name for your data source, relative to your application naming context (java:comp/env), and then map that logical reference to the physical resource defined in the application server, whose JNDI name is proprietary to the application server vendor. This approach enables your code and assembly to be portable to any compliant application server.
Step 1: Declare and Lookup Resource Reference
Option 1
This can be done by declaring a resource-ref in your web deployment descriptor (WEB-INF/web.xml):
<resource-ref>
<description>My Data Source.</description>
<res-ref-name>jdbc/MyDataSource</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
Within your code, you can then lookup this resource using the JNDI name java:comp/env/jdbc/MyDataSource:
dataSource = (DataSource) new InitialContext().lookup("java:comp/env/jdbc/MyDataSource");
This JNDI name will not change regardless of the server where the application is deployed.
Option 2
Alternatively, starting in Java EE 5 (Servlet 2.5), this can be done even easier within your code using the #Resource annotation. This eliminates the need for configuring the resource-ref in your web deployment descriptor (web.xml) and prevents the need to perform an explicit JNDI lookup:
public class MyServlet extends HttpServlet {
#Resource(name = "jdbc/MyDataSource")
private DataSource dataSource;
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// dataSource may be accessed directly here since the container will automatically
// inject an instance of the data source when the servlet is initialized
}
This approach has the same results as the previous option, but cuts down on the boilerplate code and configuration in your assembly.
Step 2: Map Resource Reference to Data Source
Then, you will need to use your application server's proprietary approach for mapping the resource reference to the physical data source that you created on the server, for example, using JBoss's custom deployment descriptors (WEB-INF/jboss-web.xml):
<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
<resource-ref>
<res-ref-name>jdbc/MyDataSource</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<jndi-name>java:/MyDataSource</jndi-name>
</resource-ref>
</jboss-web>
Or, for example, using Tomcat's context.xml:
<Resource name="jdbc/MyDataSource" . . . />
You can add to your data source definition the 'jndi-name' tag:
jndi-name - the JNDI name under which the DataSource should be bound.
You can find data source documentation on JBoss wiki: ConfigDataSources

Testing ejb with stub and openejb framework

I am trying to test an EJB that have another one injected in it.
For the tests purpose I want to use a stub for the injected EJB. I had use openEJB as framework for the EJB for the testing.
Here is the EJB :
#Stateless
#Local(IService.class)
public class Service implements IService {
#EJB
private IBean bean;
#Override
public String doService(String data) {
return bean.process(data);
}
}
The real injected EJB :
#Stateless
#Local(IBean.class)
public class Bean implements IBean {
private static Logger logger = Logger.getLogger(Bean.class);
#Override
public String process(String data) {
logger.info("Bean processing : " + data);
return "Bean processing : " + data;
}
}
The stub version of the EJB :
#Stateless
#Local(IBean.class)
public class BeanStub implements IBean {
private static Logger logger = Logger.getLogger(BeanStub.class);
#Override
public String process(String data) {
logger.info("Stub processing : " + data);
return "Stub processing : " + data;
}
}
And the JUnit test used :
public class ServiceTest {
private static Logger logger = Logger.getLogger(ServiceTest.class);
private static InitialContext context;
#BeforeClass
public static void setUpBeforeClass() throws Exception {
// openEJB
Properties p = new Properties();
p.put(Context.INITIAL_CONTEXT_FACTORY,"org.apache.openejb.client.LocalInitialContextFactory");
p.put("openejb.altdd.prefix", "stub"); // use specific ejb-jar
p.put("openejb.descriptors.output", "true");
context = new InitialContext(p);
}
#Test
public void testServiceStub() {
try {
IService service = (IService) context.lookup("ServiceStubLocal");
assertNotNull(service);
String msg = service.doService("service");
assertEquals("Stub processing : service", msg);
} catch (NamingException e) {
logger.error(e);
fail(e.getMessage());
}
}
}
I had try to override the use of the real EJB by the stub one, using a specific ejb-jar (I want to use "BeanStub" instead of default "Bean" in my service) :
<ejb-jar>
<enterprise-beans>
<session id="ServiceStub">
<ejb-name>ServiceStub</ejb-name>
<ejb-class>tests.Service</ejb-class>
<ejb-local-ref>
<ejb-ref-name>tests.Service/bean</ejb-ref-name>
<ejb-link>BeanStub</ejb-link>
</ejb-local-ref>
</session>
</enterprise-beans>
</ejb-jar>
Unfortunatly I have a problem the EJB are declared :
Apache OpenEJB 3.1.4 build: 20101112-03:32
http://openejb.apache.org/
17:14:29,225 INFO startup:70 - openejb.home = D:\Workspace_Java\tests\testejb
17:14:29,225 INFO startup:70 - openejb.base = D:\Workspace_Java\tests\testejb
17:14:29,350 INFO config:70 - Configuring Service(id=Default Security Service, type=SecurityService, provider-id=Default Security Service)
17:14:29,350 INFO config:70 - Configuring Service(id=Default Transaction Manager, type=TransactionManager, provider-id=Default Transaction Manager)
17:14:29,381 INFO config:70 - Found EjbModule in classpath: D:\Workspace_Java\tests\testejb\target\test-classes
17:14:29,412 INFO config:70 - Found EjbModule in classpath: D:\Workspace_Java\tests\testejb\target\classes
17:14:29,428 INFO config:70 - Beginning load: D:\Workspace_Java\tests\testejb\target\test-classes
17:14:29,428 INFO config:70 - AltDD ejb-jar.xml -> file:/D:/Workspace_Java/tests/testejb/target/test-classes/META-INF/stub.ejb-jar.xml
17:14:29,850 INFO config:70 - Beginning load: D:\Workspace_Java\tests\testejb\target\classes
17:14:29,850 INFO config:70 - AltDD ejb-jar.xml -> file:/D:/Workspace_Java/tests/testejb/target/classes/META-INF/stub.ejb-jar.xml
17:14:29,850 INFO config:70 - Configuring enterprise application: classpath.ear
17:14:29,912 INFO config:70 - Configuring Service(id=Default Stateless Container, type=Container, provider-id=Default Stateless Container)
17:14:29,912 INFO config:70 - Auto-creating a container for bean ServiceStub: Container(type=STATELESS, id=Default Stateless Container)
17:14:29,912 INFO options:70 - Using 'openejb.descriptors.output=true'
17:14:29,912 INFO options:70 - Using 'openejb.descriptors.output=true'
17:14:29,928 INFO config:70 - Dumping Generated ejb-jar.xml to: C:\TEMP\ejb-jar-6391test-classes.xml
17:14:29,959 INFO config:70 - Dumping Generated openejb-jar.xml to: C:\TEMP\openejb-jar-6392test-classes.xml
17:14:29,959 INFO options:70 - Using 'openejb.descriptors.output=true'
17:14:29,959 INFO config:70 - Dumping Generated ejb-jar.xml to: C:\TEMP\ejb-jar-6393classes.xml
17:14:29,975 INFO config:70 - Dumping Generated openejb-jar.xml to: C:\TEMP\openejb-jar-6394classes.xml
17:14:30,006 INFO config:70 - Enterprise application "classpath.ear" loaded.
17:14:30,084 INFO startup:70 - Assembling app: classpath.ear
17:14:30,131 INFO startup:70 - Jndi(name=ServiceStubLocal) --> Ejb(deployment-id=ServiceStub)
17:14:30,131 ERROR startup:46 - Jndi name could not be bound; it may be taken by another ejb. Jndi(name=openejb/Deployment/ServiceStub/tests.IService!Local)
17:14:30,131 INFO startup:70 - Undeploying app: classpath.ear
17:14:30,147 ERROR startup:50 - Application could not be deployed: classpath.ear
org.apache.openejb.OpenEJBException: Creating application failed: classpath.ear: Unable to bind business local interface for deployment ServiceStub
at org.apache.openejb.assembler.classic.Assembler.createApplication(Assembler.java:679)
at org.apache.openejb.assembler.classic.Assembler.createApplication(Assembler.java:450)
Is there something wrong in the approach, or in the way to write the ejb-jar ?
I had similar problems and hurdles with OpenEJB. If you need stubbing and mocking for tests (who doesn't) have a look who I finally managed to handle it (with a great help from David - OpenEJB co-founder). In the latest version (3.1.4) OpenEJB works pretty much like Arquillian, allowing inner-class test driver, without ejb-jar.xml and classpath scanning.
I've described my hurdles here: http://jakub.marchwicki.pl/posts/2011/07/01/testing-ejb-application-openejb-without-classpath-scanning/. Have a look, maybe that will make your testing easier.
Why don't you simply use a mocking framework like EasyMock or Mockito to test this. You wouldn't need any deployment descriptor, EJB container, JNDI lookup, etc. Just this kind of code :
#Test
public void testDoService() {
IBean mockBean = EasyMock.createMock(IBean.class);
mockBean.process("data");
EasyMock.replay(mockBean);
Service serviceToTest = new Service(mockBean);
serviceTotest.doService("data");
EasyMock.verify(mockBean);
}
And it would certainly run much faster, too.

Categories