November 1, 2009

Integrate JSR 303 Bean Validation And ApacheWicket

In my previous blog I was writing about the new promising standard JSR 303 Bean Validation. In this article I will write about JSR 303 Bean Validation and Apache Wicket. Let start with recapitulate the domain object Person and see how we used the Bean Validation annotation to annotate our property constraints.
package se.msc.examples.validation.domain;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

@Entity
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long personId;
@NotNull
@Size(min = 1, max = 250)
@Column(length = 250)
private String givenName;
@NotNull
@Size(min = 1)
@Column(length = 250)
private String surname;
@NotNull
@Pattern(regexp = ".+@.+\\.[a-z]+")
@Column(length = 250)
private String mail;

public Long getPersonId() {
return personId;
}

protected void setPersonId(Long personId) {
this.personId = personId;
}

public String getGivenName() {
return givenName;
}

public void setGivenName(String givenName) {
this.givenName = givenName;
}

public String getSurname() {
return surname;
}

public void setSurname(String surname) {
this.surname = surname;
}

public String getMail() {
return mail;
}

public void setMail(String mail) {
this.mail = mail;
}

}

This time I also have added sun's JPA annotation, because this is a domain object and sooner we want the domain objects to be persisted. We also the see that there is an overlap between the Bean Validation annotation and the JPA annotation. My approach to these is to use an open constraint approach for persistence and a narrowing/stricter definition with the Bean Validation for the domain layer. This way enables us to have a more flexible database, DBA would probably argue that this leads to bad data quality, but since we are building in validation directly in our domain object and writing small and easy to run unit test, to test these validation, there are no risk against bad data quality slipping through to the persistence layer. Actually we are protecting bad data to even reach the persistence layer long before, first in the presentation layer, but also we will use the same Bean Validation in the service layer. And all validation using the same code, the Bean Validation annotation, no more inventing the wheel over and over again in the different layers.

So how do we use the Bean Validation with the Apache Wicket? Well, there is no out-of-box integration, so I have written a few helper classes that will do that for us. These helper classes are actually worth using already how ever you will be using Bean Validation or not in your next project, because Apache Wicket is really to low-tech, like Swing, and when writing Apache Wicket classes from ground up requires you to write quite lengthy masses of code, this is not good. So lets start with our helper classes for writing org.apache.wicket.markup.html.form.Form.

package se.msc.examples;

import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.LoadableDetachableModel;

public class FormBuilder<T> extends Form<T> {
private static final long serialVersionUID = 1L;
public static final String SUBMIT = "submit";

public FormBuilder(final String id, final T modelObject) {
super(id, new CompoundPropertyModel<T>(new LoadableDetachableModel<T>(
modelObject) {
private static final long serialVersionUID = 1L;

protected T load() {
return modelObject;
}
}));
}

public void addTextField(final String propertyName) {
TextField<T> textField = new TextField<T>(propertyName);
textField.add(new BeanValidator<T>(getModelObject(), propertyName));
add(textField);
}

public void addSubmitButton(final FormButtonListener<T> listener) {
final FormBuilder<T> parent = this;
add(new Button(SUBMIT) {
private static final long serialVersionUID = 1L;

public void onSubmit() {
parent.execSubmit(listener);
}
});
}

public Form<T> create() {
return this;
}

public void execSubmit(final FormButtonListener<T> listener) {
listener.onSubmit(getModelObject());
}

}





package se.msc.examples;

import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;

import org.apache.log4j.Logger;
import org.apache.wicket.validation.INullAcceptingValidator;
import org.apache.wicket.validation.IValidatable;
import org.apache.wicket.validation.ValidationError;

public class BeanValidator<T> implements INullAcceptingValidator<T> {
private static final long serialVersionUID = 1L;
private static final Logger log = Logger.getLogger(BeanValidator.class);
private final Class<T> beanClass;
private final String propertyName;

@SuppressWarnings("unchecked")
public BeanValidator(final T beanObject, final String propertyName) {
this.beanClass = (Class<T>) beanObject.getClass();
this.propertyName = propertyName;
}

@Override
public void validate(IValidatable<T> validatable) {
log.info("validate... " + validatable.getValue());
ReflectionUtil<T> util = new ReflectionUtil<T>();
T beanObject = util.createInstance(beanClass);
util.setPropertyValue(beanObject, propertyName, validatable.getValue());
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<T>> violations = validator.validateProperty(beanObject, propertyName);
for (ConstraintViolation<T> violation : violations) {
String propertyPath = violation.getPropertyPath().toString();
String message = violation.getMessage();
log.error("invalid value for: '" + propertyPath + "': " + message);

ValidationError validationError = new ValidationError();
validationError.setMessage(message);
validatable.error(validationError);
}  
}

@Override
public String toString() {
return "[BeanValidator beanObject='" + beanClass + "']";
}

}

Lets now see how we use it in our Apache Wicket controller class.

package se.msc.examples;

import org.apache.log4j.Logger;
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.panel.FeedbackPanel;

import se.msc.examples.validation.domain.Person;

public class PersonEditPage extends WebPage implements FormButtonListener<Person> {
private static final long serialVersionUID = 1L;
private static final Logger log = Logger.getLogger(PersonEditPage.class);
protected static final String FEEDBACK_PANEL = "feedbackPanel";
protected static final String PERSON_FORM = "personForm";
protected static final String GIVEN_NAME = "givenName";

public PersonEditPage(final PageParameters parameters) {
log.info("PersonPage...");
add(new FeedbackPanel(FEEDBACK_PANEL));
add(createForm(new Person()));
}

private Form<Person> createForm(Person person) {
FormBuilder<Person> builder = new FormBuilder<Person>(PERSON_FORM, person);
builder.addTextField(GIVEN_NAME);
builder.addSubmitButton(this);
return builder.create();
}

@Override
public void onSubmit(Person modelObject) {
log.info("onSubmit " + modelObject);
info("Successfully created '" + modelObject.getGivenName() + "'.");
}

}

And the accompanying html file

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd"
xml:lang="en" lang="en">
<head>
<title>Person Edit Page</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>

<div id="feedbackPanel" wicket:id="feedbackPanel">Feedback Panel</div>
<form id="personForm" wicket:id="personForm">
<fieldset style="width: 30em">
<legend>Person Edit</legend>
<table border="1" style="width: 100%">
<tr>
<td align="right">Given Name:</td>
<td><input id="givenName" wicket:id="givenName" type="text" style="width: 98%" /></td>
</tr>
<tr>
<td colspan="2" align="right">
<button id="submit" wicket:id="submit" type="submit">Submit</button>
</td>
</tr>
</table>
</fieldset>
</form>

</body>
</html>

Now lets write a simple test case to test our Apache Wicket validation.

package se.msc.examples;

import junit.framework.TestCase;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.util.tester.FormTester;
import org.apache.wicket.util.tester.WicketTester;

public class PersonEditPageTest extends TestCase {

  private WicketTester tester;

  @Override
  public void setUp() throws Exception {
    tester = new WicketTester(new WicketApplication());
    tester.startPage(PersonEditPage.class);
    tester.assertRenderedPage(PersonEditPage.class);
  }

  public void testLayout() throws Exception {
    tester.assertComponent(PersonEditPage.FEEDBACK_PANEL,
    FeedbackPanel.class);
    tester.assertComponent(PersonEditPage.PERSON_FORM, Form.class);
  }

  public void testCreate_OK() throws Exception {
    FormTester form = tester.newFormTester(PersonEditPage.PERSON_FORM);
    form.setValue(PersonEditPage.GIVEN_NAME, "Magnus");
    form.submit(FormBuilder.SUBMIT);
    tester.assertRenderedPage(PersonEditPage.class);
    tester.assertNoErrorMessage();
  }

  public void testCreate_GIVENNAME_FAIL() throws Exception {
    FormTester form = tester.newFormTester(PersonEditPage.PERSON_FORM);
    form.setValue(PersonEditPage.GIVEN_NAME, "");
    form.submit(FormBuilder.SUBMIT);
    tester.assertRenderedPage(PersonEditPage.class);
    tester.assertErrorMessages(new String[] { "Field givenName is required." });
    form = tester.newFormTester(PersonEditPage.PERSON_FORM);
    form.setValue(PersonEditPage.GIVEN_NAME, " ");
    form.submit(FormBuilder.SUBMIT);
    tester.assertRenderedPage(PersonEditPage.class);
    tester.assertErrorMessages(new String[] { "Field givenName is required." });  
  }

}

October 19, 2009

How to Install Swedish Dictionary and Thesaurus for Open Office Writer

If you have installed a English distribution of Ubuntu you have already Open Office installed, but with no Swedish dictionary or Thesaurus. Do this to install:
  • Open Office Writer.
  • Select Tools->Language->More Dictionaries Online...
  • Download the two wanted files Dictionary and Thesaurus. Now you have to oxt-files on your computer.
  • Select Tools->Extension Manager and click Add then select oxt-file.
  • Restart Open Office and from Tools->Language->For All Text->More. In the window select installed language for selected document.

Clearlook Theme for Ubuntu – Clearlooks OSX

In the beginning I found it quite hard to find new themes, that was compatible with the standard Ubuntu way to install theme, i.e. from System->Preferences->Appearance.

But yesterday I found a nice OSX look that is not so grey as Mac OSX, it is also packaged in the standard Ubuntu installation way.

http://www.gnome-look.org/content/show.php/ClearlooksOSX?content=69487

How to Enable Code Completion and Syntax Control Against XML Schema with Eclipse

When I was writing Apache Camel configuration XML files, I was frustrated over that I had no code completion or XML syntax check against Apache Camel Schema, but of source is that doable in Eclipse. Simple download the Schema to your computer and then open Eclipe:
Window->Preferences->XML->XML Catalog
Click on Add and File System and select the downloaded Schema.
Now you can right-click in your XML file and select Validate.

September 29, 2009

Getting rid of notification “Network service discovery disabled” in Ubunut 9.04

After installing the latest Ubuntu I have haunted with the annoying notification “Network service discovery disabled. You current network has a .local domain, which is not recommended and incompatible with the Avahi network service discovery. The service has been disabled.”

First of all I have to google what Avahi is and found the following short summary here: https://help.ubuntu.com/community/HowToZeroconf

After that, I got rid of the notification message, by removing the below file and then starting the Avahi service, since some files where not properly auto-created during installation.
$ sudo rm /var/run/avahi-daemon/disabled-for-unicast-local
$ sudo /etc/ini.d/avahi-daemon start

After that I disabled the Avahi service, because I could not see any reason to autostart it when Ubuntu booted. You can disabled auto-services from System->Administration->Services. The last thing I did was to reboot and the problem was gone.