MathJax

SyntaxHighlighter

Highlight

Custom CSS

Friday, November 17, 2006

Using Cobertura for code coverage reporting

Cobertura is a coverage tool for Java that integrates nicely with Ant. The output is pretty and simple to digest, plus it is easy to setup.

From the Cobertura website:

Why use a coverage tool?

No matter how good [one's] methodologies are, and how diligently they're followed in an organisation, it isn't possible to ensure that software testing is as comprehensive as it could be. That's where tools can help.

Cobertura is a free, simple and easy-to-use tool that will complement your existing Java development practices. It helps you discover exactly where your software is being tested and, more importantly, where it isn't. Cobertura will help you to view your software from a number of levels, from the entire system right down to an individual line of code.

In a nutshell, Cobertura will tell you exactly which lines of code were executed after running your unit tests, what percentage of the class was covered by unit tests, and how complex your code is. However, there are still some gotchas for getting Cobertura working with Ant that some online tutorials don't tell you, and that is the subject of today's article.

Cobertura requires 3 libraries in its classpath:


I have set up a special classpath that I use for instrumenting classes in Ant. Note that the aforementioned libraries are the [i">only[/i"> ones required for instrumentation, so those are the only ones in my classpath. The reason for this is that other tools such as Hibernate may use other incompatible versions of ASM, and it may cause your instrumentation endeavor to fail.

<path id="instrument.classpath">
  <fileset dir="lib">
    <include name="cobertura.jar"/>
    <include name="jakarta-oro*.jar"/>
    <include name="asm-all-2.2.2.jar"/>
    <include name="log4j*.jar" />
  </fileset>
</path>

Then I set up a task to instrument my compiled classes, and copy them back over to my compiled classes directory. This way, any of my other tasks can use the instrumented classes without even knowing that they've been instrumented. None of my other tasks change.

<target name="instrument" depends="init, compile">
    <taskdef classpathref="instrument.classpath"
        resource="tasks.properties"/>
    <cobertura-instrument todir="build/instrumented">
      <classpath refid="instrument.classpath"/>
      <fileset dir="build/classes">
        <include name="**/*.class"/>
      </fileset>
    </cobertura-instrument>
    <copy todir="build/classes">
      <fileset dir="build/instrumented"/>
    </copy>
  </target>

Note that after running this task, Cobertura creates a cobertura.ser file in your project root. This file contains information about the instrumented classes, and is written to during instrumented runs of your code.

Once you have run your unit tests (using whatever you like, however you like; your testing framework will never know your .class files have been instrumented), you can run a coverage report to have Cobertura print out a similar looking and pretty sample report linked above.

Here is my target that does the coverage report:

<target name="coverage-report" depends="init">
    <cobertura-report srcdir="src" destdir="doc/coverage-report">
    <delete failonerror="false">
      <fileset file="cobertura.ser"/>
    </delete>
  </target>

Click here to see a sample report.

Tuesday, October 24, 2006

PuTTY with Cygwin

PuTTYCyg allows you to use PuTTY as your Cygwin console instead of the regular old shoddy windows console.

It's really great, but I was kind of confused about how to set it up at first. Apparently, you have to make a new session with hostname "-" (without the quotes) and choose "cygwin" as the protocol, then you save it as yourusername@localhost. Load that session and enjoy.

Sunday, October 22, 2006

Configuring EhCache with Spring

There is a tricky point to configuring EhCache with Spring, which I discovered earlier last week. Usually you configure EhCache with by overriding the default ehcache.xml, defining the disk store location, cache name, and various other cache properties. However, if you use Spring's EhCacheFactoryBean and/or EhCacheManagerFactoryBean to build your cache, you will be in for a surprise.

If you use the EhCacheManagerFactoryBean and specify an ehcache.xml file, the only property that will be used is the diskStorePath property; the rest of the cache properties specified in your xml configuration file will be ignored. You must use setter injection to set these properties via a EhCacheFactoryBean . However, there is no way to set the diskStorePath property using setter injection. You can try, but the property you inject will not be used.

<bean id="myCacheBean"
    class="org.springframework.cache.ehcache.EhCacheFactoryBean">
  <!-- This property is totally ignored! -->                              
  <property name="diskStorePath"                                             
      value="java.io.tmpdir/myCacheName"/>                                   
</bean>

This is because the CacheManager will automatically override any diskStorePath you have set when you add a new cache to the manager:

// Code snippet from Cache.java in the EhCache project:                         
/**
 * @param diskStorePath this parameter is ignored. CacheManager
 * sets it using setter injection.                                              
 */

The solution? Unfortunately, I don't know of any except to use both an ehcache.xml configuration file and setter injection in spring to override the defaults.

Friday, October 20, 2006

JDBC PreparedStatement Logging for Java

The JDBC PreparedStatement interface doesn't provide a reliable way to log your SQL queries. While you can always log the statement that's being prepared, more often than not, you need more information than INSERT INTO SOMETABLE VALUES(?, ?, ?, ?, ?). You need to be able to see how data is being sent to the database, and PreparedStatement, on its own, gives you no reliable way to do that.

An article by IBM shows you have to implement a PreparedStatement interface that wraps around a real statement so that you can capture parameters for logging. However, when I tried the example code, it didn't compile under Java 5.0 (presumably because of the new features for PreparedStatement - though better query logging was mysteriously missing as a feature).

You may download my modifications to LoggableStatement below.

Example

From IBM's article:

String sql = "select foo, bar from foobar where foo < ? "
    + "and bar = ?";
long fooValue = 99;
String barValue = "christmas";

Connection conn = dataSource.getConnection();
PreparedStatement pstmt;

if(logEnabled) // use a switch to toggle logging.
  pstmt = new LoggableStatement(conn,sql);
else
  pstmt = conn.prepareStatement(sql);

pstmt.setLong(1,fooValue);
pstmt.setString(2,barValue);

if(logEnabled)
  System.out.println("Executing query: "+
      ((LoggableStatement)pstmt).getQueryString());

ResultSet rs = pstmt.executeQuery();

Update

The link to loggable statement is gone, I know. Wait, why are you still using straight JDBC?

Friday, September 15, 2006

MySQL 5 Data Truncation Errors

When migrating from MySQL 4 to 5, a lot of code broke for me. I got errors like:
Data too long for column 'xxx'
This is because MySQL is a lot stricter than MySQL4. You can turn off the strictness by modifying the my.ini file in your MySQL installation directory and modifying this line:
sql-mode="STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
And changing it to...
sql-mode="NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
Additionally, you may want to append ?jdbcCompliantTruncation=false to any JDBC connnection URLs you have.

Friday, September 8, 2006

Gotchas with Selenium WAT Framework

Lately, I've been doing some research on WAT frameworks such as Selenium. You can use Selenium to record or script automated tests that will be executed directly in the browser of your choice. Note that Selenium IDE only works with Firefox (as it's a Firefox extension), and that Selenium RC works with most browsers but does not support the HTTPS protocol.

The reason for this is that Selenium RC must inject some HTML and Javascript into your site for it to do its testing work. Rather than including the Selenium Core HTML and Javascript in each page you want tested, Selenium RC runs a proxy server you access, and this proxy server injects the code on each request. You need the client libraries (included in the Selenium RC jars) if you want to write test cases. The side effect is that if the proxy server doesn't support HTTPS, you are out of luck. As of the version I tested (0.8.1), this seems to be the case.

Also, if you happen to write form submission HTML like so, with an empty string as the action (so the page posts to the same URL, a common pattern), your acceptance tests will fail:

<form name="myForm" method="post" action="${action}">...</form>

Instead, you must give the absolute URL for Selenium RC to be able to post properly. Don't ask me why =P

Friday, June 9, 2006

iBATIS parameterClass and resultClass attributes - Don't mix them up!

I just spent an hour chasing down one of the dumbest bugs ever. As anybody who works with iBATIS knows, a simple select mapped statement without any parameters looks similar to this:

<select id="mySelect" parameterClass="MyClass">
  select * from my_table where some_criteria = #someValue#
</select>

I was scratching my head for the longest time trying to figure out why no results were returned, even though there were some 900 rows in the database.

Taking a closer look at that mapped statement: I specified the parameterClass attribute instead of resultClass attribute. So if the statement returns results and iBATIS doesn't know how to map those results to a class, it will just return an empty List. How helpful.

The lesson? Don't be stupid like me, and don't mix up your parameterClass and resultClass attributes.

Update

And also note that it should be #someValue# if you want to access a bean property, and 'someValue' if you actually want to search for the literal someValue!

Monday, May 29, 2006

Exporting database schemas with Spring/Hibernate

If you are using Hibernate, you can use schemaexport create your database schema from your mapping files by including the following in your Ant build file:

<!--
    You will want to modify your runtime classpath to
    include the hibernate jar. 
//-->
<taskdef name="schemaexport"
    classname="org.hibernate.tool.hbm2ddl.SchemaExportTask"
    classpathref="runtime.classpath">
</taskdef>

<!-- 
    This assumes your hibernate.cfg.xml is in the default
    package; the resulting SQL will be saved in the tmp
    directory.
//-->
<schemaexport
    casesensitive="true"
    config="${build.dir}/hibernate.cfg.xml"
    output="${tmp.dir}/schema-export.sql"
    haltonerror="true"/>

However, this assumes the existence of a hibernate.cfg.xml file. If you are using Spring with Hibernate, you will typically configure Hibernate via Spring's configuration file. So where do you tell schemaexport your configuration is?

As far as I know, you can't. Instead, you have to do this programmatically by using LocalSessionFactoryBean#createDatabaseSchema . See the following code, and notice the "&" in front of the bean name; if you don't include the "&", you will get a SessionFactory rather than the bean:

// This assumes your LocalSessionFactoryBean is named "sessionFactory"
LocalSessionFactoryBean bean 
    = (LocalSessionFactoryBean) ctx.getBean("&sessionFactory");
bean.createDatabaseSchema();

From here on, you may write your own task or simply use the java task to run a little utility to create your database schema whenever you need to.

Sunday, April 30, 2006

Yikes! More Hibernate type gotchas!

Suppose your mapping file contains this:

<id name="id" column="uid" type="long" unsaved-value="null">

Then you write this code:

Event e = (Event) session.load(Event.class, 1);

Result:

01:46:14,375 DEBUG LongType:79 - binding '1' to parameter: 1
01:46:14,375  INFO LongType:89 - could not bind value '1' to parameter: 1
java.sql.SQLException: Statement parameter 1 not set.

How sneaky! The type is long, but the literal 1 is an int and will be boxed into an Integer. Darn. Write this and things will work:

Event e = (Event) session.load(Event.class, 1L);

Hibernate: Wrapper classes (revisited)

As an additional note to the previous entry about Hibernate and using object wrapper classes, I found this another good example in the Hibernate Quickly book:

Suppose your persistent class has a boolean property, and the property can be null in the database. You will get a PropertyAccessException when you work with a row that has a null value for that column. If your class uses java.lang.Boolean instead, you won't have this problem.

Tuesday, April 25, 2006

SQL Maps for PHP

I've always hated programming in PHP4.x - especally database programming. Unfortunately, even now, not everybody has made the jump to PHP5. In the office, there are still several websites that run off PHP4, and my webhost unfortunately uses PHP4 as well.

One of the most annoyings things about PHP4 is that there aren't any good database tools available. With PHP5, I at least have a sprawling list of frameworks which, as a whole, have all the features that I want and have become used to (I'm sure), but individually are still lacking. But enough about that. I'm using PHP4. That means no data mappers and no ORM tools.

It got me so upset that I wrote Mapsicle, a data mapper for PHP. It works similar to iBATIS, but with a lot less features (due to being a one man project). However, it's at least got the core functionality there.

Tuesday, April 18, 2006

Hibernate: Wrapper classes and unsaved-values

It's a good idea for your domain model objects in Java to make use of wrapper classes such as Integer and Double instead of using regular old int or double. With Java 5.0's auto boxing and unboxing features, you can use wrapper classes pretty much the same way you could use primitives.

So what's the big deal about using wrapper classes in your domain model objects? Consider the following code:
// Message.java
public class Message {
    private int id;
    private MessageType type;
    // Getters and setters omitted
}

// MessageType.java
public class MessageType {
    private int id;
    private String value;
    // Getters and setters omitted
}

Your database schema:

create table Message (
    id int(11) not null auto_increment,
    type_id int(11) not null,
    primary key(id)
)

create table MessageType (
    id int(11) not null auto_increment,
    value varchar(255) not null,
    primary key(id)
)

insert into MessageType values (0, 'Important Message');

If you try to innocently persist a Message with the following code, you will likely receive a HibernateException.

Message msg = new Message();
MessageType type = new MessageType();
type.setId(0);   // This is an Important Message!
msg.setType(type);
session.save(msg);

The code gives this exception:
org.hibernate.TransientObjectException: object references an unsaved 
transient instance - save the transient instance before flushing: 
MessageType

So what happened here? Hibernate uses an attribute called unsaved-value in your mapping definition to determine whether or not an object was freshly created (unsaved), or a transient instance that was loaded or saved in a previous session. In this case, because that value wasn't set in the mapping definition, the default of 0 was used. If you couple that with the fact that the default initialization of an int value is also 0, Hibernate will always assume that MessageType is unsaved.

Unfortunately, the value of 0 really means something in the database! It's the id of a record with the type name "Important Message". There are two solutions to this problem. The first is to use wrapper classes like so:

// In MessageType.java:
public class MessageType {
  private Integer id;
  private String value;
  // Getters and setters omitted
}

<!-- In MessageType.hbm.xml: -->
<id name="id" column="id" unsaved-value="null">
...
</id>

The default initialization of an object is null, so the unsaved-value should be null as well. The previous code will work without a problem. The other solution is to initialize the id to some value other than 0 (and has no meaning), so as -1. Then set the unsaved-value as such.

Thursday, April 13, 2006

No CSS with CakePHP?

CakePHP is a web development framework that is structured very much like Ruby's Rails. I've worked on an in-house low risk project using a pre-beta version of CakePHP, and from what I've seen, I'm quite impressed. It was a non-trivial website, but CakePHP made the development go much smoother.

It works with PHP4 and PHP5, but I had been having trouble with it lately on my machine that runs PHP5. When I installed Cake, none of the CSS files I had would show up. After poking around a bit and scratching my head for a bit, I found that it was a problem with mod_rewrite for Apache not being enabled. Basically you have to uncomment /cake/app/config/core.php line 42 and everything should work like a charm:

// Uncomment the line below!
define ('BASE_URL', env('SCRIPT_NAME'));

There's a hidden dev gotchas page in the CakePHP Wiki that documents all this.

Wednesday, April 12, 2006

Apache Axis admin client gotchas

I noticed today that the Axis admin client utility (org.apache.axis.client.AdminClient) can be tricky when you want to deploy a web service with a WSDD. The following things must be true before the admin client will actually do what you want.

First of all, when you use the WSDL2Java tool to generate your deploy.wsdd and undeploy.wsdd files, the tool just places them in your src tree. You can run the admin client and feed it deploy.wsdd, and it will happily run, probably saying OK, and deploy nothing. When you check Axis's list, you'll get a fault saying so-and-so web service you tried to deploy couldn't be found.

That's because the admin client expects your .class files to be in the class path! Obviously this isn't the case if you are running the client from your source directory. Copy the deploy.wsdd file to your build directory and run the client again. It still doesn't work.

Excellent.

However, try copying everything in your build directory into the WEB-INF/classes where Axis is deployed, then run the client from that directory. Everything works! Don't ask me why.

SSL with Tomcat

The Tomcat documentation for SSL setup is not entirely correct. The How-to on the Jakarta website is accurate about keystore creation, but the snippet of XML for your server.xml file is outdated.

I suppose it's because they keep changing the format of that file and the documentation is having trouble keeping up. If you include the className attribute in your Connector element, it is likely that Tomcat will start, and then promptly throw an InvocationTargetException, which you will see if you look at your stdoutXXX.log file (or your catalina.out file if you've opted for the huge monolithic catalina.out logging solution).

Instead, you should use the following snippet without the className attribute. Name sure you declare it within the you want to use (typically it is Catalina).

<Connector port="8443"
        maxHttpHeaderSize="8192"
        maxThreads="150"
        minSpareThreads="25"
        maxSpareThreads="75"
        enableLookups="false"
        disableUploadTimeout="true"
        acceptCount="100"
        scheme="https"
        secure="true"
        clientAuth="false"
        keypass="password"
        sslProtocol="TLS"/>

Make sure you follow the rest of the instructions for setting up your keystore, and place the keystoreFile attribute within the Connector element (there's no Factory element for Tomcat 5) if your keystore isn't located in the same directory as your home directory, or you're not running Tomcat as yourself (highly likely if you're running Linux).

Note: Generate your keystore file with this command:

$JAVA_HOME/bin/keytool -genkey -alias tomcat -keyalg RSA