I have an integration test in one of my projects that I want to run against an embedded jetty server. I followed along with the example here: https://www.eclipse.org/jetty/documentation/jetty-9/index.html#jndi-embedded but when I go to actually run my test it fails with the error:
javax.naming.NameNotFoundException: env is not bound; remaining name 'env/jdbc/NavDS'
at org.eclipse.jetty.jndi.NamingContext.getContext(NamingContext.java:241)
at org.eclipse.jetty.jndi.NamingContext.lookup(NamingContext.java:491)
at org.eclipse.jetty.jndi.NamingContext.lookup(NamingContext.java:491)
at org.eclipse.jetty.jndi.NamingContext.lookup(NamingContext.java:505)
at org.eclipse.jetty.jndi.java.javaRootURLContext.lookup(javaRootURLContext.java:101)
at javax.naming.InitialContext.lookup(InitialContext.java:417)
at com.tura.eyerep.test.TestWebServices.setUpBeforeClass(TestWebServices.java:63)
I'm sure there must be a simple mistake I'm making somewhere but I just can't seem to spot it. Can anyone give a suggestion of what I'm doing wrong here?
In my test I'm setting up the server with:
@BeforeClass
public static void setUpBeforeClass() throws Exception {
server = new Server(8080);
ClassList classList = ClassList.setServerDefault(server);
classList.addAfter("org.eclipse.jetty.webapp.FragmentConfiguration", "org.eclipse.jetty.plus.webapp.EnvConfiguration", "org.eclipse.jetty.plus.webapp.PlusConfiguration");
WebAppContext context = new WebAppContext();
context.setExtractWAR(false);
context.setDescriptor("src/main/webapp/WEB-INF/web.xml");
context.setResourceBase("src/main/webapp");
context.setConfigurationDiscovered(false);
BasicDataSource ds = null;
ds = new BasicDataSource();
ds.setUrl("jdbc:h2:mem:myDB;create=true;MODE=MSSQLServer;DATABASE_TO_UPPER=FALSE;");
org.eclipse.jetty.plus.jndi.Resource mydatasource = new org.eclipse.jetty.plus.jndi.Resource(context, "jdbc/NavDS",ds);
server.setHandler(context);
server.start();
}
@Test
public void testLookup()
{
InitialContext ctx = new InitialContext();
DataSource myds = (DataSource) ctx.lookup("java:comp/env/jdbc/NavDS");
assertNotNull( myds);
}
In my web.xml I have a resource ref entry:
<resource-ref>
<description>Nav Datasource</description>
<res-ref-name>jdbc/NavDS</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
Lets cleanup your testcase first.
// meaningless when you have a WAR that is a directory.
context.setExtractWAR(false); // remove this line
// This prevents Servlet 3 behaviors when using Servlet 2.x descriptors
context.setConfigurationDiscovered(false); // Remove this
The error you are getting ...
javax.naming.NameNotFoundException: env is not bound;
remaining name 'env/jdbc/NavDS'
That likely means that a server is still running somewhere, probably forgot to stop/cleanup the previous server instance. (Look at the example junit5 testcase below, for how it deals with non-fixed ports, how to reference a non-fixed port, and how it stops the lifecycle of the server).
Next, be aware of your scopes.
new org.eclipse.jetty.plus.jndi.Resource(context, "jdbc/NavDS",ds);
That will bind the resource entry to jdbc/NavDS
on the scope context
.
Which means if you look up the resource outside of the WebAppContext
scope,
like you do with testLookup()
method it will exist at initialContext.lookup("jdbc/NavDS")
, and nowhere else, the java:comp/env
prefix/tree doesn't even exist to that testLookup()
method scope.
Inside of your webapp, such as in a Filter or Servlet, that context specific resource is bound and available at jdbc:comp/env/jdbc/NavDS
.
You have 3 typical scopes.
Order | Scope | EnvEntry or Resource first parameter |
---|---|---|
1 | WebApp | new EnvEntry(webappContext, ...) or new Resource(webappContext, ...) |
2 | Server | new EnvEntry(server, ...) or new Resource(server, ...) |
3 | JVM | new EnvEntry(null, ...) or new Resource(null, ...) |
If the value doesn't exist at the WebApp scope, the Server scope is checked, and then the JVM scope is checked.
Your Server can have a value for the name val/foo
and a specific webapp can have a different value for the same name val/foo
, simply by how the scopes are defined.
Next, there's the binding in the Servlet spec, you have specify the <resource-ref>
and this combined with the declaration at the server side, bound to context
means you can access java:comp/env/jdbc/NavDS
from your servlet in that specific webapp.
To see this a different way, in code ...
package jetty.jndi;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.HttpURLConnection;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.List;
import javax.naming.InitialContext;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.plus.webapp.EnvConfiguration;
import org.eclipse.jetty.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.resource.PathResource;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.FragmentConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
public class WebAppWithJNDITest
{
private static Server server;
private static WebAppContext context;
public static class JndiDumpServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/plain");
PrintStream out = new PrintStream(resp.getOutputStream(), false, StandardCharsets.UTF_8);
try
{
dumpJndi(out);
}
catch (NamingException e)
{
throw new ServletException(e);
}
}
}
@BeforeAll
public static void startServer() throws Exception
{
server = new Server(0); // let os/jvm pick a port
Configuration.ClassList classList = Configuration.ClassList.setServerDefault(server);
classList.addAfter(FragmentConfiguration.class.getName(),
EnvConfiguration.class.getName(),
PlusConfiguration.class.getName());
context = new WebAppContext();
context.setContextPath("/");
// This directory only has WEB-INF/web.xml
context.setBaseResource(new PathResource(Paths.get("src/main/webroots/jndi-root")));
context.addServlet(JndiDumpServlet.class, "/jndi-dump");
new org.eclipse.jetty.plus.jndi.Resource(null, "val/foo", Integer.valueOf(707));
new org.eclipse.jetty.plus.jndi.Resource(server, "val/foo", Integer.valueOf(808));
new org.eclipse.jetty.plus.jndi.Resource(context, "val/foo", Integer.valueOf(909));
new org.eclipse.jetty.plus.jndi.EnvEntry(null, "entry/foo", Integer.valueOf(440), false);
new org.eclipse.jetty.plus.jndi.EnvEntry(server, "entry/foo", Integer.valueOf(550), false);
new org.eclipse.jetty.plus.jndi.EnvEntry(context, "entry/foo", Integer.valueOf(660), false);
server.setHandler(context);
server.start();
}
@AfterAll
public static void stopServer()
{
LifeCycle.stop(server);
}
public static void dumpJndi(PrintStream out) throws NamingException
{
InitialContext ctx = new InitialContext();
List<String> paths = List.of("val/foo", "entry/foo");
List<String> prefixes = List.of("java:comp/env/", "");
for (String prefix : prefixes)
{
for (String path : paths)
{
try
{
Integer val = (Integer)ctx.lookup(prefix + path);
out.printf("lookup(\"%s%s\") = %s%n", prefix, path, val);
}
catch (NameNotFoundException e)
{
out.printf("lookup(\"%s%s\") = NameNotFound: %s%n", prefix, path, e.getMessage());
}
}
}
}
@Test
public void testLookup() throws NamingException, IOException
{
System.out.println("-- Dump from WebApp Scope");
HttpURLConnection http = (HttpURLConnection)server.getURI().resolve("/jndi-dump").toURL().openConnection();
try (InputStream in = http.getInputStream())
{
String body = IO.toString(in, StandardCharsets.UTF_8);
System.out.println(body);
}
System.out.println("-- Dump from Test scope");
dumpJndi(System.out);
}
}
Contents of src/main/webroot/jndi-root/WEB-INF/web.xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<resource-ref>
<description>My Foo Resource</description>
<res-ref-name>val/foo</res-ref-name>
<res-type>java.lang.Integer</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</web-app>
The output looks like ...
2021-10-26 17:05:16.834:INFO:oejs.Server:main: jetty-9.4.44.v20210927; built: 2021-06-30T11:07:22.254Z; git: 526006ecfa3af7f1a27ef3a288e2bef7ea9dd7e8; jvm 11.0.12+7
2021-10-26 17:05:17.012:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@19932c16{/,file:///home/joakim/code/jetty/junk/src/main/webroots/jndi-root/,AVAILABLE}
2021-10-26 17:05:17.033:INFO:oejs.AbstractConnector:main: Started ServerConnector@212b5695{HTTP/1.1, (http/1.1)}{0.0.0.0:45387}
2021-10-26 17:05:17.034:INFO:oejs.Server:main: Started @816ms
-- Dump from WebApp Scope
lookup("java:comp/env/val/foo") = 909
lookup("java:comp/env/entry/foo") = 660
lookup("val/foo") = 707
lookup("entry/foo") = 440
-- Dump from Test scope
lookup("java:comp/env/val/foo") = NameNotFound: env is not bound
lookup("java:comp/env/entry/foo") = NameNotFound: env is not bound
lookup("val/foo") = 707
lookup("entry/foo") = 440
2021-10-26 17:05:17.209:INFO:oejs.AbstractConnector:main: Stopped ServerConnector@212b5695{HTTP/1.1, (http/1.1)}{0.0.0.0:0}
2021-10-26 17:05:17.210:INFO:oejs.session:main: node0 Stopped scavenging
2021-10-26 17:05:17.214:INFO:oejsh.ContextHandler:main: Stopped o.e.j.w.WebAppContext@19932c16{/,file:///home/joakim/code/jetty/junk/src/main/webroots/jndi-root/,STOPPED}
Hopefully the scope differences are obvious above.
A variation of the above test is now available at the Eclipse Jetty Embedded Cookbook project.
https://github.com/jetty-project/embedded-jetty-cookbook/
Available in 3 different Jetty flavors