Wednesday, May 6, 2009

GWT 1.6 - Using a JNDI Datasource

With the update to GWT 1.6 we found that our JNDI connections would no longer work. This was due to GWT 1.6 using Jetty instead of Tomcat as the embedded server. After searching online we found very few documents or posts explaining how to use a JNDI DataSource with GWT. The problem with most of these methods was that they required you to rewrite some of the Java code. Continuing to research Jetty the following solution was found that requires a few simple modifications to the project.

Configure the DataSource:

To do this you will need to configure a jetty-web.xml file and place it in your Web Application’s WEB-INF directory. Details on how to do this for various databases are located on the Jetty Documentation site at http://docs.codehaus.org/display/JETTY/DataSource+Examples.

NOTE: I believe the version of jetty shipped with GWT is below 6.1.12 and therefore you must leave off the first parameter in the example docs as it was added in jetty 6.1.12rc3. See the note at the top of the Jetty documents page.

A Sample jetty-web.xml file for connecting to a local mysql server follows:


<?xml version=”1.0”?>
<!DOCTYPE Configure PUBLIC “-//Mort Bay Consulting//DTD Configure//EN” http://jetty.mortbay.org/configure.dtd>

<Configure class=”org.mortbay.jetty.webapp.WebAppContext”>

        <New id=”website” class=”org.mortbay.jetty.plus.naming.Resource”>

                <Arg>java:comp/env/jdbc/database</Arg>
                <Arg>
                        <New class=”com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource”>
                                <Set name=”Url”>jdbc:mysql://localhost:3306/database</Set>
                                <Set name=”User”>(Username)</Set>
                                <Set name=”Password”>(Password)</Set>
                        </New>
                </Arg>
        </New>

</Configure>


Configure the Eclipse Run Configuration:

Open the Eclipse run configuration and select the “Arguments” Tab. Then under “VM arguments“ section add the following:


-Djava.naming.factory.initial=org.mortbay.naming.InitialContextFactory


Acquire a Database Connection:

You should now be able to make a JNDI lookup to retrieve the datasource:

public Connection retrieveConnection() throws Exception {
        Connection lConnection = null;

        Context lContext = new InitialContext();
        DataSource lDataSource = (DataSource) lContext.lookup("java:comp/env/jdbc/database");
        lConnection = lDataSource.getConnection();

        return lConnection;                
}




Resource Injection:

Supposedly Jetty supports the servlet 2.5 specification and resource injection via the web.xml entry or @resource annotation. However, I have yet to figure out if this is supported by the Jetty version shipped with GWT. If anyone has figured out whether or not this works and if so how it is done please let me know.

38 comments:

Tim Clymer said...

Thank you for posting this info. However, I'm having a bit of trouble with your example. When I run mine, I get an error:
java.lang.ClassNotFoundException: org.mortbay.jetty.plus.naming.Resource

It appears as though this isn't on the classpath by default. Did you have to add a jetty jar?

Chad Skinner said...

Yes, you will need to copy the jars jetty-plus-1.6.11.jar, and jetty-naming-1.6.11.jar into your project classpath for this to work.

maks said...

thanks I need this so bad. :D

markww said...

Thank you sir.

Adam said...

Hi, I'm new to this approach to database connection and have a quick question about this:

Context context = new InitialContext();
DataSource dataSource = (DataSource) context.lookup("java:comp/env/jdbc/database");
connection = dataSource.getConnection();

when dataSource.getConnection() is called is an entirely new connection made to the server (slow) or does the initial connection only happen once and each repeat call of .getConnection() get the previous connection from the session?

Tim Clymer said...

@Adam:

I suppose it depends on your datasource driver, but typically a new connection will be created each time. Of course it depends if your datasource is a pooling one or not...

Main point is, you should always dispose of the connection properly after using it (connection.close())

Chad Skinner said...

I agree with Tim. Whether or not a new connection is created each time depends on how you configure the datasource in your server.

The http session is not used for the database connection.

It has been a long time since I have seen stats, but in the past (and I would assume still) the most costly portion of using a database, with regard to time, was the establishment of the connection. For deployment I would recommend setting up a database pool, but the setup I describe does not as it is sufficient for our development purposes.

tp said...

Thanks for the post.

I need to incorporate a JCA Resource Adapter in a similar fashion. I tried modifying your instructions, but get a ClassNotFoundException. It seems that Jetty doesn't know about classes in package javax.resource.cci?

Do I need to include the jars mentioned by Chad Skinner: jetty-plus-1.6.11.jar and jetty-naming-1.6.11.jar?

If so, are they obtained from the Jetty site, or are they included in the GWT release?

andrash said...

It's my server side code
package de.MyFirm.gwt.server;

import javax.sql.DataSource;
import java.sql.Connection;
import javax.naming.InitialContext;
import javax.naming.*;

import org.mortbay.jetty.servlet.Context;

import de.netup.gwt.client.GreetingService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

/**
* The server side implementation of the RPC service.
*/
@SuppressWarnings("serial")
public class GreetingServiceImpl extends RemoteServiceServlet implements
GreetingService {

public String greetServer(String input) {
String serverInfo = getServletContext().getServerInfo();
String userAgent = getThreadLocalRequest().getHeader("User-Agent");
String s1 = "";


Context context = new InitialContext();
DataSource dataSource = (DataSource)context.lookup("java:comp/env/jdbc/myDB");
Connection con = dataSource.getConnection();
con.close();


return "Hello, " + input + "!

I am running " + serverInfo
+ ".

It looks like you are using:
" + userAgent + "
ticketMaschine: " + s1;
}
}

I've got following error messages:
- Type mismatch: cannot convert from InitialContext to Context

- The method lookup(String) is undefined for the type Context

Thanx a lot,
andrash

Chad Skinner said...

You have imported the wrong Context class it should be:

import javax.naming.InitialContext
import javax.naming.Context

-Not-

import org.mortbay.jetty.servlet.Context;

Give this a try and let me know whether it solves your issue.

andrash said...

Now I've been getting following error messages:

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
at javax.naming.spi.NamingManager.getInitialContext(Unknown Source)
at javax.naming.InitialContext.getDefaultInitCtx(Unknown Source)
at javax.naming.InitialContext.getURLOrDefaultInitCtx(Unknown Source)
at javax.naming.InitialContext.lookup(Unknown Source)
at de.netup.gwt.server.GreetingServiceImpl.greetServer(GreetingServiceImpl.java:35)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:527)
at com.google.gwt.user.server.rpc.RemoteServiceServlet.processCall(RemoteServiceServlet.java:166)
at com.google.gwt.user.server.rpc.RemoteServiceServlet.doPost(RemoteServiceServlet.java:86)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:487)
at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:362)
at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)
at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:729)
at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:405)
at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
at org.mortbay.jetty.handler.RequestLogHandler.handle(RequestLogHandler.java:49)
at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
at org.mortbay.jetty.Server.handle(Server.java:324)
at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:505)
at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:843)
at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:647)
at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:205)
at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:380)
at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:395)
at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:488)

Chad Skinner said...

If you are running this from eclipse you will need to set the System Property defining the initial context factory in the launch configuration

-Djava.naming.factory.initial=org.mortbay.naming.InitialContextFactory

Can you tell me what server you are running this in?

andrash said...

How can I execute this statment(I'm a beginner)?
-Djava.naming.factory.initial=org.mortbay.naming.InitialContextFactory

Chad Skinner said...

How are you running the code and what server are you running on, are just running the hosted mode? I need to know more about what you are doing in order to help.

andrash said...

Can I send an e-mail to you? It were the easiest way.

andrash said...

I'm using the standard starter code "Greetserver" of GWT. And thus I send just my server side code to you:

package de.netup.gwt.server;

import java.sql.Connection;
import java.sql.SQLException;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import de.netup.gwt.client.GreetingService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

/**
* The server side implementation of the RPC service.
*/
@SuppressWarnings("serial")
public class GreetingServiceImpl extends RemoteServiceServlet implements
GreetingService {

public String greetServer(String input) {
String serverInfo = getServletContext().getServerInfo();
String userAgent = getThreadLocalRequest().getHeader("User-Agent");
String s1 = "No OK";

try {
Context context = new InitialContext();
try {
DataSource dataSource = (DataSource)context.lookup("java:comp/env");
try {
Connection con = dataSource.getConnection();
try {
con.close();
s1="OK";
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}

} catch (NamingException e2) {
// TODO Auto-generated catch block
e2.printStackTrace();
}

} catch (NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}


return "Hello, " + input + "!

I am running " + serverInfo
+ ".

It looks like you are using:
" + userAgent + "
DB Connection: " + s1;
}

}

andrash said...

How can I publish my web.xml and jetty-env.xml? I don't want big modifications on both xml's.

Chad Skinner said...

What email address do you want me to use to contact you?

Also, The problem is not with your code, but with the fact the jetty server used for hosted mode does not have a JNDI ContextFactory Configured.

What IDE are you using for development? (Eclipse?)

andrash said...

jetty-env.xml

?xml version="1.0"?>

configure class="org.mortbay.jetty.webapp.WebAppContext">

!-- new class="org.mortbay.jetty.plus.naming.EnvEntry">-->
!-- arg>meinEnvParameter /arg>-->
!-- arg type="java.lang.String"> /arg>-->
!-- arg type="boolean">true /arg>-->
!-- /new> -->

new id="jdbc/DS" class="org.mortbay.jetty.plus.naming.Resource">
arg>jdbc/DS /arg>
arg>
!-- com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource -->
new class="com.mysql.jdbc.Driver">
Set name="Url">jdbc:mysql://kaltenberg2.ticketmachine.net:3306/ /Set>
set name="User">testbenutzer /set>
set name="Password">test /set>
set name="DatabaseName">sandrar /set>
/new>
/arg>
/new>
/configure>

andrash said...

Please check one more time my server side code, because I have changed it

*******************
web.xml
*******************
?xml version="1.0" encoding="UTF-8"?>
!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

web-app>

!-- Servlets -->
servlet>
servlet-name>greetServlet /servlet-name>
servlet-class>de.netup.gwt.server.GreetingServiceImpl /servlet-class>
/servlet>

servlet-mapping>
servlet-name>greetServlet /servlet-name>
url-pattern>/p7/greet /url-pattern>
/servlet-mapping>

resource-ref>
description>MySQL Connection /description>
res-ref-name>jdbc/DS /res-ref-name>
res-type>javax.sql.DataSource /res-type>
res-auth>Container /res-auth>
/resource-ref>

!-- Default page to serve -->
welcome-file-list>
welcome-file>P7.html /welcome-file>
/welcome-file-list>

/web-app>
*******************
server side code
*******************

package de.netup.gwt.server;

import java.sql.Connection;
import java.sql.SQLException;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import de.netup.gwt.client.GreetingService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

/**
* The server side implementation of the RPC service.
*/
@SuppressWarnings("serial")
public class GreetingServiceImpl extends RemoteServiceServlet implements
GreetingService {

public String greetServer(String input) {
String serverInfo = getServletContext().getServerInfo();
String userAgent = getThreadLocalRequest().getHeader("User-Agent");
String s1 = "No OK";

try {
Context context = new InitialContext();
try {
DataSource dataSource = (DataSource)context.lookup("java:comp/env/jdbc/DS");
try {
Connection con = dataSource.getConnection();
try {
con.close();
s1="OK";
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}

} catch (NamingException e2) {
// TODO Auto-generated catch block
e2.printStackTrace();
}

} catch (NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}


return "Hello, " + input + "!

I am running " + serverInfo
+ ".

It looks like you are using:
" + userAgent + "
DB Connection: " + s1;
}

}
*******************

andrash said...

Note! I had to replace the "<" with " " for this publishing.

andrash said...

I'm using Eclipse and Jetty(Hosted mode).

Thanx a lot
andrash

donald.ruby said...

Hi Chad,

I followed your instructions but I am getting a ClassCastException on java.lang.String on this line:

DataSource lDataSource = (DataSource) lContext.lookup("java:comp/env/jdbc/database");

I checked to see if the lookup was working with this

String str = (String) ctx.lookup( "java:comp/env/jdbc/database" );

and it returns "com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource@adecd4 "

Can you suggest what might be wrong with the DataSource lookup?

Thanks,
Don

Chad Skinner said...

Donald, what DataSource class are you importing? It should be javax.sql.DataSource. If it is I'll need more information in order to help. If it is can you post the code and stack trace to http://pastie.org/ or snipt.org or the like and leave the url I can take a look.

Chad Skinner said...

Ok, I've had some sleep and have looked at your post again and I believe the problem is with the way you configured the DataSource. Rather than registering the DataSource in JNDI the DataSource Classname is registered.

Are you running this in hosted mode in Jetty or on a different server. If you are running in Jetty what I would need to see is the jetty-web.xml file that you used to define your DataSource.

andrash said...

Hi Chad Skinner,

I still have the same problem with the mySQL-Connection.
All infos on http://pastie.org/706082

(eclipse, jetty in hosted mode)

Thanx a lot,
andrash

Chad Skinner said...

You will need to resolve the following error message:

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

I believe that this indicates that you have not setup the initial context system property to do this:

From the Menu Bar choose "Run" > "Run Configurations"

Verify that the correct Run Configuration is highlighted and select the "(x)= Arguments" tab.

Then under the VM Arguments section paste the line:

-Djava.naming.factory.initial=org.mortbay.naming.InitialContextFactory

andrash said...

Now I've get following error messages:
javax.naming.NoInitialContextException: Cannot instantiate class: org.mortbay.naming.InitialContext [Root exception is java.lang.ClassNotFoundException: org.mortbay.naming.InitialContext]
at javax.naming.spi.NamingManager.getInitialContext(Unknown Source)
at javax.naming.InitialContext.getDefaultInitCtx(Unknown Source)
at javax.naming.InitialContext.init(Unknown Source)

the whole stack:
http://pastie.org/712434

Chad Skinner said...

The required Jetty jar files:

http://mirrors.ibiblio.org/pub/mirrors/maven2/org/mortbay/jetty/jetty-naming/6.1.12/jetty-naming-6.1.12.jar

http://mirrors.ibiblio.org/pub/mirrors/maven2/org/mortbay/jetty/jetty-plus/6.1.12/jetty-plus-6.1.12.jar

And if you need the mysql connector:

http://mirrors.ibiblio.org/pub/mirrors/maven2/mysql/mysql-connector-java/5.1.9/mysql-connector-java-5.1.9.jar

donald.ruby said...

Chad, I figured out what was wrong with my jetty-xml. Apparently when I cut and pasted from your HTML example there were some whitespace characters that were preventing jetty from correctly parsing the xml. Once I replaced those characters with spaces it works great!!

THANKS for posting this solution!!!!!!!!

Don

jochen said...

To find out Jetty Version of GWT Plugin there is a pom.xml for jetty in gwt-dev.jar.

PatrickTucker said...

Score... this article saved my butt.

Thanks

Shay said...

Just in case anybody has trouble with this and uses ant and not eclipse, add the following line under the task under the "devmode" target:



(also make sure you have the aforementioned JARs in the classpath, of course)

cody said...

Thank you for posting this!!!!

zididude said...

Does this example work with Google App Engine???

Chad Skinner said...

We do not use GAE in the School District where I work so I can not say for certain. However, It is my understanding that GAE does not support JDBC so you would have to use the APIs Google provides for Data Access or use JPA/JDO.

Int64 said...

Hi,

M using jetty 7 and i am stuck with this error.

This is what i have in jetty.xml


jdbc/DBName


org.postgresql.Driver
jdbc:postgresql://localhost:5432/DBName
username
password
10
50
true
120






I have tried to put etty-plus-1.6.11.jar, and jetty-naming-1.6.11.jar in WAR_INF also. It dint work. please help

Chad Skinner said...

I don't see an error, nor is the jetty.xml valid xml ... post more information and I'll try to help.