Hello I have a web application running on apache-tomee-plus-8.0.1. My problem is about getting an Environment variable from a runnable in a custom executor. The variable is defined in /conf/context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<!-- Default set of monitored resources. If one of these changes, the -->
<!-- web application will be reloaded. -->
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>WEB-INF/tomcat-web.xml</WatchedResource>
<WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>
<!-- disable the scan in order to avoid errors at startup due to ora18n.jar-->
<JarScanner scanManifest="false" scanClassPath="false" scanBootstrapClassPath="false"></JarScanner>
<!-- base64 from user:pass -->
<Environment name="myCreds"
value="toto" type="java.lang.String" />
</Context>
The function I use to get the variable "myCreds"
private static String getCredentialsFromContext() throws NamingException {
Context initialContext = new InitialContext();
Context environmentContext = (Context) initialContext.lookup("java:comp/env");
return (String) environmentContext.lookup("myCreds");
}
This function is called via a JAX-RS endpoint which is used to start long background maintenance tasks of the server application. The progress of the background task is then available on another endpoint.
If I do
#GET
#Path("/testOK")
public static String testOK() {
return getCredentialsFromContext(); // works fine
}
But when I use an executor, the lookup fails with
javax.naming.NameNotFoundException: Name [comp/env] is not bound in this Context. Unable to find [comp].
private static ExecutorService index_executor;
#GET
#Path("/testKO")
public static Response testKO() {
if (index_executor == null){
index_executor = Executors.newFixedThreadPool(5);
}
index_executor.submit(new Runnable() {
#Override
public void run() {
System.out.println(getCredentialsFromContext()); // FAIL
}
});
return Response.ok.build()
}
It looks like the InitialContext is not the same when called from the runnable. I would like to avoid to pass through args the value of "myCreds". I tried to move the declaration of "myCreds" in the context.xml of the webapp but it didn't help. Using JNDIContext also fails.
Do you understand what is the problem and why the context is different?
Thanks :)
JNDI lookups depend on some context information on the running thread, usually the context class loader.
On a Java EE/Jakarta EE server you should not spawn new (unmanaged) threads yourself, but use the ManagedExecutorService provided by the container. This service automatically propagates some kinds of contexts from the calling thread:
The types of contexts to be propagated from a contextualizing application component include JNDI naming context, classloader, and security information. Containers must support propagation of these context types.
(Jakarta Concurrency Specification, emphasis mine)
You can inject a ManagedExecutorService using a #Resource annotation:
#Resource
private ManagedExecutorService executorService;
Using a ManagedExecutorService works on Wildfly, but on TomEE there is IMHO a bug that prevents the propagation of the naming context: JAX-RS resources use CxfContainerClassLoader as context classloader, which wraps the real classloader, preventing it from propagating to the managed thread.
A workaround would consist in switching temporarily to the wrapped classloader:
final ClassLoader tccl = Thread.currentThread().getContextClassLoader();
if (tccl instanceof org.apache.openejb.util.classloader.Unwrappable) {
final ClassLoader cl = ((org.apache.openejb.util.classloader.Unwrappable) tccl).unwrap();
Thread.currentThread().setContextClassLoader(cl);
}
executorService.submit(...);
Thread.currentThread().setContextClassLoader(tccl);
Edit: actually, it is enough to mark the JAX-RS resource as #Stateless for the correct propagation of the JNDI naming context.
Related
I have a code which does context lookup to get UserTransaction JNDI as ctx.lookup("java:comp/UserTransaction").
When I run this code without using CompletableFuture, it works as expected.
When working with CompletableFuture in async thread, it gives exception saying jndi lookup failed.
I tried to check if I can get the required JNDI from global scope, but no luck.
CompletableFutures often run on the JDK's ForkJoinPool rather than application server managed threads, and so lack access to services provided by the application server. MicroProfile Context Propagation (available in Liberty) solves this problem by giving you a way to create CompletableFutures that run on the Liberty thread pool and with access to application component context.
In server.xml,
<featureManager>
<feature>mpContextPropagation-1.2</feature> <!-- 1.0 is also valid -->
<feature>jndi-1.0</feature>
<feature>jdbc-4.2</feature> <!-- or some other feature that participates in transactions -->
... other features
</featureManager>
In your application,
import org.eclipse.microprofile.context.ManagedExecutor;
import org.eclipse.microprofile.context.ThreadContext;
...
ManagedExecutor executor = ManagedExecutor.builder()
.propagate(ThreadContext.APPLICATION)
.build();
CompletableFuture<?> f = executor.supplyAsync(() -> {
UserTransaction tx = InitialContext.doLookup("java:comp/UserTransaction");
...
});
...
executor.shutdown();
If you don't want to construct a new ManagedExecutor, Liberty will also let you cast an EE Concurrency ManagedExecutorService to ManagedExecutor and use that. For example,
ManagedExecutor executor = InitialContext.doLookup("java:comp/DefaultManagedExecutor");
It should also be noted that with a ManagedExecutor, the application context is made available to dependent stages as well as the initial stage, allowing you to perform the lookup in a dependent stage such as the following if you prefer:
executor.supplyAsync(supplier).thenApplyAsync(v -> {
UserTransaction tx = InitialContext.doLookup("java:comp/UserTransaction");
...
});
The problem seems to be that the JNDI context is not propagated to the async thread, so when the CompletionStage attempts to execute the JNDI lookup, it has no context, so it doesn't know which component it is in and thus fails.
There is a very detailed explanation of context propagation and how to do it effectively in Open Liberty (which is the underlying product for WebSphere Liberty) at https://openliberty.io/docs/21.0.0.8/microprofile-context-propagation.html - I'd highly suggest reading it.
Certain Java/Jakarta/MicroProfile APIs will allow you to specify the async service (ExecutorService) to use for the async operation. If possible, you can pass it an instance of ManagedExecutorService which should propagate contexts (like JNDI, security, classloading, etc.) to the async thread. Otherwise, you may need to specify the managed executor service when constructing your CompletionStage.
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.
Consider the following mule configuration and having mule embedded in a Web (Java EE) Application:
<jms:connector
name="jmsConnector"
connectionFactory-ref="jmsConnectionFactory"> <!-- From spring -->
<!-- JNDI Name Resover here? -->
</jms:connector>
<flow name="mainTestFlow">
<jms:inbound-endpoint connector-ref="jmsConnector"
queue="jms/MessageQueue" />
<logger level="INFO" category="mule.message.logging" message="Message arrived." />
</flow>
jmsConnectionFactory refers to a JMS Connection Factory defined in Spring, from:
<bean id="jmsConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jms/QueueConnectionFactory" />
</bean>
The Queue Connection Factory was tested and is working.
The jms/MessageQueue queue name refers to a resource-ref defined in the web application web.xml file. This JNDI reference is bound at the container level to a javax.jms.Queue managed by the application server and connected to a proper messaging server (ActiveMQ, in this case).
However, Mule doesn't treat the queue="" attribute as a JNDI destination, but as the queue name itself. So, when the above code is initialized, it actually creates a new queue in ActiveMQ named "jms/MessageQueue". What I really wanted was that it correctly retrieved the queue from the JNDI reference in the Web Application descriptor.
Ok, you could say, all I had to do was to configure a JNDI Name Resolver at the JMS Connector and also add the jndiDestinations="true" and forceJndiDestinations="true" attributes to it.
This is acceptable:
<jms:default-jndi-name-resolver
jndiProviderUrl="tcp://localhost:1099"
jndiInitialFactory="???"/>
The real problem is that I don't want to place the real Initial Context Factory class name in the jndiInitialFactory, because it would fall into a container-specific definition. However, my application is sometimes deployed into JBoss 4.2.3, and sometimes, into WebSphere 7. Having 2 configurations and 2 EAR packages is not an option, due to our development process.
Anyway, is it anyhow possible to either tell Mule-ESB to assume the current container (as it is in embedded mode) as the default JNDI Initial Factory for lookups or provide a "generic" JNDI Initial Factory that would recognize the container's JNDI environment? That shouldn't be a problem, because a web application can refer to it's container JNDI environment without additional (or even visible) configuration.
If not possible, can I have my jms:inbound-endpoint refer to a javax.jms.Queue defined in Spring, just as the jms:connector does with the JMS Connection Factory? That would actually be rather elegant and clean, as Mule is Spring-friendly.
Thank you all in advance!
Solution
After much thought and consideration, I finally solved my problem by creating a custom JndiNameResolver wired up to spring JNDI facilities (JndiTemplate, for instance). This is far from the best solution, but I found that to be the one that would least interfere and tamper with Mule's and Spring's inner intricacies.
That said, here is the class:
package com.filcobra.mule;
import javax.naming.NamingException;
import org.mule.transport.jms.jndi.AbstractJndiNameResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jndi.JndiObjectLocator;
import org.springframework.jndi.JndiTemplate;
public class SpringJndiNameResolver extends AbstractJndiNameResolver implements InitializingBean {
private static Logger logger = LoggerFactory.getLogger(SpringJndiNameResolver.class);
private JndiTemplate jndiTemplate;
#Override
public void afterPropertiesSet() throws Exception {
if (jndiTemplate == null) {
jndiTemplate = new JndiTemplate();
}
}
#Override
public Object lookup(String name) throws NamingException {
Object object = null;
if (name != null) {
logger.debug("Looking up name "+name);
object = jndiTemplate.lookup(name);
logger.debug("Object "+object+" found for name "+name);
}
return object;
}
public JndiTemplate getJndiTemplate() {
return jndiTemplate;
}
public void setJndiTemplate(JndiTemplate jndiTemplate) {
this.jndiTemplate = jndiTemplate;
}
}
With that, the configuration falls back into the usual:
<spring:bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate" />
<jms:connector
name="jmsConnector"
connectionFactoryJndiName="java:comp/env/jms/MyConnectionFactory" <!-- from Resource-Ref -->
jndiDestinations="true"
forceJndiDestinations="true"
specification="1.1" >
<jms:custom-jndi-name-resolver class="com.filcobra.mule.SpringJndiNameResolver">
<spring:property name="jndiTemplate" ref="jndiTemplate"/>
</jms:custom-jndi-name-resolver>
</jms:connector>
With that, I was finally able to not have my Mule ESB installation tied up to a specific JMS vendor/implementation. In fact, the JMS (queues and factories) configuration is all left under the application server responsibility.
Nevertheless, one thing remained odd. I expected that the JMS endpoints also used my Jndi Name Resolver in order to lookup the queue from a resource-reference, or its JNDI Name, the same way it did with the Connection Factory. That wouldn't work whatsoever. I finally worked around that by placing the queue name itself, as created in the JMS server:
<flow name="mainTestFlow">
<jms:inbound-endpoint connector-ref="jmsConnector" queue="queue/myQueue"/> <!-- Queue Name, not JNDI Name -->
That worked. So, I'm assuming the JMS Connector doesn't try and look up the queue, but simply uses the connection factory (looked up or not) to directly access the JMS Server.
Regards!
I see the problem in the source code: basically if you provide an externally created connection factory, jndiDestinations and forceJndiDestinations are forcefully set to false.
I haven't messed with JNDI enough recently to provide a generic solution to your problem, which indeed would be the best.
What I would try would be to sub-class org.mule.transport.jms.Jms11Support, inject my Spring looked-up queues in it, rewire it internally to use these queues and, lastly, inject it in the Mule JMS connector itself.
I would like to use Spring as a JNDI provider. This means that I would like to configure a bean in my Spring context, which can be accessed via JNDI. This would look something like this:
<bean class="org.some.thing.here">
<property name="beans">
<map>
<entry key="w/t/f">
<bean class="some.thing.Else">
// rest ommitted
</bean>
</entry>
</map>
</property>
</bean>
Then, in my application (lets say a Controller), I want to be able to grab this bean via:
Context ctx = new InitialContext();
some.thing.Else bar = (some.thing.Else) ctx.lookup("w/t/f");
How could I go about doing this? I've looked at XBean, however the project looks out of date (doesn't work with Spring 3.0.X I don't think), and there is very little documentation.
Any other options? I would also considering rolling my own jndi provider class if it isn't too hard to do.
EDIT: I should add that I don't have an option using JNDI, I have a library we have to use which requires certain components to be loaded via JNDI. I would like to use Spring as the provider.
Why use JNDI at all? Just get the Spring ApplicationContext and get the bean from that.
Assuming you initialized Spring using ContextLoaderListener in your webapp, you should be able to retrieve the application context from the ServletContext. From there you can get any bean you declared in Spring.
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
Object bean = context.getBean(some.thing.Else.class);
If you have to use JDNI, then you can create a ServletContextListener that does something like the following in contextInitialized():
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
Object bean = context.getBean(some.thing.Else.class);
Context initCtx = new InitialContext();
Context springCtx = initCtx.createSubcontext("spring");
springCtx.bind("bean", bean);
Then, you should be able to lookup the Spring bean at "spring/bean" from the InitialContext.
Two things to note:
The context listener should probably also call initCtx.destroySubcontext("spring") in contextDestroy too.
The java:comp/env namespace is read-only (in Tomcat at least), so you can't put anything there.
Asker edit: Just a couple more points of clarity...
If you plan on referencing Spring beans via ApplicationContext, then you need a ContextLoaderListener defined in your web.xml. This must be defined before your custom listener class... like so:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>
org.example.sandbox.MyCustomServletContextListener
</listener-class>
</listener>
Also, you can get the ServletContext that getWebApplicationContext uses from the ServletContextEvent, like so:
#Override
public void contextInitialized(ServletContextEvent contextEvent) {
try {
ApplicationContext appContext = WebApplicationContextUtils.getWebApplicationContext(contextEvent.getServletContext());
// get a bean named "myCalendar" from the application context
Calendar cal = (Calendar)appContext.getBean("myCalendar");
// bind via JNDI
Context initialContext = new InitialContext();
Context subCtx = initialContext.createSubcontext("sample");
subCtx.bind("calendar", cal);
} catch (NamingException e) { // ommitted }
}
AngerClown is right, don't bother with JNDI unless you need to provide references to other modules that insist on it. If you are in a webapp container like Tomcat, it will have a JNDI registry. Use that. If not inside a webapp container, it doesn't make sense to have JNDI anyway, since it's for J2EE environments.
Assuming you are inside a webapp, a better way to launch your app is to have the main class be a Spring bean that implements lifecycle interfaces (like InitializingBean) to get a call when it's time to start your app. By that point, your main application class will have been injected with all it's dependencies. This avoids the need to call methods on the ApplicationContext directly.
Even so, if you must call methods on the ApplicationContext and you are launched by Spring, you can implement BeanContextAware and get injected with the context.
Yet another way to write your own JndiExporter
https://blog.konstantinpavlov.net/2008/12/31/how-to-export-spring-bean-to-jndi/
I'm writing a Java servlet that needs to read some site-specific
configuration data; I would like it to be easily accessible/modifiable
by the sysadmins at deployment time. There is no sensible default,
so the data has to be provided by the site admin.
It consists of a few string key/value pairs (think Properties).
It would only be read once (at initialization time).
I'm aware of this SO question
and the ServletContext.getInitParameter() mechanism, but as far as
my understanding goes, they require the data to be bundled in the
servlet package (either as a properties file, or specified in the
web.xml), which makes it inconvenient to upgrade the servlet code.
Is there any "standard" interface for a servlet to get this kind of
key/value configuration data? It would be ok if the programming
interface is the same everywhere, but the actual way of setting the
configuration data depends on the actual servlet container being used.
I'm looking preferably at portable solutions, but I'd be content with
something that only works in Tomcat and Jetty.
The recommended way to configure an application server for a web application is per JNDI.
Every application server (including Jetty and Tomcat) allows you to configure JNDI parameters.
For Jetty you can add the following to your jetty.xml to add the JNDI parameter param.file:
<!-- JNDI java:comp/env -->
<New id="param.file" class="org.mortbay.jetty.plus.naming.EnvEntry">
<Arg>param.file</Arg>
<Arg type="java.lang.String"><SystemProperty name="jetty.home" default="."/>etc/config.properties</Arg>
<Arg type="boolean">true</Arg>
</New>
Then in your servlet you can read the JNDI parameter:
import javax.naming.InitialContext;
import javax.naming.NamingException;
...
public Object readJndi(String paramName) {
Object jndiValue = null;
try {
final InitialContext ic = new InitialContext();
jndiValue = ic.lookup("java:comp/env/" + paramName);
} catch (NamingException e) {
// handle exception
}
return jndiValue;
}
public String getConfigPath() {
return (String) readJndi("param.file");
}
The way to set JNDI values differs for other application servers but the code to read the configuration is always the same.
The Servlet init parameters are the right (and standardized) way of defining properties which can be configured by the administrator. Many of the application servers provide a GUI backend where the parameters can be configured.
For an example for Tomcat, see Defining Tomcat servlet context parameters
Configure the external location of the properties - either via a jvm argument (when starting the servlet container), or in the web.xml
in the external location use config.properties and read it with java.util.Properties
You may take Preferences or hack with user.home, user.dir, etc. But for a few key/value keep things simple.
Write a small Singleton to wrap around Properties and load them from a fix & absolute location
public class LocalConfig extends Properties {
public static LocalConfig $ = new LocalConfig();
private LocalConfig() throws IOException {
load(new File("/etc/myconfig.properties"));
}
}