Access Weblogic DataSource remotely from Java and JRuby client

In previous posts, I wrote how to access JBoss or Glassfish DataSource remotely from a java client (JBoss example and Glassfish example). Here is a simple program in Java and JRuby, how to do it in Weblogic 10, using the same appserver setup as Calling Weblogic EJB 3 from JRuby and java client.
package foo;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.ResultSet;

public class DataSourceClient {
public static void main(String[] args) throws Exception {
String sql = "SELECT id FROM MEDRECWLSTORE";
Context ic = new InitialContext();
DataSource dataSource =
(DataSource) ic.lookup("jdbc/MedRecGlobalDataSource");
System.out.println("lookup dataSource returned " + dataSource);
Connection connection = dataSource.getConnection();
System.out.println("Got connection: " + connection);

Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while(rs.next()) {
System.out.print(rs.getString(1) + " ");
}
}
}
To do the equivalent in JRuby with less code (testDataSourceWeblogic.rb):
require 'java'
include_class 'javax.naming.InitialContext'

sql = "SELECT id FROM MEDRECWLSTORE"
ic = InitialContext.new
data_source = ic.lookup("jdbc/MedRecGlobalDataSource")
puts "lookup DataSource returned #{data_source}"
connection = data_source.getConnection
puts "Got connection #{connection}"
stmt = connection.createStatement
rs = stmt.executeQuery(sql)
while rs.next
print rs.getString(1) + " "
end
Set environment variable CLASSPATH for JRuby. Currently this is the only way to pass java classpath to JRuby (jruby1.0.2). Since I need to set CLASSPATH anyway, I will use it for compiling and running java client as well.
set CLASSPATH=%WL_HOME%\server\lib\weblogic.jar;%WL_HOME%\server\lib\api.jar;C:\simple-ejb3\classes
prepare jndi.properties somewhere in client classpath, e.g., simple-ejb3\classes directory:
java.naming.factory.initial=weblogic.jndi.WLInitialContextFactory
java.naming.provider.url=t3://localhost:7011
7011 is the default port for t3, iiop, ldap and http for the sample MedRecServer. Your port number may be different.

Compile and run java client:
C:\simple-ejb3\classes > javac -d . ..\src\foo\DataSourceClient.java
C:\simple-ejb3\classes > java foo.DataSourceClient
Run JRuby client:
jruby testDataSourceWeblogic.rb
Both clients will produce the following output, if run successfully:
lookup DataSource returned ClusterableRemoteRef(-4637547633404831470S:localhost:[7011,7011,-1,-1,-1,-1,-1]:medrec:MedRecServer [-4637547633404831470S:localhost:[7011,7011,-1,-1,-1,-1,-1]:medrec:MedRecServer/286])/286
Got connection weblogic.jdbc.rmi.SerialConnection_weblogic_jdbc_rmi_internal_ConnectionImpl_weblogic_jdbc_wrapper_PoolConnection_com_pointbase_net_netJDBCConnection30_1001_WLStub@1
-1 1 2 3 4 5 6 7 8 9 10 134217727 268435454 402653181 536870908 671088635 805306362 939524089 1073741816 1207959543 1342177270 1476394997 1610612724 1744830451 1879048178 2013265905 214748363
Note that iiop protocol is not supported when accessing remote DataSource on Weblogic. Using iiop will cause the following failure:
Exception in thread "main" java.lang.ClassCastException: weblogic.jdbc.common.internal.ConnectionEnv cannot be cast to java.io.Serializable
at weblogic.iiop.InboundResponseImpl.unmarshalReturn(InboundResponseImpl.java:103)
at weblogic.rmi.cluster.ClusterableRemoteRef.invoke(ClusterableRemoteRef.java:338)
at weblogic.rmi.cluster.ClusterableRemoteRef.invoke(ClusterableRemoteRef.java:252)
at weblogic.jdbc.common.internal.RemoteDataSource_IIOP_WLStub.getConnection(Unknown Source)
at foo.DataSourceClient.main(DataSourceClient.java:12)
Caused by: java.lang.ClassCastException: weblogic.jdbc.common.internal.ConnectionEnv cannot be cast to java.io.Serializable
at weblogic.iiop.IIOPOutputStream.writeObject(IIOPOutputStream.java:2253)
Technorati Tags: , ,

Calling Weblogic EJB 3 from JRuby and java client

This is a continuation of Calling EJB 3 from JRuby client and Calling JBoss EJB 3 from JRuby client. This post will show how to do it with Weblogic application server 10.

1. project directory structure:
C:\simple-ejb3> tree /A /F
+---classes
| \---foo
\---src
\---foo
Client.java
FooBean.java
FooRemote.java
2. create EJB remote business interface and bean class under src\foo:
package foo;
import javax.ejb.*;

@Remote
public interface FooRemote {
public String echo(String s);
}

package foo;
import javax.ejb.*;

@Stateless
public class FooBean implements FooRemote {
public String echo(String s) {
return s;
}
}
3 createJRuby script (testEJBWeblogic.rb) or java client, which is almost identical to the one for glassfish and JBoss, except the default jndi name.

Note that the jndi name I'm using below is not user-friendly, but I found this is the one that always worked. I also tried using the default jndi name for weblogic EJB3 but didn't find any in admin console's View JNDI Tree. Weblogic doesn't seem to fully honor mappedName either. When I specified mappedName="FooBean", the actual jndi name after deployment is FooBean/foo.FooRemote, with the remote business interface name appended to mappedName value.
package foo;
import javax.ejb.*;
import javax.naming.*;

public class Client {
public static void main(String[] args) throws Exception {
Context ic = new InitialContext();
Object obj = ic.lookup("_appsdir_foo-ejb_jarfoo-ejb_jarFooBean_FooRemote");

//the above jndi name is the default one used by weblogic server.
//You can check ejb jndi-name in
//admin console | environment | servers | MedRecServer(admin) | view JNDI tree
//You can also customize the jndi name with @Stateless(mappedName="myname")

FooRemote foo = (FooRemote) obj;
String s = foo.echo("Hello Foo!");
System.out.println(foo + " echo returned " + s);
}
}
==== testEJBWeblogic.rb ====

require 'java'
include_class 'javax.naming.InitialContext'

ic = InitialContext.new
foo = ic.lookup("_appsdir_foo-ejb_jarfoo-ejb_jarFooBean_FooRemote")
result = foo.echo("This is foo!")
puts "Calling the EJB 3 : #{foo} returned #{result}"
4. compile java src. An environment variable WL_HOME is set for convenience but it's not required.
C:\simple-ejb3\classes> set WL_HOME=C:\bea\wlserver_10.0
C:\simple-ejb3\classes> javac -d . -classpath %WL_HOME%\server\lib\weblogic.jar;%WL_HOME%\server\lib\api.jar;. ..\src\foo\*.java
5. start Weblogic server. I will use the sample medrec server to deploy the EJB:
%WL_HOME%\samples\domains\medrec\startWebLogic
6. package and autodeploy ejb-jar. We can combine the 2 steps in 1 by using %WL_HOME%\samples\domains\medrec\autodeploy as the destdir:
C:\simple-ejb3\classes> 
jar cvf %WL_HOME%\samples\domains\medrec\autodeploy\foo-ejb.jar
foo\FooBean.class foo\FooRemote.class
7. start the EJB3 in the admin console http://localhost:7011/console, login with username "weblogic" and password "weblogic". Select Deployments on the left, then _appsdir_foo-ejb_jar (autodeployed) on the right, and click start button.

Alternatively, you can deploy and start the ejb app with weblogic.Deployer class:
java -cp %WL_HOME%\server\lib\weblogic.jar weblogic.Deployer -adminurl t3://localhost:7011 -username weblogic -password weblogic -deploy foo-ejb.jar 
8. prepare jndi.properties somewhere in client classpath, e.g., simple-ejb3\classes directory:
java.naming.factory.initial=weblogic.jndi.WLInitialContextFactory
java.naming.provider.url=iiop://localhost:7011
Note that weblogic server listens on port 7011 for iiop, t3 and http protocols. So using t3://localhost:7011 works as well. If you use the sample wl_server instead of medrec server, the default port is 7001.

9. run JRuby client. Before running the script, we need to set the environment variable CLASSPATH, which is of course different from the one for glassfish and JBoss.
set CLASSPATH=%WL_HOME%\server\lib\weblogic.jar;%WL_HOME%\server\lib\api.jar;C:\simple-ejb3\classes
jruby testEJB.rb
The test output:
Calling the EJB 3 : Delegate(602878) [weblogic.iiop.IOR[RMI:foo.FooBean_my4bwg_FooRemoteIntf:0000000000000000] @localhost:7011, <337, null>] returned This is foo!
The command to run java client (no -cp nor -classpath is needed since we already set CLASSPATH:
java foo.Client
Technorati Tags: , ,

Enum wrapper for javax.transaction.Status

javax.transaction.Status contains a list of int constants representing transaction status. It suffers many drawbacks like lack of type-check and unreadable output. Here is my attempt to create a wrapper enum type:
package javahowto;

import java.util.HashMap;
import java.util.Map;
import javax.transaction.Status;
public enum TransactionStatusEnum {
STATUS_ACTIVE (Status.STATUS_ACTIVE),
STATUS_COMMITTED (Status.STATUS_COMMITTED),
STATUS_COMMITTING (Status.STATUS_COMMITTING),
STATUS_MARKED_ROLLBACK (Status.STATUS_MARKED_ROLLBACK),
STATUS_NO_TRANSACTION (Status.STATUS_NO_TRANSACTION),
STATUS_PREPARED (Status.STATUS_PREPARED),
STATUS_PREPARING (Status.STATUS_PREPARING),
STATUS_ROLLEDBACK (Status.STATUS_ROLLEDBACK),
STATUS_ROLLING_BACK (Status.STATUS_ROLLING_BACK),
STATUS_UNKNOWN (Status.STATUS_UNKNOWN);

private static Map<Integer, TransactionStatusEnum> codeToEnums
= new HashMap<Integer, TransactionStatusEnum>();

static {
codeToEnums.put(Status.STATUS_ACTIVE, STATUS_ACTIVE);
codeToEnums.put(Status.STATUS_COMMITTED, STATUS_COMMITTED);
codeToEnums.put(Status.STATUS_COMMITTING, STATUS_COMMITTING);
codeToEnums.put(Status.STATUS_MARKED_ROLLBACK, STATUS_MARKED_ROLLBACK);
codeToEnums.put(Status.STATUS_NO_TRANSACTION, STATUS_NO_TRANSACTION);
codeToEnums.put(Status.STATUS_PREPARED, STATUS_PREPARED);
codeToEnums.put(Status.STATUS_PREPARING, STATUS_PREPARING);
codeToEnums.put(Status.STATUS_ROLLEDBACK, STATUS_ROLLEDBACK);
codeToEnums.put(Status.STATUS_ROLLING_BACK, STATUS_ROLLING_BACK);
codeToEnums.put(Status.STATUS_UNKNOWN, STATUS_UNKNOWN);
}

private Integer statusCode;

public Integer getStatusCode() {
return statusCode;
}

public static TransactionStatusEnum getEnumFor(Integer i) {
return codeToEnums.get(i);
}

@Override public String toString() {
return super.toString() + "(" + statusCode + ")";
}

private TransactionStatusEnum(int statusCode) {
this.statusCode = statusCode;
}
}
All 10 transaction status code are mirrored in the enum type. You can get the enum instance based on javax.transaction.Status code. The toString method of the enum is also overridden to print out both the string literal and the status code. This is a sample test program to illustrate these usage. Comments are welcome. Another implementation without using static mapping is described in this post (Enum wrapper for javax.transaction.Status (2))

Below is a testcase that prints the 2-way association:
package javahowto;
import java.util.EnumSet;

public class TestTransactionStatusEnum {
public static void main(String[] args) {
System.out.println("status code -> enum:");
for(int i = 0; i < TransactionStatusEnum.values().length; i++) {
System.out.println(TransactionStatusEnum.getEnumFor(i));
}

System.out.println("enum -> status code:");
for(TransactionStatusEnum e : EnumSet.allOf(TransactionStatusEnum.class)) {
System.out.println(e.toString() + "\t" + e.getStatusCode());
}
}
}
In the above test, I use 2 ways to get all elements of an enum type:
  • values() is a built-in public static method in all enum type, and returns an array of this type of enum.
  • allOf(Class<E> elementType) is a public static method in java.util.EnumSet, which returns a set of this type of enum.
Technorati Tags: ,

Followers

Pageviews Last 7 Days