Tuesday, April 15, 2008

A quick update on JPA Boolean Magic Converter

Some update on changes:
  • Fixed the bug of Null pointer exception, when JPA return null on the annotated field.
  • Introduce a new properties call ifNull, which allows user to configure what to return if JPA returns null, it expect enum of org.jbpcc.util.jpa.ReturnType, which have values of ReturnType.True, ReturnType.FALSE, and ReturnType.Null,. The default value of ifNull is ReturnType.Null
Thus as an example, assuming we have model class define as below:

package org.jbpcc.domain.model;

import javax.persistence.Entity;
import javax.persistence.Id;
import org.jbpcc.util.jpa.BooleanMagic;
import org.jbpcc.util.jpa.BooleanMagic.ReturnType;

@Entity
public class SomeVO {
@Id
private Integer id;
@BooleanMagic(trueValue = "Yes", falseValue = "No",
columnName = "OVERDUED", ifNull = ReturnType.FALSE)
private transient Boolean overdued;

public Boolean isOverdued() {
return overdued;
}

public void setOverdued(Boolean overdued) {
this.overdued = overdued;
}
}

Java APT with JPABooleanMagicConverter factory, code above will be converted to:

@Entity
public class SomeVO {
@Id
private Integer id;
private transient Boolean overdued;
//--- Lines below are generated by JBPCC BooleanMagicConvertor PROCESSOR
//--- START :

@Column(name="OVERDUED")
private String magicBooleanOverdued;


public Boolean isOverdued() {
if (this.magicBooleanOverdued == null)
return false;
return this.magicBooleanOverdued.equals("Yes") ? Boolean.TRUE : Boolean.FALSE;
}

public Boolean getOverdued() {
if (this.magicBooleanOverdued == null)
return false;
return this.magicBooleanOverdued.equals("Yes") ? Boolean.TRUE : Boolean.FALSE;
}

public void setOverdued(Boolean trueFlag) {
this.magicBooleanOverdued = trueFlag ? "Yes" : "No";
}
//--- END
//--- GENERATED BY JBPCC BooleanMagicConvertor PROCESSOR

}


That's all, as usual, you could find the JPABooleanMagicConverter binary at http://code.google.com/p/jbpcc/downloads/list.

Do share me your thoughts and comments, happy coding

Sunday, April 13, 2008

A portable JPA Boolean Magic Converter

While doing some small POC with JPA, I am surprise to find out that the current Java Persistence API (JPA) standard does not mandate JPA provider to support data type conversion via annotation, not even a with simple boolean field. For reader who unfamiliar with JPa, what I means is, to persist and "JPA" manage a boolean field, JPA expect the database data type to be integer, where value of "1" means true, and value of "0" means false.

You got to be joking, right? Every "real life" programmer knows that, there are many existing database use different combination of char or string on a table boolean field, such as "True/False", "T/F", "Yes/No", "Y/N", "-1/0", and etc. Thus, as a "real life" programmer, I kind expect the JPA should allows me specific a boolean field via Annotation, such as example below:

@booleanField(trueValue="Yes", falseValue="No")
private Boolean enabled;

And to my surprise, the answer is nope!!!..So far only Hibernate JPA provider provide data type conversation. Daniel Pfeifer, have explain the problem in details, he also discuss some solutions to work around this stupid limitation at his blog, title "Type Conversation with JPA", do check it out.

Daniel's "manual mapping" idea, do solve the limitation. However, maybe it's just me, I just don't like the idea to temporary change a Boolean field to map with database data type (either char, string) in order to resolve the limitation, even we make the field private, this is just me. And I am stubborn, and old . :-)

Anywhere, after some research (Also, a good excuse to my manager to research how difficult to come out our own custom Java 5 annotation with custom Java APT process factory), I come out our own compile time annotation, called @BooleanMagic with our own BooleanMagicProcessorFactory, which I hope, shall temporary resolve the problem.

@BooleanMagic comprise of three attributes:
  • trueValue, specified the table boolean field true value here, such as "True", "T", "Yes"
  • falseValue, specified the table boolean field false value here, such as "False", "F","N"
  • columnName, tableColumn name;
Also please note that BooleanMagicProcessorFactory mandate any field annotated with @BooleanMagic annotation, must either annotated with @Transient annotation or with modifier transient.

Thus, as an example, assuming we have a boolean field called "enabled", which map to table "ENABLED" field, with boolean value of "True"/"false", we will specified our VO as below:

@Entity
public class SomeVO {
@Id
Integer id;
@BooleanMagic(trueValue="True", falseValue="false", columnName="ENABLED")
private transient Boolean enabled;

public Boolean isEnabled() {
return enabled;
}

public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
}

Using Java "APT" tool with JPABooleanMagicConverter factory, code above will be converted to:


@Entity
public class SomeVO {
@Id
Integer id;

private transient Boolean enabled;
//---
//--- Lines below are generated by JBPCC BooleanMagicConvertor PROCESSOR
//--- START :

@Column(name="ENABLED")
private String magicBooleanEnabled;

public Boolean isEnabled() {
return this.magicBooleanEnabled.equals("True") ? Boolean.TRUE : Boolean.FALSE;
}

public Boolean getEnabled() {
return this.magicBooleanEnabled.equals("True") ? Boolean.TRUE : Boolean.FALSE;
}

public void setEnabled(Boolean trueFlag) {
this.magicBooleanEnabled = trueFlag ? "True" : "false";
}

//--- END
//--- GENERATED BY JBPCC BooleanMagicConvertor PROCESSOR
}


Now, isn't that cool (haha, sorry, I am bit biased here). Btw, BooleanMagicConvertor is portable across JPA protable, and IMHO, this is a better interim solution till JPA make data conversation standard.

I decided to contribute my BooleanMagicConverter back to open source community, it's currently park under my open source project Java Batch Process Control Center, at http://code.google.com/p/jbpcc, you could either build from the source, or get the "BooleanMagicProcessorFactory.jar" from here.

Here an example of how to use the BooleanMagicConvertor using Ant 1.7 apt tasks, assuming all your entity class is declare at ${src.dir}/org/abc/domain/model directory, and you wants to output all generated call to ${basedir}/target directory
 <target name="preProcessJPABoolean" depends="buildJPABoolenProcessorFactory">
<apt srcdir="${src.dir}/org/abc/domain/model"
destdir="${build.classes.dir}"
classpath="./dist/BooleanMagicProcessorFactory.jar:./lib/Model_Dependent_thirdparty.jar"
debug="on"
compile="false"
factory="org.jbpcc.util.jpa.BooleanMagicProcessorFactory"
preprocessdir="${basedir}/target">
</apt>
</target>

That's all, do share me your thoughts and comments, cheers!