Database Transactions With Google Guice AOP

24 Oct 2011 | By Mihai Roman | Comments | Tags google guice aop dependency injection

Today I’m going to show you a simple way of implementing transactional class methods with Google Guice Aspect Oriented Programming.

The endgame would be to annotate methods as transactional and automatically handle the JDBC transactions boilerplate code:

 @Transaction
  public void executeStatements(Connection conn, String[] statements) throws SQLException {
    /* run db queries here */
  }

Transaction Annotation

We need to declare a Transaction annotation class. Only methods can be annotated with Transaction and annotations can be intercepted at runtime.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
public @interface Transaction {
}

Method Intercepter

We’re going to intercept methods annotated with Transaction. Executing SQL queries using JDBC requires either a DataSource or a Connection. In this example, I’m assuming that all transactional methods receive a Connection object as the first parameter.

import java.lang.reflect.Method;
import java.sql.Connection;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class TransactionIntercepter implements MethodInterceptor {
  @Override
  public Object invoke(MethodInvocation invocation) throws Throwable {
    Method method = invocation.getMethod();
    Transaction annotation = method.getAnnotation(Transaction.class);
    // Make sure the intercepter was called for a transaction method
    if (annotation == null) {
      return invocation.proceed();
    }
    if (invocation.getArguments().length == 0 ||
        !(invocation.getArguments()[0] instanceof Connection)) {
      throw new Exception("First parameter must be a Connection instance: " + method.getName());
      
    }
    Connection conn = (Connection)invocation.getArguments()[0];
   try {
     conn.setAutoCommit(false);
     // Proceed with the original method's invocation.
     Object returnObj = invocation.proceed();
     // Commit if successful.
     conn.commit();
     conn.setAutoCommit(true);
     return returnObj;
   } catch (Throwable ex) {
     // Rollback on error.
     conn.rollback();
     conn.setAutoCommit(true);
     throw ex;
   }
  }
}

Binding the Method Intercepter With Guice

We need to bind the MethodIntercepter in the Guice module. Here’s the code for a dedicated module that can be installed in other modules:

import com.google.inject.AbstractModule;
import com.google.inject.matcher.Matchers;
public class TransactionModule extends AbstractModule {
  @Override
  protected void configure() {
    TransactionIntercepter intercepter = new TransactionIntercepter();
    requestInjection(intercepter);
    bindInterceptor(Matchers.any(), Matchers.annotatedWith(Transaction.class), intercepter);
  }
}

Transactional Method Example

Now you can annotated methods that receive the Connection object as parameter:

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class Manager {
  @Transaction
  public void executeStatements(Connection conn, String[] statements) throws SQLException {
    if (statements == null || conn == null) {
      throw new IllegalArgumentException();
    }
    Statement stmt = conn.createStatement();
    for (int i = 0; i < statements.length; i++) {
      stmt.execute(statements[i]);
    }
  }
}

Some Demo Code

Let’s wrap the above code in a simple example using PostgreSQL:

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

import com.google.inject.Guice;

public class Main {

  public static void main(String[] args) throws SQLException {
    System.out.println("Checking if Driver is registered with DriverManager.");
    
    try {
      Class.forName("org.postgresql.Driver");
    } catch (ClassNotFoundException cnfe) {
      System.out.println("Couldn't find the driver!");
      cnfe.printStackTrace();
      System.exit(1);
    }
    
    TransactionModule module = new TransactionModule();
    Connection conn = null;
    try {
      conn = DriverManager.getConnection("jdbc:postgresql://localhost/demo",
                                      "demo", "demo");
      // Get an instance from Guice, to intercept the methods annotated with @Transaction
      Manager manager = Guice.createInjector(module).getInstance(Manager.class);
      String[] stmts = {"CREATE TABLE a(a int);", 
          "CREATE TABLE b(b int);", 
          "INSERT INTO a(a) values(1);", 
          "INSERT INTO b(b) values(1)"
          };
      manager.executeStatements(conn, stmts);

      String[] stmts_bad = {"CREATE TABLE c(c int);", 
          "CREATE TABLE d(b int;", // a mistake in the SQL statement
          "INSERT INTO c(a) values(1);", 
          "INSERT INTO d(b) values(1)"
          };
      manager.executeStatements(conn, stmts_bad);
     } finally {
       if (conn != null && !conn.isClosed()) {
         conn.close();
       }
     }
  }
}

Conclusion

Guice is a lightweight library that makes it easy to deal with dependency injection but also implement aspect oriented programming paradigms. I’ve used and implemented database transactions programatically with Spring and I have to say that the pattern above is much more flexible and lightweight.

A Flexible J2EE Setup - Case Study on Jira, Confluence and Crowd

In a previous post I showed you how to setup an Apache front-end to proxy to J2EE servers listening on localhost safe ports.

In this post I’m going to show you a flexible setup that allows me to host any number of applications on any number of server instances, proxy-ed not only from relative paths in your domain but from subdomains as well.

Here’s the case study:

  • Jira, Confluence, Crowd hosted on a single Tomcat instance;
  • External war directories, single server.xml configuration file;
  • Apache front-end with separate subdomains for each application: jira.example.com, wiki.example.com, proxy without SSL;

Server.xml

The server.xml configures all applications in multiple services:

<Server port="8005" shutdown="SHUTDOWN">

<Service name="CrowdIdService">
    <Connector port="8086" proxyPort="80" proxyName="crowdid.example.com"
      maxHttpHeaderSize="8192" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" useBodyEncodingForURI="true"
      enableLookups="false" redirectPort="8446" acceptCount="100" connectionTimeout="20000" disableUploadTimeout="true" />

    <Engine name="CrowdIdEngine" defaultHost="localhost">
      <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
        <Context path="" docBase="/path/to/crowd-openid-2.0.2-war" reloadable="false">
                <Logger className="org.apache.catalina.logger.FileLogger" prefix="atlassian-crowdid." suffix=".log" timestamp="true"/>
       <Resource name="jdbc/CrowdIDDS" auth="Container" type="javax.sql.DataSource" username="your_crowd_db_user" password="your_password" driverClassName="org.postgresql.Driver" url="jdbc:postgresql://127.0.0.1:5432/crowdiddb"/>
        <Manager className="org.apache.catalina.session.PersistentManager" saveOnRestart="false"/>
         </Context>
     </Host>
</Engine>
</Service>

<Service name="CrowdService">
<Connector port="8085" proxyPort="80" proxyName="crowd.example.com"
      maxHttpHeaderSize="8192" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" useBodyEncodingForURI="true"
      enableLookups="false" redirectPort="8445" acceptCount="100" connectionTimeout="20000" disableUploadTimeout="true" />

<Engine name="CrowdEngine" defaultHost="localhost">
      <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
        <Context path="" docBase="/path/to/crowd-2.0.2-war" reloadable="false">
                <Logger className="org.apache.catalina.logger.FileLogger" prefix="atlassian-crowd." suffix=".log" timestamp="true"/>
        </Context>
     </Host>
</Engine>
</Service>


<Service name="WikiService">
    <Connector port="8083" proxyPort="80" proxyName="wiki.example.com"
      maxHttpHeaderSize="8192" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" useBodyEncodingForURI="true"
      enableLookups="false" redirectPort="8443" acceptCount="100" connectionTimeout="20000" disableUploadTimeout="true" />
    <Engine name="WikiEngine" defaultHost="localhost">
      <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
        <Context path="" docBase="/path/to/confluence-3.0.2-war/confluence" reloadable="false">
                <Logger className="org.apache.catalina.logger.FileLogger" prefix="atlassian-confluence." suffix=".log" timestamp="true"/>
        </Context>
     </Host>
</Engine>
</Service>

<Service name="JiraService">

    <Connector port="8082" proxyPort="80" proxyName="jira.erbix.com"
      maxHttpHeaderSize="8192" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" useBodyEncodingForURI="true"
      enableLookups="false" redirectPort="8442" acceptCount="100" connectionTimeout="20000" disableUploadTimeout="true" />

    <Engine name="JiraEngine" defaultHost="localhost">
     <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
        <Context path="" docBase="/path/to/jira-4.0.1-war" reloadable="false">
          <Resource name="jdbc/JiraDS" auth="Container" type="javax.sql.DataSource"
            username="your_jira_user"
            password="your_jira_password"
            driverClassName="org.postgresql.Driver"
            url="jdbc:postgresql://127.0.0.1:5432/jiradb"
            />
          <Resource name="UserTransaction" auth="Container" type="javax.transaction.UserTransaction"
            factory="org.objectweb.jotm.UserTransactionFactory" jotm.timeout="60"/>
          <Manager pathname=""/>
        </Context>
      </Host>

   <Valve className="org.apache.catalina.valves.AccessLogValve" resolveHosts="false"
              pattern="%a %{jira.request.id}r %{jira.request.username}r %t &quot;%m %U%q %H&quot; %s %b %D &quot;%{Referer}i&quot; &quot;%{User-Agent}i&quot; &quot;%{jira.request.assession.id}r&quot;"/>

    </Engine>
 </Service>

  <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.storeconfig.StoreConfigLifecycleListener"/>

</Server>

For each application there’s a configured Service that listens to a safe-port. You can now configure the Connector proxyName and Context path to either host the apps on different subdomains (e.g. jira.example.com and wiki.example.com) or on relative paths (e.g. example.com/jira and example.com/wiki).

Apache Virtual Hosts

Now we configure a virtual host for each subdomain we’re hosting:

</VirtualHost>
  <VirtualHost 127.0.0.1:80>
    ServerName wiki.example.com
    ProxyRequests Off
    ProxyPreserveHost On
    <Proxy *>
      Order deny,allow
      Allow from all
    </Proxy>
    ProxyPass /  http://localhost:8083/
    ProxyPassReverse /  http://localhost:8083/
    <Location // >
      Order deny,allow
      Deny from all
      Allow from 127.0.0.1
    </Location>
</VirtualHost>

If you are hosting multiple apps on the same subdomain, add multiple ProxyPass lines with the relative paths.

Conclusions

These configurations provide the most flexible way of setting your apps and server instances with an Apache front-end. I would also recommend having a single server.xml configuration file and external folders for apps. Self-extracting wars and per-application configuration files don’t offer the big-picture perspective.

The Attlassian instructions on integrating Jira and Confluence with Apache restricted the setup to relative paths on a domain: http://confluence.atlassian.com/display/JIRA/Integrating+JIRA+with+Apache.

Apache Front-End for Java EE Application Servers Using mod_proxy and mod_ssl

22 Oct 2011 | By Mihai Roman | Comments | Tags apache httpd tomcat jboss mod_proxy mod_ssl j2ee

The following setup is appropriate if:

  • you want to run your Java EE Application Server (e.g. JBoss) or servlet container (e.g. Apache Tomcat) with non-root privileges (which is recommended);
  • run multiple application server instances on the same domain/IP address.

Software

Proxy Without SSL:

You need to setup a virtual host in httpd.conf (or in a file included with an #include directive). Replace 127.0.0.1 with your public IP address.

<VirtualHost 127.0.0.1:80>
    ServerName example.com
    ServerAlias www.example.com
    ProxyRequests Off
    ProxyPreserveHost On

    <Proxy *>
      Order deny,allow
      Allow from all
    </Proxy>

    ProxyPass /  http://localhost:8090/
    ProxyPassReverse /  http://localhost:8090/

    <Location // >
      Order deny,allow
      Deny from all
      Allow from 127.0.0.1
    </Location>
</VirtualHost>

Apache will proxy from your public IP on port 80, preserving the hostname, to localhost (127.0.0.1) on port 8090. Your J2EE server will listen on port 8090.

The corresponding connector in the XML configuration file (e.g. conf/server.xml) looks like this:

 <Connector port="8090" proxyPort="80"
      maxHttpHeaderSize="8192" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" useBodyEncodingForURI="true"
      enableLookups="false" redirectPort="8446" acceptCount="100" connectionTimeout="20000" disableUploadTimeout="true" />

Proxy With SSL:

When using a SSL connection, you need to turn on mod_ssl and configure the virtual host with the SSL certificates and key:

<VirtualHost 127.0.0.1:443>
    ServerName secure.example.com:443
    ServerAdmin webmaster@example.com

    ProxyRequests off
    ProxyPreserveHost off
    SSLProxyEngine on
    
    SSLEngine on
    SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
    SSLCACertificateFile /path/to/ca-bundle/Your-SSL.ca-bundle
    SSLCertificateFile /path/to/secure.example.com.crt
    SSLCertificateKeyFile /path/to/secure.example.com.key

    <Location /  >
      SSLRequireSSL
      ProxyPass  https://localhost:8091/
      ProxyPassReverse https://localhost:8091/
    </Location>
</VirtualHost>

The corresponding connector in the J2EE server XML configuration file (e.g. conf/server.xml) look like this:

<Connector port="8091" protocol="HTTP/1.1" SSLEnabled="true"
               maxThreads="150" scheme="https" secure="true"
               clientAuth="false" sslProtocol="SSL"
               keystorePass="changeit"
               keystoreFile="/path/to/self/signed/localhost.keystore"
               proxyName="secure.example.com"
               proxyPort="443"
/>

Customizations

You can customize this setup to proxy from subdomains or from relative paths on your domain. You can also host multiple applications (war files) on the same server instance and proxy to them through an Apache front-end.

JAX-RS (Jersey) Configured With Google Guice

21 Oct 2011 | By Mihai Roman | Comments | Tags google guice jax-rs dependency injection jersey

JAX-RS is an API for RESTful web services in Java. I’ve started with Jersey implementation and as I was using Guice I needed a way to configure Jersey servlets to inject dependencies with Guice.

You can find the boilerplate code for a Guice application in a previous post so I’m just going to show you the details for adding JAX-RS servlets.

JAX-RS Servlets Extend GuiceContainer

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;

@Singleton
public class MyServlet extends GuiceContainer {
  private static final long serialVersionUID = -5088704754634598007L;
  @Inject
  MyServlet(Injector injector) {
    super(injector);
  }
  @Override
  public void service(ServletRequest request, ServletResponse response)
      throws ServletException, IOException {
    super.service(request, response);
  }
}

Just Serve

In your ServletModule you can just serve with your guiced Jersey servlet:

import java.util.HashMap;
import java.util.Map;
import com.google.inject.servlet.ServletModule;
public class JaxServletModule extends ServletModule {

  protected void configureServlets() {
    // Bind beans.
    bind(MyAction.class);
    bind(MySecondAction.class);
    // Bind other dependencies
    bind(MyDependency.class).to(MyDependencyImpl.class);
    // Serve with MyServlet
    Map<String, String> params = new HashMap<String, String>();
    serve("/*").with(MyServlet.class, params);
  }
}

Define Applications

You can also split your code into applications. Each application will only load the set classes associated with it. However, because of a Jersey issue, you will not be able to assign identical paths to beans from different applications.

import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.core.Application;
import com.google.inject.Singleton;
@Singleton
public class MyApplication extends Application {
    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> classes = new HashSet<Class<?>>();
        classes.add(MyAction.class);
        classes.add(MySecondAction.class);
        return classes;
    }
}

Now you have to add an extra parameter to the servlet:

    Map<String, String> params = new HashMap<String, String>();
    params.put("javax.ws.rs.Application", "com.example.MyApplication");
    serve("/*").with(MyServlet.class, params);

Beans Are Injected by Guice

Now all beans will be injected by Guice. Don’t forget to bind them in your ServletModule.

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.StreamingOutput;

import com.google.inject.Inject;
import com.google.inject.Singleton;

@Path("/{lang : [a-z]{2}}/app/")
@Singleton
public class MyAction {
  private final MyDependency dependency;

  @Inject
  MyAction(MyDependency dependency) {
    this.dependency = dependency;
  }
  @GET
  @Produces("text/html")
  @Path("/")
  public StreamingOutput getHome() throws UnknownHostException, MongoException {
    return new StreamingOutput() {
        @Override
        public void write(OutputStream stream) throws IOException,
           /* stream output here*/
        }
      };
  }
}

SHA1/MD5 Encryption in Java

20 Oct 2011 | By Mihai Roman | Comments | Tags md5 sha encryption

A simple function I use when I need hash encryption in Java. You could catch the NoSuchAlgorithmException exception and log it inside the function or hardcode the algorithm parameter.

This function also shows a way of converting a byte array to hex character string.

  //Algorithm: "SHA1" or "MD5"
  public static String encrypt(String data, String algorithm) throws NoSuchAlgorithmException {
    if (data == null) return null;
    MessageDigest md = MessageDigest.getInstance(algorithm);
    md.update(data.getBytes());
    byte[] digest = md.digest();
    StringBuffer buf = new StringBuffer();
    for (byte b : digest) {
      buf.append((Character.forDigit((b & 0xF0) >> 4, 16)));
      buf.append((Character.forDigit((b & 0xF), 16)));
    }
    return buf.toString();
  }
1 2 3 4 5