Sunday, January 10, 2010

Produce documentation with syntax highlighting from Docbook using Maven and Docbkx-tools


Chapter 1. Introduction

1.1. Docbook and maven

I was looking for a maven plugin that produces documentation with syntax highlighting from docbook .

1.2. For the impatient

This article has been written in docbook , and generated via maven with the docbkx maven plugin .

You can check it out

You can download a ready-to-build maven project here http://www.springfuse.com/blog/docbook/docbook-1.0.0-src.zip . It is ready to be customized for your needs.

1.3. Celerio and Springfuse

We write our documentation for Springfuse and Celerio using docbook .

We used to have an ant build to produce html, html-chunked and pdf out of it. We had an old XSL and had to do many xsl updates (and that was a reaaaaaaal pain)

We wanted to upgrade the xsl and use maven ... and we wanted to have syntax highlighting.

1.6. Looking around

As I could not find my way, I went to do something I do very frequently : look for what the best teams did. Look in spring code, in hibernate, in drools.

1.7. Enter docbkx

Then I stumbled upon http://code.google.com/p/docbkx-tools/ . It looked like very much to what I needed.

I was up to speed very quickly with their documentation http://docbkx-tools.sourceforge.net/docbkx-maven-plugin/plugin-info.html and their examples here

... Until I tried to handle correctly PDF . I am not sure what I did wrong. But many stuff were missing.

By example when I imported my old xsl, and I had this kind of errors :

Embedded error: org.apache.fop.apps.FOPException: null:1:28951: Error(1/28951):
No element mapping definition found for (Namespace URI:"http://xml.apache.org/fop/extensions", Local Name: "destination")

I tried many alternatives, tried different FOP version, looked at bugs reports, source code.

I was lost, the html was okay, but pdf would not produce correct results (or no result at all)

1.8. Cocoon is my friend

While looking for many kind of errors on google, I stumbled on a commit from http://cocoon.apache.org/ . I looked into their source at http://svn.apache.org/repos/asf/cocoon/cocoon3/trunk/ I downloaded it, built it (thank you maven)

Woah : everything I needed was working smoothly !

1.9. There you have it

I created this blog entry, so do you not loose 2 or 3 days of your life having to deal with xsl, lib, fop version, weird errors, and frustration !

Chapter 2. Syntax highlighting examples

2.1. Java example

Here is a java example with syntax highlighting

@Entity
@Table(name = "ATTACHMENT")
@Cache(usage=CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public  class Attachment implements Comparable<Attachment>, Serializable {
 static final private long serialVersionUID = 1L;
 static final private Log logger = LogFactory.getLog(Attachment.class);

 // Raw Properties
 private String key;
 private String contentType;
 private byte[] binary;
 
 ...
 
}

2.2. Java with callouts example

This is a java example with syntax highlighting and callouts

@Entity                                                                                  (1)
@Table(name = "ATTACHMENT")
@Cache(usage=CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public  class Attachment implements Comparable<Attachment>, Serializable {
 static final private long serialVersionUID = 1L;
 static final private Log logger = LogFactory.getLog(Attachment.class);

 // Raw Properties
 private String key;
 private String contentType;
 private byte[] binary;                                                                  (2)
1

The @javax.persistence.Table annotation is JPA blabla

2

This is the last line: binary field

2.3. XML

This is XML with syntax highlighting

<security:anonymous />
<security:http-basic />
<security:logout
 logout-url="/logout.action"
 logout-success-url="/index.action"/>
<security:remember-me user-service-ref="accountDetailsServiceImpl"/>
  

Chapter 3. Docbook authoring for a java developer

3.2. command

You can specify commands like this : <command>javac –version</command>

The output: javac –version

3.3. screen

You can specify output like this : <screen>screen output</screen>

The output:

c:\tutorial\springfuse-example>mvn initialize
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building springfuse-example
[INFO]    task-segment: [initialize]
[INFO] ------------------------------------------------------------------------
[INFO] [sql:execute {execution: Create and initialize the database}]
[INFO] Executing file: c:\tutorial\springfuse-example\src\main\sql\h2\drop.sql
[INFO] Executing file: c:\tutorial\springfuse-example\src\main\sql\h2\create.sql
[INFO] Executing file: c:\tutorial\springfuse-example\src\main\sql\h2\comment.sql
[INFO] Executing file: c:\tutorial\springfuse-example\src\main\sql\h2\init.sql
[INFO] 45 of 45 SQL statements executed successfully
[INFO] [springfuse:extract {execution: Extract database Meta Data}]
[INFO] Ready to extract the database schema
[INFO] Connecting to database jdbcUrl=jdbc:h2:~/.h2/quickstartdb
[INFO] Connected OK
[INFO] database Product Name: H2
[INFO] database schema extracted
[INFO] File data-model.springfuse passed reverse conversion OK
[INFO] File data-model.springfuse created successfully
[INFO] You are now ready to upload data-model.springfuse to http://www.springfuse.com/ 
       and generate your project!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 seconds
[INFO] Finished at: Tue Jan 27 16:10:07 CET 2009
[INFO] Final Memory: 4M/8M
[INFO] ------------------------------------------------------------------------

3.4. filename

You can specify filenames like this : <filename>src/main/generated-java/com/springfuse/example/model/Account.java</filename>

The output: src/main/generated-java/com/springfuse/example/model/Account.java

3.5. classname

You can specify classname like this : <classname>GenericDaoService</classname>

The output: GenericDaoService

3.6. literals

You can specify literals like this : <literal>jdbc.driver</literal>

The output: jdbc.driver

3.7. itemizedlist

You can set lists like this

<itemizedlist>
 <listitem><para>Spring Core</para></listitem>
 <listitem><para>Spring MVC</para></listitem>
 <listitem><para>Spring Security</para></listitem>
 <listitem><para>Spring Integration</para></listitem>
 <listitem><para>Spring Web Flow</para></listitem>
</itemizedlist>

The output

  • Spring Core

  • Spring MVC

  • Spring Security

  • Spring Integration

  • Spring Web Flow

3.8. imageobject

You can add images like this

<mediaobject>
 <imageobject>
  <imagedata fileref="images/how-springfuse-works.png" align="center" />
 </imageobject>
 <caption>How springfuse works</caption>
</mediaobject>

The output:

How springfuse works

3.9. Maven

You can use the properties from your pom

Table 3.1. Maven properties in docbook

PropertyXMLValue
pom.groupId<?eval ${project.groupId}?>com.springfuse.blog.docbook
pom.artifactId<?eval ${project.artifactId}?>docbook
pom.name<?eval ${project.name}?>docbook
pom.version<?eval ${project.version}?>1.0.0

Chapter 4. Bugs

4.1. Regression

For some reason the latest version ( 2.0.9 ) of the plugin produces :

Embedded error: org.apache.fop.apps.FOPException: null:1:28951: Error(1/28951):
No element mapping definition found for (Namespace URI:"http://xml.apache.org/fop/extensions", Local Name: "destination")

I had to rely on version 2.0.8 this sole behavior stole hours from me.

4.2. PDF XSL and image

I also could not add an image in the fopdf.xsl on the cover page.

<fo:block>
 <fo:external-graphic src="file:images/logo.png"/>                                       (1)
</fo:block>
1

Not working

I had to use this notation instead:

<fo:block>
 <fo:external-graphic src="file:src/docbkx/resources/images/logo.png"/>                  (1)
</fo:block>
1

Working

Even if this kind of bug seems obvious, it was not that easy to find when mixed between version problems, dependency hell, syntaxic errors etc.

Anyway, hopefully this will help you to build great looking documentation.

Chapter 5. Conclusion

5.1. Docbkx works great

Docbkx is the right tool for the job. When correctly setup, it works like a charm.

5.2. Download maven project

You can download this maven project and build it by yourself here: http://www.springfuse.com/blog/docbook/docbook-1.0.0-src.zip

5.3. Missing artifacts ?

To produce image in PDF I found the solution reading Open JPA : FOP needs the JAI jars, unfortunately they are not in the central maven repository.

5.3.1. Manual installation

5.3.2. Maven profile installation

I created the install maven profile: it will download the file for you and unzip it in target/ .

  • Just execute

    mvn -Pinstall initialize

  • Then in target execute

    install.(bat|sh)

After that you have the jars in your local repository,you can re-execute the maven command:

mvn

to produce the documentation

5.4. Download PDF

You can download this article as a PDF at http://www.springfuse.com/blog/docbook/maven-docbook-syntax-highlighting.pdf

Wednesday, December 2, 2009

Flex and Spring integration using Blazeds and SpringFuse

Hello, Here is a nice article about adding BlazeDS support to SpringFuse generated project. The target audience is people that want to implement a Java based back-end for a Flex web application but are novice to Spring and Hibernate. Enjoy! Nicolas.

Sunday, November 1, 2009

Java Code Generator Success Story

Earlier this year, a software development team working at Steria, a leading IT consulting firm in Europe, has invited Jaxio to demonstrate Celerio, the Enterprise version of SpringFuse.
During our presentation of Celerio, we realized that the development team had already bootstrapped a major project for a bank with the Java source code generated by SpringFuse.
Three days later Celerio was added to their build process.
We are happy to announce that 7 months later, Steria delivers on time the project BForBank, the “first 100% online private bank from Credit Agricole”.
The launch of BforBank has been extensively covered by the media in France: press articles and even a TV ad.
Steria and Jaxio are going to present this success story at the Model Driven Day conference in Paris on November 26th. We hope to meet you there!

Sunday, October 25, 2009

Java Code Generation Promises

In the last months there has been some increasing interest for Java code generation.
However, code generation may be source of a great disappointment if you expect too much out of it.
In France, a politician said something like: “promises commit only those who believe in it” (I am unsure about the translation). Applied to code generation it becomes: if you believe all promises of code generation, it is your responsibility.
If you consider using code generation on your next project, you may be interested in checking the various points that drive us in the development of Celerio, the code generator that powers SpringFuse.

The Model

SQL is still predominant in this world. Many web applications are data-oriented and the database schema is the only model that is always up to date.
We have created Celerio especially for this kind of applications. So, it is natural for us to use the database schema as an entry model for the code generation. When the schema is not enough, for example to describe entity inheritance, we rely on a configuration file.
We could have taken a different path, starting from UML or Java Entities; but for the application we target, SQL + some configuration remain the most pragmatic model.

Maintenance

In some many cases, an application is maintained by developers that were not part of the initial core team. To ease the maintenance, the code generation tool should be pragmatic, easy to learn, with as fewer bells and whistles as possible.

Self-Training

The generated code should help the most junior developers to train themselves. The code should be clean, well organized, and documented. The generated code should not be considered as a black-box. At the end of a project, the developer should understand the ins and outs of the generated code and the underlying technologies it uses.
We have received many feedbacks from users who used SpringFuse to learn JPA, Spring MVC, Spring Security, Spring Web Flows, etc.

Code generation Templates

Developers should focus on the business code, not on writing the code generation templates.
Moreover the underlying technology is moving fast: One year you use hibernate, the next you support JPA, the next you move to JPA 2. Spring is out in 2.5, but version 3 is coming etc… Java ecosystem has not landed yet, it may never.
We write templates for the latest stable technologies as they are released and we support them. For example our next move will be supporting Spring 3.0 and part of EE6.

Speed

Code generation phase should be almost transparent to the developers that generate-compile-deploy-debug often, even on large models.

Over generating

The most difficult thing is to resist to the temptation of generating some code. As we progress with Celerio, we tend to generate less and less code. We also warn our users that parts of the code that Celerio generates are just meant for training/tutorial purposes (i.e. web UI code) or should be used as a copy-paste source.

IDE

A code generation tool is just a tool. I believe it should not force you to use a specific IDE. Celerio is just a Maven2 plugin; it can run in a console mode, in Eclipse etc.

Lock-in

The generated code should run without the code generation tool itself. Of course Celerio is not required at runtime and the generated code depends only on well known Open Source projects.

Code generation Round Trip

The generated code manual modification should not be lost when regenerating. Celerio uses different techniques to preserve the user code (this could be the subject of a separated post).

Conclusion

Code generation can be of great helps if you select the right tool for the right project, for the right team and if you know in advance what to expect from it.
Keep in mind that at the end, one of the most important features is simply the result, which is the generated code. So take the time to review it before selecting a tool. Make sure the generated code complies with your highest expectations in term of quality. Try to put yourself in a position where you would have to maintain this code manually or explain it to another team.
To ease the process of reviewing the code that Celerio generates, we have created SpringFuse. Developers can use this service to generate a project in less than 15 minutes. The result is a zip file that contains the generated source code organized into a Maven2 project. Have fun!

Sunday, October 18, 2009

Project lombok - a MUST have in your java toolbox !

What you'll read in this post:
  • using eclipse to write POJO
  • using eclise and commons-lang to write POJO
  • using lombok to write POJO
  • a maven project with working examples

ProjectLombok is my new technical friend

Today I would like to share with you my new technical companion: projectlombok.

What is lombok

ProjectLombok like Springfuse is a code generator, and I very much like code generators as they make my life easier. Where springfuse is producing working knowledge spanning many technologies, project lombok produces simple code to bypass java excessive verbosity. Project lombok is generating : setter, getter, toString, equals, hashCode. While reading this you should think that eclipse does it for you already since... for ever. You could also tell me that I should use a real language like scala or a dynamic language like groovy.

The problem

Well they could be ideal solutions, but like so many I have no choice but to stick with java.
  • Should we really rely on our IDE to generate this kind of code ?
  • Should we really have to see all these methods clutter our code ?
  • Should we really need to loose time and focus to manually verify that our hashcode, equals, toString methods are in sync ?
Come on, our time is precious, and this really totally dumb, and error prone ! But there should not be this problem to solve in the first place !

Writing all these methods manually

I used to write them manually while using emacs back in the 2000 days, let's skip these painful memories...

A little help from Eclipse

We all know how to generate all these methods using eclipse, but I had the curiosity for this post to count the number of steps to generate them:
 1- Click Source / 2- click generate getter & setter / 3- click Select all / 4- click Ok
 5- Click Source / 6- click generate to String / 7- click Ok
 8- Click Source / 9- click generate hashcode and equals / 10- click Ok
 and then, 11- I tidy up things
That’s 11 steps ! Ok, now it’s time to do some eclipse magic and bind "generate setter/getter" to a shortcut. That’s right, no big deal. But sounds like an ugly solution isn’t it? And I insist, this problem should not exist in the first place ! So you end up with :
package com.springfuse.blog.projectlombok;

public class UsingEclipse {
 private String not;
 private String really;
 private String fun;

 public String getNot() {
  return not;
 }

 public void setNot(String not) {
  this.not = not;
 }

 public String getReally() {
  return really;
 }

 public void setReally(String really) {
  this.really = really;
 }

 public String getFun() {
  return fun;
 }

 public void setFun(String fun) {
  this.fun = fun;
 }

 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((fun == null) ? 0 : fun.hashCode());
  result = prime * result + ((not == null) ? 0 : not.hashCode());
  result = prime * result + ((really == null) ? 0 : really.hashCode());
  return result;
 }

 @Override
 public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (getClass() != obj.getClass())
   return false;
  UsingEclipse other = (UsingEclipse) obj;
  if (fun == null) {
   if (other.fun != null)
    return false;
  } else if (!fun.equals(other.fun))
   return false;
  if (not == null) {
   if (other.not != null)
    return false;
  } else if (!not.equals(other.not))
   return false;
  if (really == null) {
   if (other.really != null)
    return false;
  } else if (!really.equals(other.really))
   return false;
  return true;
 }

 @Override
 public String toString() {
  return "UsingEclipse [fun=" + fun + ", not=" + not + ", really=" + really + "]";
 }
} 
Basically the usual pojo suspect you see everywhere ...

Some real help from Commons-lang

Commons-lang is giving simple and effective solutions to reduce the boilerplate code with EqualsBuilder, ToStringBuilder, HashCodeBuilder. So you have now only the getter and setter to generate with eclipse and no more need to worry about your equals/hashcode methods. You should now have this:
package com.springfuse.blog.projectlombok;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;

public class UsingCommonsLang {
 private String not;
 private String really;
 private String fun;

 public String getNot() {
  return not;
 }

 public void setNot(String not) {
  this.not = not;
 }

 public String getReally() {
  return really;
 }

 public void setReally(String really) {
  this.really = really;
 }

 public String getFun() {
  return fun;
 }

 public void setFun(String fun) {
  this.fun = fun;
 }

 @Override
 public int hashCode() {
  return HashCodeBuilder.reflectionHashCode(this);
 }

 @Override
 public boolean equals(Object obj) {
  return EqualsBuilder.reflectionEquals(this, obj);
 }

 @Override
 public String toString() {
  return ToStringBuilder.reflectionToString(this);
 }
}
This is better, less error prone, but we still still have so many getter/setters that we can’t stand.

Lombok comes and wins !

Now look how we do it using lombok:
package com.springfuse.blog.projectlombok;

import lombok.Data;

@Data
public class UsingProjectLombok {
 private String get;
 private String fun;
 private String back;
}
Isn't it elegant ? The @Data does express my intention nicely ... and completely as the code is also generated After the shock of such a concise POJO, the next though you have is for configuration ... Projectlombok gives you this freedom.
package com.springfuse.blog.projectlombok;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@EqualsAndHashCode(exclude = { "get" })
@ToString(exclude = { "get" })
public class UsingProjectLombokWithConfiguration {
 protected String get;
 @Getter
 private String fun;
 @Getter
 @Setter
 private String back;

 public UsingProjectLombokWithConfiguration(String get, String fun, String back) {
  this.get = get;
  this.fun = fun;
  this.back = back;
 }
}
Please note that Lombok is a good citizen as it gives you clear warnings such as

But how does it work ?

Project lombok is a javac plugin that will use the javac 6 annotation processor. That means that the generation of the methods is part of the compilation, and no runtime nor java agent is required ! It's almost like adding behavior with aspectj but with plain java !

Lombok and maven

Using lombok and maven is straightforward:
 
  
   org.projectlombok
   lombok
   0.8.5
   compile
  
 
 
  
   projectlombok.org
   http://projectlombok.org/mavenrepo
  
 
 
  
   
    org.apache.maven.plugins
    maven-compiler-plugin
    
     1.6
     1.6
     1.6
    
   
   
 

Lombok and eclipse

Execute the lombok.jar and specify your eclipse folder it will add itself or add manually in eclipse.ini the botclasspath and javaagent like this
-javaagent:lombok.jar
-Xbootclasspath/a:lombok.jar

Constraints

javac 1.6

Lombok does work only under javac 1.6, but if you want to target 1.5 just configure it in your pom. But JDK 1.5 has 10 more days before it is no more supported by sun ! It is time to give this info to your boss and make the switch.

eclipse refactoring

The current version 0.8.5 does not work great in this regard, however they fixed it in the trunk. If you do build your own, you'll be fine
git clone git://github.com/rzwitserloot/lombok.git
ant
... or wait for the next binary release ! Edit: the 0.9 release is out

Netbean support

It's in the way

Do not abuse

Lombok generates other methods like @Cleanup, @Synchronized, @SneakyThrows or @Logger I am not sure what to think about these, maybe it could go too far but as the intent is clearly described via the annotation, why not... Anyway I'll stick with the simple features for now and see how it goes.

I love this tool !

I have to say, that the combination of google collections and project lombok gave me a fresh view of what you can accomplish with java. It feels like the gap between the language "du jour" and my plain old java has been dramatically reduced. And you know what ? It feels good !

Sunday, October 11, 2009

Testing various equals and hashCode strategies

Equality in Java and in particular when working with Hibernate/JPA is a tricky subject. You will find in this post a simple Java project with some unit tests that illustrate the importance of overriding the equals() and hashCode() methods.
The project has a User entity with pluggable equals/hashCode implementations. Of course this design is only meant for testing different equals/hashCode implementations against the same unit tests.
Running the tests and seeing them fail or succeed depending on the equals/hashCode strategy used is a fun and easy way to learn and remember the importance of overriding equals/hashCode.
Note that the subject of identity is extensively covered in "Item 9" of Effective Java second Edition and in chapter 9 of Java Persistence with Hibernate. Both books are must read.
Here is a preview of the kind of equals/hashCode tests you are going to find in the project:
 @Test
 public void reflexive() {
  User x = newUser(1, "x");
  assertTrue(x.equals(x));
 }

 @Test
 public void logicalEquality() {
  User x1 = newUser(1, "x", "yellow");
  User x2 = newUser(1, "x", "yellow");

  assertTrue(x1.equals(x2) && x2.equals(x1));

  // consistent
  x1.setFavoriteColor("red");
  x2.setFavoriteColor("blue");
  assertTrue(x1.equals(x2) && x2.equals(x1));

  x2.setId(2);
  x2.setUsername("x2");
  assertFalse(x1.equals(x2));
 }

 @Test
 public void entityInSet() {
  Set<User> set = new HashSet<User>();
  set.add(newUser(1, "x"));
  set.add(newUser(1, "x"));
  assertEquals(1, set.size());
 }


 @Test
 public void differentManagersReturnSameEntity() {
  UserManager um1 = new UserManager(this);
  UserManager um2 = new UserManager(this);

  Set<User> set = new HashSet<User>();
  set.add(um1.getUser(1));
  set.add(um2.getUser(1));

  assertEquals(1, set.size());
 }
Etc..
You can download the simple equal/hashCode project here. Unzip it locally and import it in your IDE as a Maven project. You may also run directly the unit tests from a command line (mvn test).
Once imported, for example in Eclipse, you can run the Junit tests.

Running the tests with the default equals/hashCode implementation

Run as a junit test the DefaultUserIdentityTest class. You should have the following result :
As you can see most of the tests fail. The following logical identity test is not satisfied as you can expect.
 @Test
 public void logicalEquality() {
  User x1 = newUser(1, "x", "yellow");
  User x2 = newUser(1, "x", "yellow");

  assertTrue(x1.equals(x2) && x2.equals(x1));

  // consistent
  x1.setFavoriteColor("red");
  x2.setFavoriteColor("blue");
  assertTrue(x1.equals(x2) && x2.equals(x1));

  x2.setId(2);
  x2.setUsername("x2");
  assertFalse(x1.equals(x2));
 }
The default equals implementation is not sufficient. Let’s use a strategy based on the primary key that is the id property and see what happens.

Running the tests with an equals/hashCode strategy using the primary key

Run as a junit test the IdUserIdentityTest class.
package com.springfuse.blog.identity;

import java.io.Serializable;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * equals/hashCode strategy using the User primary key, the id property.
 */
public class IdEqualsStrategy implements EqualsStrategy, Serializable {
 static private final long serialVersionUID = 1L;
 static final private Log logger = LogFactory.getLog(IdEqualsStrategy.class);

 private User user;

 public IdEqualsStrategy(User user) {
  this.user = user;
 }

 public boolean doEquals(User other) {
  return user.getId() != null ? user.getId().equals(other.getId()) : false;
 }

 public int doHashCode() {
  if (user.getId() != null) {
   return user.getId().hashCode();
  } else {
   logger.warn("TODO: developer your code is not safe regarding hashCode", new Exception("stack trace"));
   return System.identityHashCode(user);
  }
 }
}
You should have the following result:
Almost all the tests pass now, except the one below:
 @Test
 public void setIdAfterAddingEntityInSetAndTryToRemoveIt() {
  Set<User> set = new HashSet<User>();
  User x = newUser("x");
  set.add(x);
  x.setId(1);
  set.remove(x);

  assertEquals(0, set.size());
 }
You may not encounter this scenario everyday, but with hibernate you might, for example when using saveOrUpdate(user). Imagine, if the user is added a Set before being saved in the database, you fall into this scenario. This lead us to use instead a "business key", that is a property or a set of properties that is unique, not null and rarely or never change and known before inserting the entity in the Set and in the database.

Running the tests with an equals/hashCode strategy using a business key

Run as a junit test the UsernameUserIdentityTest class.
package com.springfuse.blog.identity;

import java.io.Serializable;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * equals/hashCode strategy using a business key, the username property.
 */
public class UsernameEqualsStrategy implements EqualsStrategy, Serializable {
 static private final long serialVersionUID = 1L;
 static final private Log logger = LogFactory.getLog(UsernameEqualsStrategy.class);

 private User user;

 public UsernameEqualsStrategy(User user) {
  this.user = user;
 }

 public boolean doEquals(User other) {
  return user.getUsername() != null && user.getUsername().length() > 0 ? user.getUsername().equals(other.getUsername()) : false;
 }

 public int doHashCode() {
  if (user.getUsername() != null && user.getUsername().length() > 0) {
   return user.getUsername().hashCode();
  } else {
   logger.warn("TODO: developer your code is not safe regarding hashCode", new Exception("stack trace"));
   return System.identityHashCode(user);
  }
 }
}
You should have the following result :
All the tests pass now :-)
Note that the doHashCode() methods warns the developer if the username is not set.
 public int doHashCode() {
  if (user.getUsername() != null && user.getUsername().length() > 0) {
   return user.getUsername().hashCode();
  } else {
   logger.warn("TODO: developer your code is not safe regarding hashCode", new Exception("stack trace"));
   return System.identityHashCode(user);
  }
 }
Indeed, the hashCode is most likely called by a Set implementation and if the username is not yet set, you may break the Set contract if you set (sorry for this repetition) the username while the User is in the Set. You could be more violent and throw an exception instead to be sure to spot such pratice.
If you want to send us some code modifications, please use the following maven command to zip the project: mvn assembly:assembly -DdescriptorId=src -Dmaven.test.skip=true

Thursday, October 1, 2009

Why we switched from jaxb to jibx

Here is what you will find in this post :
  • xsd to java generation using jaxb2 with an maven project example
  • java to xsd generation using jaxb2
  • jaxb2 cannot generate an xsd with documentation
  • xsd from java generation using jibx with a maven project example
  • jibx customization is easy
  • add jibx xsd generation in maven with customization
  • how to use jibx using spring oxm

XSD authored manually

Continuing on my previous post on how to integrate your own xsd to ease your plugin configuration, I will talk today on something that really bugged me for a while. Some of our xsd are pretty large; we created them using xsd authoring tool, then continued by hand. We used jaxb to produce the java code to map the xsd, but the generated code was not that great. There are some ways to tweak the xjc plugin to bend the produced code the way you like. But this was way too awkward for us.

Jaxb generates XSD from java annotations

Then we thought that it was time to use the java code and annotate it with jaxb annotation so we could produce the xsd for our users to use. You annotate with the same few annotations here and there, and after this very tedious task you can add maven-jaxb-schemagen-plugin plugin to your pom and voilà you have a fresh xsd ready for production generated directly from your classes. But something is missing, looks like the generated xsd is very small compared to the manually authored one: jaxb did not insert any documentation ! Of course I thought that I did a silly mistake, and was ready to add something like @XmlDocumentation annotations. And here starts the story: there is no way to add documentation information to schemagen !

Jaxb cannot generate an XSD with configuration

This really surprised me, because the xsd are also here to help the users when they author an xml by showing documentation while they type etc. I really searched to find a way to use the tool; I even tried to dive into the schemagen source-code to see if there would be an existing annotation not present in the documentation. By the way, let me give a big thumb down to java.net for beeing so slow ! It was hell to navigate through the documentation on a site that is that slow. Wrong path: I was stuck. But there was no way I wanted to continue maintaining both the java and the xsd !

Enter jibx

Then I looked at the alternatives to jaxb, and quickly found out jibx http://jibx.sourceforge.net/ was the tool I was looking for! Bindgen is for jibx what maven-jaxb-schemagen-plugin is for jaxb. But instead of relying on annotation, bindgen rely solely on the class attributes and on the javadocs for producing the xsd and its documentation. Here is an example :
public class XSD {
 
 /** please define your complex object */
 private ComplexObject complexObject;
 /** you are allowed to set many simple objects */
 private List simpleObject = new ArrayList();
}
public class ComplexObject {
 /**
  * only string are allowed
  */
 private String stringAttribute;
 /**
  * This should be a date
  */
 private Date dateAttribute;
 /**
  * boolean are so over
  */
 private boolean thisIsABool = false;
 /**
  * enums are much better than booleans
  */
 private MyEnum myEnum;
}
Would generate the following xsd :

  
  
    
      this is my annotated xsd
    
    
      
        
          please define your complex object
        
        
          
            
              
                only string are allowed
              
            
            
              
                enums are much better than booleans
              
              
                
                  this is a enum, you need to choose between the values
                
                
                  
                  
                  
                  
                
              
            
          
          
            
              This should be a date
            
          
          
            
              boolean are so over
            
          
        
      
      
        
          
            
              
                trivial string
              
            
          
          
            
              trivial numeric
            
          
        
      
    
  

Perfect: all the documentation is present, it is taken directly from the javadoc! But if you follow closely the xsd, the stringAttribute and myEnum are not properties, they are defined as element. An xml would look like

    my string attribute
    B

You could also see that there is no mention on required, default values etc.

Jibx customization is easier than jaxb's

Bindgen is not that magic, we have to give it hints using customization, fortunately it is pretty straightforward thanks to a good documentation along with examples.

    
        
        
    

The only major pain was to make everything work in maven, in particular:
  • The BindGen setup using the ant task maven plugin and the correct dependencies
  • maven-jibx-plugin can use only relative path for its directory configuration point.
You can download and execute a working example here.

Jibx and spring OXM

And now jibx and Spring 3.0 oxm project! Oxm will help you to have a nice hierarchy of exception that wraps your underlying xml provider so you do not have to deal with it.

JibxMarshaller marshaller = new JibxMarshaller();
marshaller.setTargetClass(XSD.class);
marshaller.afterPropertiesSet();

// write
StringWriter writer = new StringWriter();
marshaller.marshal(xsd, new StreamResult(writer));
System.out.println(writer.toString());

// load 
(XSD) marshaller.unmarshal(new StreamSource(new FileInputStream(filename))
You have a unit test ready to be executed in the sample project Have fun with jibx !