ALBERLAU blog

Blog about my personal experience in software development.

Friday, March 20, 2009

 

AnnotationsBasedValidator for JPA entities in SpringFramework

Despite there is Hibernate Validator, I wrote my own. It is adopted to use with SpringFramework WEB MVC framework. Why I not used Hibernate Validator? In Hibernate we have @NotNull, @Length annotations and in @Column we have simmilar annotations. I think that violates DRY principle. Result of Hibernate Validator is array of InvalidValue. For Spring it is better to have messages in ResultBinding.

Usage example:




protected IAnnotationsBasedValidator annotationsBasedValidator;

@Autowired
public void setAnnotationsBasedValidator(IAnnotationsBasedValidator annotationsBasedValidator) {
this.annotationsBasedValidator = annotationsBasedValidator;
}




then somewhere in your code:




annotationsBasedValidator.validate(payment, bindingResult);




As a result bindingResult is filled with validation messages(if there is violated constraints for annotations).

This validator fully utilizes JPA @Column length, nullable, precission, scale, @JoinColumn nullable, @OneToOne , @Embedded. Nested objects for @JoinColumn , @OneToOne , @Embedded is also validated traversing full object tree, except those who is not !propertyInitializationInfo.isInitializaed(cs) (lazy properties).

Additionally it supports annotations: RegexChecker, RangeChecker, BooleanResultChecker to call static method to validate value.

Validator source

Tuesday, March 10, 2009

 

Decorating list objects dynamically to display calculated fields

Imagine there is Account class that holds IBAN, currency, and balance. Customer wants to display combo of accounts that displays options in format LT025010200020000141 LTL(1000.23) . Most quick solution probably is to place :

getDisplayInFormat1() { return iban+" "+currency+"("+balance+")";}

method inside of Acount class. Of course Account class will soon be overwhelmed with getDisplayInFormat2, getDisplayInFormat3 and so on methods with view related information, possibly with style:red to indicate insufficient balance.

Next solution is to create some DropDownAccountDecorator. Usage is below:




List<Account> acc = cpFacade.loadAccounts(user);
List decoratedAcc = ListDecorator.getInstance(new DropDownAccountDecorator(), acc);




DropDownAccountDecorator source :



import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class DropDownAccountDecorator implements IListItemDecorator,Serializable {
private boolean hideBalance;

@Override
public Object decorate(final Object o) {
return Enhancer.create(o.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method mth, Object[] args, MethodProxy mpx) throws Throwable {
Object res = mth.invoke(o, args);
String mname = mth.getName();
if (!mname.equals("getDisplay")) {
return res;
}
Account acc = (Account) obj;
String display;
if (AccStates.BLOCKED.equals(acc.getAccState())) {
display = String.format("%s (%1.2f %s blocked)", acc.getName(), acc.getTotalBalance(), acc.getCurrencyDetail().get(0).getCurrency().getCode());
} else {
if(hideBalance) {
display =  String.format("%s %s", acc.getName(), acc.getCurrencyDetail().get(0).getCurrency().getCode());
} else {
display = String.format("%s (%1.2f %s)", acc.getName(), acc.getTotalBalance(), acc.getCurrencyDetail().get(0).getCurrency().getCode());
}
}
return display;
}
});
}

public void setHideBalance(boolean b) {
this.hideBalance=b;
}
}




Code uses ListProxy infrastructure from my previous post.
 

Workaround for SpringFramework NullValueInNestedPath

I faced springframework famous NullValueInNestedPath problem. I found one out of the box solution to workaround this problem. The solution is to create decorator who wraps list and creates null objects lazily jus for display. There is LegacyTransaction object who owns one payer or one payee or both of class Participant. In most cases there is null payer or payee. Of course we can create somewhere those objects. But in case of hibernate they will be persisted. Create fake objects, just to display correct data is not right solution. Example usage is as follows.





List<LegacyTransaction> tr = bnkPmtFacade.loadLegacyTransactions(tf, user, br);
List trDeco=ListDecorator.getInstance(new NullSafeParticipantDecorator(), tr);
mm.addAttribute(BnkPmtSessionKeys.transactions, trDeco);






Actual decorator to create fake objects NullSafeParticipantDecorator.
Source is below :




import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;






public class NullSafeParticipantDecorator implements IListItemDecorator,Serializable{

@Override
public Object decorate(final Object o) {
return Enhancer.create(o.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method mth, Object[] args, MethodProxy mpx) throws Throwable {
Object res = mth.invoke(o, args);
String mname = mth.getName();
if (!mname.equals("getPayer") && !mname.equals("getPayee")) {
return res;
}
LegacyTransaction lt=(LegacyTransaction)o;
if(mname.equals("getPayer")) {
if(lt.getPayer()!=null) {
return lt.getPayer();
} else {
return new Participant();
}
}
if(mname.equals("getPayee")) {
if(lt.getPayee()!=null) {
return lt.getPayee();
} else {
return new Participant();
}
}
throw new RuntimeException("Should never get this!");
}
});
}
}




Below is classes that makes easer to create such kind of decorators
ListDecorator source:



import java.lang.reflect.Proxy;
import java.util.List;

public class ListDecorator {
public static List getInstance(IListItemDecorator deco,List list) {
return (List)Proxy.newProxyInstance(List.class.getClassLoader(), new Class[]{List.class}, new ListProxy(list,deco));
}
}




IListItemDecorator source:



public interface IListItemDecorator {
Object decorate(Object o);
}




ListProxy source:



import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Iterator;
import java.util.List;

public class ListProxy implements InvocationHandler {

private IListItemDecorator deco;
private List list;

public ListProxy(List list,IListItemDecorator deco) {
this.list=list;
this.deco = deco;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res;
if (method.getName().equals("iterator")) {
res = Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Iterator.class}, new IteratorProxy(list.iterator(), deco));
} else if(method.getName().equals("get")) {
res=deco.decorate(list.get((Integer)args[0]));
} else {
if(list==null) {
res=null;
} else {
res=method.invoke(list, args);
}
}
return res;
}
}




IteratorProxy source:



import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Iterator;

public class IteratorProxy implements InvocationHandler{
private IListItemDecorator deco;
private Iterator iterator;

public IteratorProxy(Iterator iterator, IListItemDecorator deco) {
this.iterator=iterator;
this.deco=deco;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res;
if(method.getName().equals("next")) {
res=deco.decorate(iterator.next());
} else {
res = method.invoke(iterator, args);
}
return res;
}
}



Wednesday, February 20, 2008

 

International characters in JEXL

When using i18n chars in string's i'm getting

org.apache.commons.jexl.parser.TokenMgrError: Lexical error at line 1,
column 17. Encountered: "\u0139" (313), after : "\'U"

Because of this problem in JEXL i compiled jar which solves that problem.


Below is Randy H. solution pasted from forum how to fix things.

1. Download and unzip jexl 1.1 source from:
http://www.axint.net/apache/jakarta/commons/jexl/source/commons-jexl-1.1-src.zip

2. Download and unzip JavaCC 4.0 from:
https://javacc.dev.java.net/files/documents/17/26777/javacc-4.0.zip

3. Run javacc-4.0\bin\javacc -UNICODE_INPUT
commons-jexl-1.1-src\src\java\org\apache\commons\jexl\parser\Parser.jj

4. Copy *.java to
commons-jexl-1.1-src\src\java\org\apache\commons\jexl\parser

5. Run ant -f commons-jexl-1.1-src\build.xml

6. Use the new jar commons-jexl-1.1-src\target\commons-jexl-1.1.jar

Labels: ,


Friday, February 01, 2008

 

JasperReports issues

Textfield border is not visible when rendering empty string using html exporter
When drawing table in JasperReports i'm not using lines, but placing text or static fields with borders set on them. Problem occures when there is empty string. Border is not rendered then. I think it is common problem to html, because in html tables you need to place & n b s p ; . In JasperReports nbsp will be rendered as nbsp in pdf exporter as well in html. If cell is empty you can place space instead of empty string. But what in case BigDecimal, Date or some other type?
The source causing this problem is in net.sf.jasperreports.engine.export.JRHtmlExporter .
emptyCellStringProvider =
new StringProvider()
{
public String getStringForCollapsedTD(Object value, int width, int height, String sizeUnit)
{
return " style=\"width: " + width + sizeUnit + "; height: " + height + sizeUnit + ";\">";
}
public String getStringForEmptyTD(Object value)
{
return "";
}
};

It is not possible to specify other emptyCellStringProvider. At least in 1.3.4 and 2.0.4 versions.
I think the line returning empty string should look as follows:
public String getStringForEmptyTD(Object value)
{
return "&nbsp;";
}


Unfortunately i was unable to findout other way than to download sources, fix problem and make jar by myself.

Labels:


Friday, January 25, 2008

 

iReport 2.0.4 and 1.3.0 compatibility issue

My collegue uses iReport plugin on NetBeans, as he said iReport version is 1.3.3. After i opened report design that he created, it looked like below.











I figured out that the following line causes the problem:

?xml version="1.0" encoding="UTF8" ?>
!DOCTYPE jasperReport PUBLIC "-//JasperReports//DTD JasperReport//EN" "http://jasperreports.sourceforge.net/dtds/jasperreport.dtd"

iReport 2.0.4 when having above line completely overwrites design xml with new blank report having width and height equal to 0.



After changing header line to :

?xml version="1.0" encoding="UTF-8" ?>
!DOCTYPE jasperReport PUBLIC "//JasperReports//DTD Report Design//EN" "http://jasperreports.sourceforge.net/dtds/jasperreport.dtd">


the problem becomes resolved. It seems that iReport expects DOCTYPE of "DTD Report Design", but not "DTD JasperReport".

Labels: ,


Thursday, January 24, 2008

 

Playing with Maven and AppFuse

Appfuse allows to build application skeletons quickly. First choose archetype, or combination of app frameworks, because of maven all dependencies, config files and samples will be installed with single line.

Below is my personal notes taken while experimenting with appfuse application framework. I'm choosing modular spring and jsf archetype.

Change to your projects root. Type : mvn archetype:create -DarchetypeGroupId=org.appfuse.archetypes -DarchetypeArtifactId=appfuse-modular-jsf -DremoteRepositories=http://static.appfuse.org/releases -DarchetypeVersion=2.0.1 -DgroupId=com.mycompany.myproject -DartifactId=myproject


After this command, type cd myproject, edit pom.xml and change jdbc settings located at the end of the file. Then type mvn. Project build starts. At most cases you will get BUILD ERROR caused something like: Could not connect to SMTP host: localhost, port: 25. To fix situation it is easest way to get James from http://james.apache.org/ . You only need to download and start it. Start mvn again. Now i'm getting BUILD SUCCESSFUL.


Now type cd web and mvn jetty:run-war . Web application becomes available at http://localhost:8080/ . Press Ctrl+C to stop jetty.


Type cd ../core. Type mvn appfuse:gen-model . Model classes is generated from database tables.



There is a list of AppFuse Maven commands at this link: http://www.appfuse.org/display/APF/AppFuse+Maven+Plugin .


Trying to generate CRUD stuff for mvn appfuse:gen -Dentity=Message and getting java.lang.StringIndexOutOfBoundsException: String index out of range: 1. I resolved this problem by performing mvn in project root and mvn appfuse:gen -Dentity=Message both in core and web modules.

Getting "Error executing database operation: CLEAN_INSERT". Mostly this problem occures when there is a problems with example data or bean-table mapping. It is consequence of SQLException. First look at YOUR_PROJECT_ROOT\(webcore)\src\test\resources\sample-data.xml and try to fix example data. If all is ok, check your console after mvn run for SQL errors and then fix some bean in YOUR_PROJECT_ROOT\core\app\model\SomeBean.java .

The first thing in case of Windows is to change Maven repository path to something without spaces in path. Edit MAVEN_HOME/conf/settings and add or change c:/apache-maven-2.0.8/repo line. That should be done because sooner or later you'll get : Illegal character in path at index 18: file:/C:/Documents and Settings . I got this while executing mvn appfuse:gen-core . After changfing this you can move Maven repository located at C:/Documents and Settings/.m2 to a new location.

Second issue. I got java.io.FileNotFoundException: Could not open ServletContext resource [/WEB-INF/xfire-servlet.xml] . After googling i find out that i need to run mvn jetty:run-war instead of mvn jetty:run.

Now i'm dealing with javax.el.ELException: ELResolver cannot handle a null base Object with identifier 'errors' . I find out solution to use mvn -Dcargo.wait . Use -Dmaven.test.skip=true if tests fail and you don't want to fix them. Currently i have no idea what kind of thing is cargo. But it started tomcat instead of jetty. Also running in tomcat requires context path like this: http://localhost:8081/myproject-webapp-1.0-SNAPSHOT/mainMenu.html .

Getting javascript error when selecting to edit Branch. I generated branches using mvn appfuse:gen -Dentity=Name . The list is looking ok, but going to edit i getting "Object doesn't support this property or method". The main problem was that mvn appfuse:gen -Dentity=Name makes file named branchs.xhtml, but in all config files it generates branches.xhtml . Rename file to branches to fix the problem.


[INFO] Use of @OneToMany or @ManyToMany targeting an unmapped class: org.eparty.model.Branch.members[org.eparty.model.Member]

The above problem is caused by missing entries in hibernate.cfg.xml.



java.lang.NoClassDefFoundError: org/ep/webapp/filter/LocaleFilter (wrong name: org/appfuse/webapp/filter/LocaleFilter) or something like someYourPackage.yourClass (wrong name: org/appfuse/webapp/filter/SomeAppfuseClass) . I changed all these classes to appfuse classes.


mvn appfuse:full-source . Dont't do this. Or do after full backup of what you have. At least with 2.0.1 version. After making efforts to do making working application, this will break your code. It is better to grab appFuse core classes in to project. More info about that: http://appfuse.org/display/APF/AppFuse+Core+Classes .

Thursday, January 10, 2008

 

Oracle JDBC oracle.sql.TIMESTAMP issue

Because of this magics it's possible to spend whole day while finding solution. The problem was java.lang.ClassCastException: oracle.sql.TIMESTAMP cannot be cast to java.sql.Timestamp on Tomcat, while on OC4J application was running fine. As I supposed ResultSet was returning oracle.sql.TIMESTAMP instead of java.sql.Timestamp. If oracle.sql.TIMESTAMP were extending java.sql.Timestamp there would be no problem. But it extends oracle.sql.Datum. I tried different Oracle JDBC versions, starting from classes12.jar ending ojdbc6.jar . The problem remains unsolved. After deeper research i found that Oracle JDBC uses oracle.jdbc.J2EE13Compliant property to determine what to return : oracle.sql.TIMESTAMP or java.sql.Timestamp. In case when this property is false, Oracle JDBC becomes not JDBC compatible, because oracle.jdbc.driver.TimestampAccessor returns oracle.sql.TIMESTAMP. There is some abstract info about oracle.jdbc.J2EE13Compliant in Oracle JDBC FAQ . There is more issues related to this property. In case of oracle.jdbc.J2EE13Compliant==true and precission not equals zero and scale is minus 127 oracle.jdbc.driver.NumberCommonAccessor returns Double in other cases it will return BigDecimal . The fastest and easest way to solve this problem is to add -Doracle.jdbc.J2EE13Compliant=true to your Tomcat or any other "not J2EE13Compliant" application server startup properties. It's a strange ways to get compatibilities :)

Labels: , , , , ,


Archives

January 2008   February 2008   March 2009  

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]