Spring & JNDI: locate resource platform independent - java

I'm trying to load in a property file provided by JNDI which should be platform independent. I know I can do it in the following ways, dependent on the platform:
For Weblogic:
Properties properties = new Properties();
properties.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
InitialContext context = new InitialContext(properties);
context.lookup(propertiesLocation);
For Tomcat:
Context context = new InitialContext();
Context envCtx = (Context) context.lookup("java:comp/env");
final Object lookup = envCtx.lookup(propertiesLocation);
The core problem is that in Tomcat the prefix java:comp/env/ is needed. Since Spring is able to load all this platform independently, I tried looking into the possibilities of Spring loading my JNDI resources.
I found out I can use the JndiTemplate of Spring in the following way:
JndiTemplate jndiTemplate = new JndiTemplate();
Object lookup = jndiTemplate.lookup(propertiesLocation);
This is still platform dependent however, needing to use java:comp/env as a prefix during the lookup on tomcat. Looking further on StackOverflow and in the Spring javadocs, I found the class JndiLocatorSupport, which has the following:
JNDI names may or may not include the "java:comp/env/" prefix expected by J2EE applications when accessing a locally mapped (ENC - Environmental Naming Context) resource. If it doesn't, the "java:comp/env/" prefix will be prepended if the "resourceRef" property is true (the default is false) and no other scheme (e.g. "java:") is given.
So I created a JndiObjectFactoryBean which extends JndiLocatorSupport, enabled setResourceRef but it doesn't seem to append the prefix.
Core problem:
When using the following code:
JndiObjectFactoryBean factoryBean = new JndiObjectFactoryBean();
factoryBean.setResourceRef(true);
Object lookup = factoryBean.getJndiTemplate().lookup(propertiesLocation);
I'd expect it to have the same effect as:
JndiObjectFactoryBean factoryBean = new JndiObjectFactoryBean();
Object lookup = factoryBean.getJndiTemplate().lookup("java:comp/env/" + propertiesLocation);
But it doesn't. It seems to have no effect at all. But if I look through the source code, it does go like this:
JndiObjectFactoryBean.lookup() -> JndiObjectLocator.lookup() -> JndiLocatorSupport.lookup(), which does call the right methods.

JndiObjectFactoryBean factoryBean = new JndiObjectFactoryBean();
factoryBean.setResourceRef(true);
Object lookup = factoryBean.getJndiTemplate().lookup(propertiesLocation);
and
JndiTemplate jndiTemplate = new JndiTemplate();
Object lookup = jndiTemplate.lookup(propertiesLocation);
Are the same with regards to the lookup. The first is only a very complex way to obtain a JndiTemplate. All the settings you do are for the JndiObjectFactoryBean NOT for the internal JndiTemplate. Basically your whole approach doesn't add anything.
Instead use a JndiLocatorDelegate and let that do the lookup (don't try to get the JndiTemplate!).
JndiLocatorDelegate jndi = JndiLocatorDelegate.createDefaultResourceRefLocator();
Object lookup = jndi.lookup(propertiesLocation);
This will by default do a lookup in java:comp/env and if not found do a fallback to a plain propertiesLocation (what you passed in).

Related

Simple-JNDI VS SimpleNamingContextBuilder with EmbeddedDatabaseBuilder and already existing Data Source

Does anyone knows to configure a EmbeddedDatabaseBuilder Datasource with Simple-JNDI?
I have a DataSource for testing purposes that I am building like this:
public DataSource dataSource() {
EmbeddedDatabase datasource = new EmbeddedDatabaseBuilder()
.setType(HSQL)
.setSeparator(";")
.addScript("classpath:/tables-definitions.sql")
.build();
return datasource;
}
And I want to bind this to a JNDI name with Simple-JNDI. Do you know how to do this?
Finally I found the answer how to use Simple-JNDI to bind to a jndi name a data source that you already have, in general for testing purposes.
Just an observation if you try to use SimpleNamingContextBuilder:
SimpleNamingContextBuilder is deprecated in spring 5.2 and above in favour of Simple-JNDI
Unfortunately I did not find a great source of documentation for Simple-JNDI which make things for more advanced stuff a bit cumbersome.
Also SimpleNamingContextBuilder does not work with JTA only with JPA - it does not cover all the naming needs for JTA
Now, how to bind a JNDI name with Simple-JNDI to a data source:
you need to set Context.INITIAL_CONTEXT_FACTORY to the factory class that you want to do the job for you, in my case I choose org.osjava.sj.memory.MemoryContextFactory - if you look into the location of this class in Simple-JNDI library, you will find more options there, depending of your needs
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.osjava.sj.memory.MemoryContextFactory");
Then you create a Hashtable and add all the properties you need to set as you set them through properties.
Hashtable env = new Hashtable();
env.put("org.osjava.sj.jndi.shared", "true");
Then you bind the DS to JNDI name as you would do it in old plain way:
create the initial context with the properties from the Hashtable
create the sub-context
then bind the data source to JNDI name/context:
InitialContext ic = new InitialContext(env);
ic.createSubcontext("java:/comp/env/jdbc");
ic.bind("java:/comp/env/jdbc/"+dataSourceJndiname, datasource);

Get Datasources programmatically

I have a web-app in Java/Java EE deployed on any application server/web server. I would like to get all the datasources configured on this server in my application.
Does anyone have any ideas to achieve it? I can get those through WLST ant tasks. But I need to get them programatically.
If your datasources are configured with JNDI then you can list the context and can get all the names (more from here) with Context.list() method and from those name you can find all the datasources
the Context.list() returns an enumeration of NameClassPair. Each NameClassPair consists of the object's name and its class name. So just iterate it and check the class name for java.sql.DataSource and then get the object name to retrieve it.
With JBoss you can do the following (assuming JMX is available):
Context ctx = new InitialContext();
MBeanServerConnection mconn = (MBeanServerConnection)ctx.lookup("jmx/invoker/RMIAdaptor");
ObjectName name = new ObjectName("jboss.jca:service=DataSourceBinding,*");
Set s = mconn.queryMBeans(name, null);
Where s is an mbeans collection.

Configure JNDI names with Open EJB

I'm trying to (unit) test my EJB class without having to startup my websphere environment. Now I'm using Open EJB, but there are some issues with resolving the JNDI Names for other EJBs that are used within my EJB... and there is no way for me to inject mocked classes from my test right now.
Getting the InitialContext
final Properties properties = new Properties();
properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
properties.setProperty("log4j.category.OpenEJB.options ", "debug");
properties.setProperty("log4j.category.OpenEJB.startup ", "debug");
properties.setProperty("log4j.category.OpenEJB.startup.config ", "debug");
properties.setProperty("MyOwnDatasource.JdbcDriver ", "com.ibm.as400.access.AS400JDBCDriver");
properties.setProperty("MyOwnDataSource.JdbcUrl ", "jdbc:as400:MYHOSTNAME;database name=MYDATABASE;libraries=MYDEFAULTTABLE");
ic = new InitialContext(properties);
Inside my class under test there is a lookup for java:comp/env/ejb/PrefixEjbNameLocalHome and I can not set Open EJB to generate JNDI names in that format.
Additional Property for JNDI name format
I tried setting the formatting rule like this:
properties.setProperty("openejb.jndiname.format ", "comp/env/ejb/{interfaceClass}");
Properties aren't used?
Also the logging configuration isn't used. I'm only seeing INFO and WARN messages from Open EJB, although I set log4j.category.OpenEJB.* and the like to DEBUG or TRACE.
It's the "java:" part that is messing up your test case. Basically Context.INITIAL_CONTEXT_FACTORY and "java:" are mutually exclusive. The InitialContext class has a special understanding of "java:" or any "foo:" lookups and if they are at the beginning of the name it will not use INITIAL_CONTEXT_FACTORY you specified. A somewhat frustrating part of JNDI.
If you lookup the name exactly as printed in the log, it will work. So for example this log message:
INFO - Jndi(name=WidgetBeanRemote) --> Ejb(deployment-id=WidgetBean)
Then in code:
Properties p = new Properties();
p.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
// set any other properties you want
Context context = new InitialContext(p);
Object o = context.lookup("WidgetBeanRemote");

NameNotFoundException when calling a EJB in Weblogic 10.3

I have a EJB defined as this:
package com.foo;
#Stateless (mappedName="HelloWorld")
public class HelloWorldBean implements HelloWorld, HelloWorldLocal
....
When it's deployed to Weblogic (WL), it gets the name myBean. I'm not sure if this is important.
I try to call the bean with this code:
Hashtable ht = new Hashtable();
ht.put(Context.INITIAL_CONTEXT_FACTORY,"weblogic.jndi.WLInitialContextFactory");
ht.put(Context.PROVIDER_URL, "t3://localhost:7001");
ic = new InitialContext(ht);
tp = (HelloWorld) ic.lookup("HelloWorld#com.foo.HelloWorldBean");
Anyone know why I get the following error?
javax.naming.NameNotFoundException: While trying to lookup 'HelloWorld#com.foo.HelloWorldBean' didn't find subcontext 'HelloWorld#com'.
Resolved '' [Root exception is javax.naming.NameNotFoundException: While trying
to lookup 'HelloWorld#com.foo.HelloWorldBean' didn't find
subcontext 'HelloWorld#com'. Resolved '']; remaining name 'HelloWorld#com/foo/HelloWorldBean'
To lookup a Remote Interface of a Session Bean with multiple Remote Business interfaces (e.g.com.acme.FooBusiness1, com.acme.FooBusiness2), you need to lookup a name derived from the combination of the target ejb's global JNDI name (the mappedName() in #Stateless) and the specific Remote Business Interface, separated by a "#":
InitialContext ic = new InitialContext();
FooBusiness1 bean1 = (FooBusiness1) ic.lookup("FooEJB#com.acme.FooBusiness1");
FooBusiness2 bean2 = (FooBusiness2) ic.lookup("FooEJB#com.acme.FooBusiness2");
In the typical case of a bean only having one Remote Business Interface, this fully-qualified form is not needed. In that case, the bean's JNDI name can be used directly :
FooBusiness bean = (FooBusiness) ic.lookup("FooEJB");
That was the theoretical part. Now the practice. In your case, from what I can see, you are accessing the EJB from Weblogic so I'd rather use the no-arg InitialContext() constructor (and use a jndi.properties configuration file for other environments) but this is just a side note. Then, you should look up com.foo.HelloWorld, the Remote Interface, not com.foo.HelloWorldBean, the implementation:
InitialContext ic = new InitialContext();
(HelloWorld) ic.lookup("HelloWorld#com.foo.HelloWorld");
And if your bean has only one Remote Business Interface, this should work:
(HelloWorld) ic.lookup("HelloWorld");

Object reference lookup from JNDI results in ClassCastException

I'm having problems calling EJB3 stateless bean outside the container.
Code for getting the object reference:
Context envCtx = (Context) context.lookup("ejb");
MyObject o = (MyObject) envCtx.lookup(MyObject);
The second row results in exception:
java.lang.ClassCastException: javax.naming.Reference
I use JBoss.org 5.1.0 GA.
Based on some other posts I suspect this might be due to wrong version of client libraries. However, I'm unsure which library jar(s) I should include in the jar? (I get the error using 5.0.4.GA jnpserver.)
For JBoss, your code should look something like that:
Properties properties = new Properties();
properties.put("java.naming.factory.initial","org.jnp.interfaces.NamingContextFactory");
properties.put("java.naming.factory.url.pkgs","=org.jboss.naming:org.jnp.interfaces");
properties.put("java.naming.provider.url","localhost:1099");
Context context = new InitialContext(properties);
(EchoBeanRemote) c.lookup("EchoBean/remote");
If you prefer, you can put the JNDI environement settings in a jndi.properties file (that needs to be on the classpath):
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
java.naming.provider.url=jnp://localhost:1099
And use the non-arg InitialContext constructor:
Context context = new InitialContext();
(EchoBeanRemote) c.lookup("EchoBean/remote");
This is obviously more portable.
And in both case, you'll need jbossall-client.jar on the classpath on the client side.
P.S.: You can check the Global JNDI Name your bean is registered at in the JNDI View of the web-based JMX console (if it still exists).

Categories

Resources