I have a web application deployed under Tomcat, with some configuration defined in its context.xml such as an Oracle datasource. (the context.xml is either in the webapp directory or in the Tomcat configuration directory according to what we choose).
The thing is, I want to use the datasource defined in the context.xml of the web application inside a service/daemon that runs alongside (not under Tomcat then), and I would like to use it as simply as in the code below:
DataSource vDs;
//--------------------------------
// Initialize the database connection using the JNDI DataSource :
// jdbc/oracleds
//--------------------------------
try
{
InitialContext vInitContext = new InitialContext();
vDs = ( DataSource ) vInitContext.lookup( Const.kJNDIDataSourceView );
}
catch( NamingException vE )
{
LogManager.logException( vE, "", 0, true );
throw new RuntimeException();
}
The thing is, I could not find how to give a path to my context.xml in the JNDI API.
I could of course manually parse it and configure my connection, but I would like to spare the effort.
Related
I created with Springboot a JAR application.
I launch this JAR application under Tomcat server.
One asks me to use JNDI to retrieve datasource stored in the Tomcat context.xml, saying it's easy.
I try to do this with this following code.
Context initContext;
try {
initContext = new InitialContext();
Context envContext = (Context) initContext.lookup("java:comp/env");
DataSource ds = (DataSource) envContext.lookup("jdbc/mydatabase");
} catch (NamingException e) {
System.out.println("ERROR!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
e.printStackTrace();
System.out.println("ERROR!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
}
But it does not work.
I obtain this error :
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
Is it a simple mean to overcome this problem ?
Or with my JAR standalone application, it is still absolutely impossible to overcome this problem, like the precedent post Tomcat JNDI + Standalone Java informs ?
I see many posts, many docs but it remains unclear for me.
Thanks for your help, your sharing.
Thomas
Kedar brings me an answer.
I make a summary of the Kedar answer :
when running your JAR application with the jar command line, i am using embedded Tomcat
when i use embedded Tomcat, it's possible that i have not access to my JNDI datasource
That's the reason why my JNDI does not work.
I will try to see this post to configure my application in order to try tu use JNDI :
How to create JNDI context in Spring Boot with Embedded Tomcat Container
But, now, thanks to Kedar, i understand why my JNDI code does not work.
I will see the other post to see how configure to use anyway JNDI if possible.
Thanks Kedar.
Sorry for the delay to give the answer.
Thanks to Kedar that indicates me that JNDI does not work with a JAR, i try to :
read on the Tomcat server the context.xml file
extract from this context.xml file the XML element containing all the properties concerning the database
construct a DataSource object from this database properties
Thanks also to MKyong with this tutorial :
https://mkyong.com/java/how-to-read-xml-file-in-java-dom-parser/
Here is my working code under SpringBoot :
#Configuration
public class ApplicationConfiguration {
/**
* Absolute path of the configuration file named context.xml on any Tomcat Server (DEV, TEST, PROD).
*/
private final String CONTEXT_XML_PATH_ON_SERVER = "/opt/tomcat/apache-tomcat-version/conf/context.xml";
#Bean
#Primary
public DataSource dataSource() {
try {
Element datasourceElement = getDatasourceElementInTomcatContextXml();
return DataSourceBuilder
.create()
.username(datasourceElement.getAttribute("username"))
.password(datasourceElement.getAttribute("password"))
.url(datasourceElement.getAttribute("url"))
.driverClassName(datasourceElement.getAttribute("driverClassName"))
.build();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException | IOException e) {
e.printStackTrace();
}
return null;
}
private Element getDatasourceElementInTomcatContextXml() throws ParserConfigurationException, SAXException, IOException {
File contextXmlFile = new File(CONTEXT_XML_PATH_ON_SERVER);
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(contextXmlFile);
//optional, but recommended
//read this - http://stackoverflow.com/questions/13786607/normalization-in-dom-parsing-with-java-how-does-it-work
doc.getDocumentElement().normalize();
NodeList resourceElementList = doc.getElementsByTagName("Resource");
Node nNode = resourceElementList.item(0);
Element eElement = (Element) nNode;
return eElement;
}
}
Thanks Kedar and MKyong.
Thomas
I am using HikariCP 2.3.3 with Spring and Jetty 9 and am trying to resolve the fact that when I hot deploy a new war file, all of the Hikari database pool connections to MySQL are left open and idle. I am using a JNDI lookup in my spring applicationContext file to retrieve the datasource from a Jetty context file.
Since I cannot specify a destroy-method in the jndi-lookup like I can if I were to define a dataSource bean, I referred to this question: Should I close JNDI-obtained data source?, where it mentions you can attempt to close the datasource in the contextDestroyed() method of a ServletContextListener. In that case they were using tomcat and c3po so I'm not sure how much the example relates.
I have tried the following in my contextDestroyed method:
InitialContext initial;
DataSource ds;
try
{
initial = new InitialContext();
ds = (DataSource) initial.lookup("jdbc/myDB");
if (ds.getConnection() == null)
{
throw new RuntimeException("Failed to find the JNDI Datasource");
}
HikariDataSource hds = (HikariDataSource) ds;
hds.close();
} catch (NamingException | SQLException ex)
{
Logger.getLogger(SettingsInitializer.class.getName()).log(Level.SEVERE, null, ex);
}
But at HikariDataSource hds = (HikariDataSource) ds; I get the following exception: java.lang.ClassCastException: com.zaxxer.hikari.HikariDataSource cannot be cast to com.zaxxer.hikari.HikariDataSource
I have also tried the following after reading this issue on GitHub: Is it essential to call shutdown() on HikariDataSource?:
InitialContext initial;
DataSource ds;
try
{
initial = new InitialContext();
ds = (DataSource) initial.lookup("jdbc/myDB");
ds.unwrap(HikariDataSource.class).close();
} catch (NamingException | SQLException ex)
{
Logger.getLogger(SettingsInitializer.class.getName()).log(Level.SEVERE, null, ex);
}
But I get the following exception: java.sql.SQLException: Wrapped connection is not an instance of class com.zaxxer.hikari.HikariDataSource
at com.zaxxer.hikari.HikariDataSource.unwrap(HikariDataSource.java:177)
I feel like I'm close to a working solution but can't quite get it. What is the proper way to close a JNDI HikariCP data source, whether in contextDestroyed() or elsewhere?
I can't find where the 2.3.3 code lines up with the HikariDataSource.java:177 line number above. One suggestion is upgrading to the latest HikariCP version, 2.3.8.
While your code looks correct, I suspect that you are running into a classloader issue, whereby the HikariDataSource (class) loaded by Jetty/Spring classloader and registered in JNDI is not the same classloader that is loading HikariDataSource in your web app.
One quick way to check is to log/print both class instances like so:
...
ds = (DataSource) initial.lookup("jdbc/myDB");
logger.info("JNDI HikariDataSource : " + System.identityHashCode(ds.getClass()));
logger.info("Local HikariDataSource: " + System.identityHashCode(HikariDataSource.class));
...
If the two class objects have different hashCodes, they are not the same class. In which case you will have to investigate whether the JNDI instance is registered in the "global JNDI context". If it is, that datasource can be shared across web app instances, and it is not appropriate for your web app to unilaterally shut it down.
UPDATE: Sorry I missed it. Re-reading your question, my guess was correct. Your original error:
java.lang.ClassCastException: com.zaxxer.hikari.HikariDataSource cannot be cast to
com.zaxxer.hikari.HikariDataSource
is a clear indication that there are two classloaders that have loaded two separate instances of the HikariDataSource class. The first is the Jetty classloader (JNDI), and the second is your web application classloader.
This indicates that the pool is shared across web applications and you probably should not try to shut it down from your application context.
I want to create Queue and the MessageDrivenBean in Oracle 11g.
I created the JMS Module on Weblogic and into it I created Queue and ConnectionFactory.
JDBC names looks like:
Queue: jms/EvZahQueue
ConnectionFactory: jms/ConnectionFactory
I tried to get them with:
Context context = new InitialContext();
connectionFactory = (QueueConnectionFactory) context.lookup("jms/QueueConnector");
queue = (Queue) context.lookup("jms/EvZahQueue");
But, I've got an exception like this:
javax.naming.NameNotFoundException: While trying to look up comp/env/jms/QueueConnector in /app/webapp/registri-view/31900933.; remaining name 'comp/env/jms/QueueConnector'
Also, I tried with:
Context context = new InitialContext();
connectionFactory = (QueueConnectionFactory) context.lookup("java:comp/env/jms/QueueConnector");
queue = (Queue) context.lookup("java:comp/env/jms/EvZahQueue");
And I tried to create default properties and to put them into new InitialContext() but nothing changed.
What should I do? Maybe I need to write something in web.xml, ejb-jar.xml, weblogic-ejb-jar.xml?
From the WebLogic console, Environment -> Servers -> View
JNDI Tree.
From there, find the queue, take note of the nested path where it's
located, then use that path in the lookup.
I resolve the problem.
In Weblogic, I must create subdeployment of my JMS module (Oracle said it is not mandatory, but in my case it seems it is) and then everything works fine.
Helo masters, I have to create a JNDI Datasource dynamically, I tried to do it with a listener called SetupApplicationListener. Here is the beginning of WEB-LIB/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee">
<display-name>pri-web</display-name>
<!-- Listeners -->
<listener>
<listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class>
</listener>
<listener>
<listener-class>myapp.SetupApplicationListener</listener-class>
</listener>
The code of the listener:
public class SetupApplicationListener implements ServletContextListener {
public static Log LOG = null;
public void contextInitialized(ServletContextEvent ctx){
try {
createOracleDataSource();
.....
}
}
private void createOracleDataSource() throws SQLException, NamingException {
OracleDataSource ds = new OracleDataSource();
ds.setDriverType(...);
ds.setServerName(...);
ds.setPortNumber(...);
ds.setDatabaseName(...);
ds.setUser(...);
ds.setPassword(...);
new InitialContext().bind("java:comp/env/jdbc/myDS", ds);
}
.....
}
And there is the error:
[ERROR] 29/01/2013 09:44:50,517 (SetupApplicationListener.java:86) -> Error
javax.naming.NamingException: Context is read only
at org.apache.naming.NamingContext.checkWritable(NamingContext.java:903)
at org.apache.naming.NamingContext.bind(NamingContext.java:831)
at org.apache.naming.NamingContext.bind(NamingContext.java:171)
at org.apache.naming.NamingContext.bind(NamingContext.java:187)
at org.apache.naming.SelectorContext.bind(SelectorContext.java:186)
at javax.naming.InitialContext.bind(InitialContext.java:359)
at myapp.SetupApplicationListener.createOracleDataSource(SetupApplicationListener.java:102)
Can I set the read-only properties of the Context to "true"? Thanks! :)
Tomcat 6.0
Oracle 11g
jdk1.5
EDIT: Don't need to be dynamically, i have to define a jndi datasource internally I can't modify the server files because it is a shared server. It must be jndi because other modules use it in that way, thanks.
If you need to create a datasource dynamically is there really any need for a JNDI lookup? JNDI is designed to make the connection external to the application, while in your scenario its tightly coupled to the application due to a legitimate requirement. Why not just use a JDBC connection?
You need to create a ServletContextListener and there you can make the InitialContext writable - it's not the way it should be done, but if you really need it, this is one way you can do it.
This also works with Java Melody!
protected void makeJNDIContextWritable(ServletContextEvent sce) {
try {
Class<?> contextAccessControllerClass = sce.getClass().getClassLoader().loadClass("org.apache.naming.ContextAccessController");
Field readOnlyContextsField = contextAccessControllerClass.getDeclaredField("readOnlyContexts");
readOnlyContextsField.setAccessible(true);
Hashtable readOnlyContexts = (Hashtable) readOnlyContextsField.get(null);
String context = null;
for (Object key : readOnlyContexts.keySet()) {
String keyString = key + "";
if (keyString.endsWith(sce.getServletContext().getContextPath())) {
context = keyString;
}
}
readOnlyContexts.remove(context);
} catch (Exception ex) {
ex.printStackTrace();
}
}
I haven't got this problem before since I usually defined JNDI in application server(tomcat, weblogic and etc). Just like what Kevin said, this is exactly what JNDI was designed for; separating datasource config from your source code and retrieving JNDI resources through lookup and inject;
Back to your question, I think tomcat has every strict rules on modifying JNDI at runtime. In another word, you cannot re-bind or remove jndi from Context. If you go through the tomcat specification you will probably see some thing about jndi lookup but no re-bind.
From section EE.5.3.4 of the EE 6 platform specification (JSR 316):
The container must ensure that the application component instances
have only read access to their naming context. The container must
throw the javax.naming.OperationNotSupportedException from all the
methods of the javax.naming.Context interface that modify the
environment naming context and its subcontexts.
Note that "their naming context" in this section is referring to java:comp.
I solved this problem when found that I was closing environmentContext object
For example:
Context context=new InitialContext();
Context environmentContext=(Context) context.lookup("java:comp/env");
And my code was:
environmentContext.close();
After removing close function from environmentContext problem was solded for me;
I also had this problem, but being new to Tomee, I didn't know that there is a simple solution. When I deployed my web app to the webapps folder, the app worked fine, but when I deployed it to a service folder, I got the same abort. The problem was that the folder name did not match the war name (minus the .war). Once I fixed that, the app worked fine. Make sure the war name, folder name and service name are identical. This problem produces several different errors, including Context is read only and Error merging Java EE JNDI entries.
I solved this issue by setting useNaming="false" in my context.xml.
From the documentation:
useNaming : Set to true (the default) to have Catalina enable a JNDI InitialContext for this web application that is compatible with Java2 Enterprise Edition (J2EE) platform conventions.
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