JNDI configuration for Springboot standalone JAR application - java

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

Related

Use context.xml outside web application

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.

Tomee dynamic Resources/Datasources

After searching for a while, I'm exhausted and hope you can help me. I've been trying to write a Java EE application that comes with a datasource configurator (like WordPress at first startup). The datasources are not known on deploy time. With the router example of TomEE I found a method to dynamically replace datasources at application lifetime. I'm currently generating my datasources with the following code:
GeronimoTransactionManager transactionManager = (GeronimoTransactionManager) OpenEJB.getTransactionManager();
Properties props = new Properties();
props.setProperty("driverClassName", "com.mysql.cj.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost/test");
props.setProperty("password", "test");
props.setProperty("passwordCipher", "PlainText");
props.setProperty("username", "test");
try {
DataSource underlying = new org.apache.tomcat.jdbc.pool.DataSourceFactory().createDataSource(props);
dataSource = new ManagedDataSource(underlying, transactionManager, transactionManager);
} catch (Exception e) {
e.printStackTrace();
}
My problem is that at this point no ddl-generation is executed, so that entitymanager.persist(object); throws an exception that the table was not generated.
The following code produces an java.lang.AbstractMethodError:
Map properties = new HashMap<>();
StringWriter create = new StringWriter();
properties.put("javax.persistence.schema-generation.scripts.action", "create");
properties.put("javax.persistence.schema-generation.scripts.create-target", create);
Persistence.generateSchema("router", properties);
Is there a not "hacky" Java EE way to dynamically load resources, especially datasources. If not, is there a TomEE way? Thanks in advance.

How to Close HikariCP JNDI DataSource on Shutdown/Redeploy

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.

Context is read only

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.

JAVA Datasource

I am trying to use a datasource that I set up on my weblogic server.
datasource JNDI name = thinOracleDataSource
in my code I have the following
public class DAOBean implements java.io.Serializable {
private Connection conn;
public void connect() throws ClassNotFoundException,
SQLException, NamingException {
Context ctx = new InitialContext();
// Lookup using JNDI name.
DataSource ds = (javax.sql.DataSource) ctx.lookup("thinOracleDataSource");
conn = ds.getConnection();
}
But I get this error
javax.naming.NameNotFoundException: While trying to look up /thinOracleDataSource in /app/webapp/PreAssignment2/24911485.; remaining name '/thinOracleDataSource'
am I looking the JNDI name in the right way? or am I missing something? Thanks for any help!!
EDIT:
This is the jndi tree that I can get from the weblogic console
Try naming your datasource jdbc/thisOracleDataSource in Weblogic and reference it as:
DataSource ds = (javax.sql.DataSource) ctx.lookup("jdbc/thinOracleDataSource");
Also, make sure the datasource is "targeted" to your weblogic Java server. All of this can be done in the Weblogic admin console.
Your JNDI key should look like approximately "java:comp/env/jdbc/thinOracleDataSource".
You can verify it by using Weblogic console that allows access (and probably search) in JNDI. So, you can check this manually before writing the code.

Categories