I am trying to use GlobalNamingResources to create a global bean that will be used by 2 different webapps (on the same tomcat of course).
My problem is that I get a NullPointerException when I try to set new data to the class I got from the JNDI.
I followed the following link, and I am still not sure what I did wrong:
http://tomcat.apache.org/tomcat-7.0-doc/config/globalresources.html
This is the line that crashes my servlet:
single.setText("TEXT");
this is my test servlet:
#WebServlet("/JNDIServlet")
public class JNDIServlet extends HttpServlet {
/**
* #see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
Thread.sleep(4000);
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
SingletonClass single = (SingletonClass) envCtx.lookup("TestMe");
single.setText("TEXT");
initCtx.rebind("TestMe", single);
response.getWriter().println(envCtx.lookup("TestMe").toString());
} catch (NamingException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
my "SingletonClass" is just a pojo:
package com.jndi.test;
public class SingletonClass {
private String text;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public SingletonClass() {
}
}
this is my web.xml file:
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
<display-name>JNDIServletTest</display-name>
<resource-ref>
<res-ref-name>TestMe</res-ref-name>
<res-type>com.jndi.test.SingletonClass</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</web-app>
and finally, the relevant part from my server.xml file from the tomcat:
<GlobalNamingResources>
<Resource name="TestMe" auth="Container"
type="com.jndi.test.SingletonClass"
factory="org.apache.naming.factory.BeanFactory"/>
<!-- Editable user database that can also be used by
UserDatabaseRealm to authenticate users
-->
<Resource auth="Container" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" name="UserDatabase" pathname="conf/tomcat-users.xml" type="org.apache.catalina.UserDatabase"/>
</GlobalNamingResources>
also, I have added the needed Resource link in the context.xml in my tomcat:
<ResourceLink name="TestMe" global="TestMe" type="com.jndi.test.SingletonClass"/>
Can anyone please provide me some insight about what I did wrong?
Thanks a lot :)
[SOLVED]
Part 1:
In ResourceLink of webapp's context.xml, use a different name from that used for global name defined for Resource in server.xml
Part 2:
Remove duplicate implementation JARs/classes of the resource bean from-webapp's-lib and keep the ones placed in tomcat lib.
Part 2 Explained:
In my case, i had created a factory in a separate package and packaged it as a JAR. I was using the JAR in both webapp's lib and tomcat lib.This resulted in a ClassCastException (caused as the Resource was created with tomcat's version class - and then the application tried to cast it to the class from it's own lib [if interested, read a little more about ClassLoaders in tomcat, which will make things a lot clearer]).
Reference: JNDI ClassCastException
Related
Recently I tried Tomcat 10.0.10 and when trying to inject the connection pool as a JNDI resource find out that the #Resource annotation doesn't work.
Then I tried obtain it programmatically by creating a InitialContext and it worked. Initially I thought it was only for the java:comp/env/jdbc so I tried with a simple bean like below and tried to inject it with the #Resource annotation it didn't work again. When I try to obtain it programmatically by creating a InitialContext and it works. Then I check whether the #PostConstruct or #PreDestroy annotation works and found out that they also don't work.
package lk.ijse.test.tomcatdbcp;
public class Something {
}
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource name="bean/Something" auth="Container"
type="lk.ijse.test.tomcatdbcp.Something"
factory="org.apache.naming.factory.BeanFactory"
/>
</Context>
<?xml version="1.0" encoding="UTF-8"?>
<web-app metadata-complete="false" xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<resource-env-ref>
<resource-env-ref-name>bean/Something</resource-env-ref-name>
<resource-env-ref-type>lk.ijse.test.tomcatdbcp.Something</resource-env-ref-type>
</resource-env-ref>
</web-app>
package lk.ijse.test.tomcatdbcp;
import java.io.*;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
import javax.naming.InitialContext;
import javax.naming.NamingException;
#WebServlet(name = "helloServlet", value = "/hello", loadOnStartup = 1)
public class HelloServlet extends HttpServlet {
private String message;
#Resource(name= "java:comp/env/bean/Something")
private Something something;
#PostConstruct
public void doSomething(){
System.out.println("Does it work?");
}
public void init() {
message = "Hello World!";
try {
InitialContext ctx = new InitialContext();
Something lookup = (Something) ctx.lookup("java:comp/env/bean/Something");
System.out.println(lookup);
System.out.println(something); // null
} catch (NamingException e) {
e.printStackTrace();
}
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html");
// Hello
PrintWriter out = response.getWriter();
out.println("<html><body>");
out.println("<h1>" + message + "</h1>");
out.println("</body></html>");
}
public void destroy() {
}
}
To reproduce the same issue, I created a sample repo here: https://github.com/sura-boy-playground/play-with-tomcat10
(Complete code can be found there)
At first, I had used javax.annotation.Resource annotation, so I thought that was the reason because of the javax.* to jakarta.* namespace change. Then I tried it with jakarta.annotation.Resource but the result was same.
I tried the same application with Tomcat 9.0.41 plus javax.* namespace, it works perfectly.
Is there any extra stuff that I need to do on Tomcat 10.0.10 to enable these annotations? I dug the Tomcat 10 documentation but I wasn't able to find out any thing related to my issue.
I found out that there was a similar case in Tomcat 7 previously, but I don't like that kind of workaround now.
Tomcat #Resource annotations API annotation stops working in Tomcat 7
You should declare the scope of your jakarta.annotation dependency as provided:
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.0.0</version>
<scope>provided</scope>
</dependency>
If you have two copies of jakarta.annotation.Resource (one in the common classloader and one in your application's classloader), the two classes are different. The InstanceManager will look for fields annotated with the common classloader's copy of #Resource, while the something field is annotated with your webapp's copy of #Resource.
Edit: this problem was fixed in Tomcat 10.0.17 (cf. changelog), 9.0.59 and 10.1.0-M11.
Remark: You will have the same problem in Tomcat 9.0 if you use Java 11 or later. Before Java 11 the javax.annotation.* classes where included in the JRE. Servlet containers are required to look in the bootstrap/JRE classloader before looking in the webapp classloader (overriding javax.* classes is a breach of Java's licence), therefore Tomcat would never find the additional copy of the classes.
How can i set simple string value in configuration of tomcat and then read in java application?
context.xml
<ResourceLink name="global/test" global="testing" type="java.lang.String" />
server.xml
<Enviroment name="testing" value="myUser" type="java.lang.String"/>
web.xml in application
<resource-env-ref>
<resource-env-ref-name>global/test</resource-env-ref-name>
<resource-env-ref-type>java.lang.String</resource-env-ref-type>
</resource-env-ref>
in my java application
public String getValue(){
return new JndiDataSourceLookup().getDataSource("global/test").toString();
}
When i Run tomcat, i see these errors...
org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException: Failed to look up JNDI DataSource with name 'global/test'; nested exception is javax.naming.NameNotFoundException: Name [global/test] is not bound in this Context. Unable to find [global].
javax.naming.NameNotFoundException: Name [global/test] is not bound in this Context. Unable to find [global].
In your web.xml use,
<env-entry>
<description>Sample env entry</description>
<env-entry-name>isConnected</env-entry-name>
<env-entry-type>java.lang.Boolean</env-entry-type><!--order matters -->
<env-entry-value>true</env-entry-value>
</env-entry>
In code,
try {
Context initCxt = new InitialContext();
Boolean isConn = (Boolean)initCxt.lookup("java:comp/env/isConnected");
System.out.println(isConn.toString());
// one could use relative names into the sub-context
Context envContext = (Context) initCxt.lookup("java:comp/env");
Boolean isConn2 = (Boolean)envContext.lookup("isConnected");
System.out.println(isConn2.toString());
} catch (NamingException e) {
e.printStackTrace();
}
Have a look here Naming service tutorial to get a good understanding of InitialContext and JNDI.
I don't know what's inside JndiDataSourceLookup().getDataSource("global/test") but by the name of it, it should return a DataSoruce not a string.
If your lookup is local, simply do
Context ctx = new InitialContext();
String s = (String) ctx.lookup("global/test");
or if you are in a javaee container,
#Resource(name="global/test")
String testString;
and finally in your ejb-jar.xml
<env-entry>
<description>The name was explicitly set in the annotation so the classname prefix isn't required</description>
<env-entry-name>global/test</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>StringValue</env-entry-value>
</env-entry>
Refer this link: http://tomee.apache.org/examples-trunk/injection-of-env-entry/README.html
Your configuration of context.xml, server.xml, and web.xml aren't gonna work.
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.
I am trying to create simple web-app. And stuck on datasource injection. There seems to be several problems. So I will start from my confusion. As I understand there's 2( at least) ways to inject the DataSource into Servlet:
web.xml
#Resource
web.xml sample
<resource-ref>
<res-ref-name>jdbc/MyDB</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
<injection-target>
<injection-target-class>ua.test.TestServlet</injection-target-class>
<injection-target-name>dataSource</injection-target-name>
</injection-target>
</resource-ref>
#Resource sample
public class TestServlet extends HttpServlet{
#Resource
private DataSource dataSource;
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
My confusion : web.xml doesn't work in Tomcat 7 on my simple project. In my opinion, web.xml option should work since there were no annotations before Java 5. Please explain.
Update:
Datasource configuration
<Resource name="jdbc/MyDB"
type="javax.sql.DataSource"
auth="Container"
username="SA"
password=""
driverClassName="org.hsqldb.jdbcDriver"
url="jdbc:hsqldb:file:~/database/my_db"
/>
Try taking out the injection-target entry in web.xml and using the name attribute on the #Resource annotation:
public class TestServlet extends HttpServlet {
#Resource(name = "jdbc/MyDB")
private DataSource dataSource;
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
}
That worked in my local testing with Tomcat 7.0.50. If you're looking for the annotation-less way of doing it, I haven't gotten that to work, even though it should given their changelog1.
EDIT
I still haven't found a solution, but I was curious why this doesn't work so I took a look at the injection-target code. I found that it loads the context.xml entry first, and does pick up the settings from web.xml, but chooses not to override the configuration it found in context.xml because it already sees a jdbc/MyDB entry. I'm not sure how to get the injection-target settings into context.xml or the DB settings like driverClassName into web.xml.
As far as I know, tomcat is a nice servlet container, but it is not a full Java EE container. From The BalusC Code: How to install CDI in Tomcat?, I think that out of the box tomcat is not able to do any dependency injection. Tomcat alone works perfectly associated with Spring, because it is lightweight.
If you do not want to use Spring, the link I wrote above should give you some ways to do CDI with tomcat (TomEE instead of tomcat, Weld or OpenWebBeans).
EDIT:
Apparently, recent versions of tomcat 7 should accept DI - see below the link in comment from davidfmatheson.
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