Monday, November 30, 2009

Internal DSL for Criteria - part 2:3

In previous article I introduced the new internal DSL for JPA/Hibernate criteria. Let us look at how to handle the property names.

criteria().prop("lastName").eq("Svensson").build();
The problem with the above expression is that the property lastName is a String. Refactoring of Person.lastName will not be automatically detected. Another issue is that you have to remember/lookup that the property name is "lastName" when writing this expression, the IDE will not help you.

We, who are using Sculptor, define the domain object in the design model like this:

Entity Person {
String firstName
String lastName
}
From this Sculptor generates the Person class. We can also easily generate meta data of the properties of Person, which makes it possible to write expressions like this:

List<ConditionalCriteria> conditionalCriteria = ConditionalCriteriaBuilder.criteriaFor(Person.class)
.withProperty(PersonProperties.lastName()).eq("Svensson").build();
PersonProperties is generated and it contains static methods for each property, returning a Property<Person> object, which internally defines the strings. The builder is also parameterized, using the class defined in the criteriaFor factory method. This means that the withProperty method only accepts properties of correct type, i.e. Property<Person>.

Static imports can be used to make the expression more compact and readable:

import static org.fornax.cartridges.sculptor.framework.accessapi.ConditionalCriteriaBuilder.criteriaFor;
import static org.library.person.domain.PersonProperties.lastName;

List<ConditionalCriteria> conditionalCriteria = criteriaFor(Person.class)
.withProperty(lastName()).eq("Svensson").build();

Note that the eclipse keyboard shortcut for static import is <ctrl+shift+m> (mac: <cmd+shift+m>).

The expression builder looks like this:

public class ConditionalCriteriaBuilder<T> {

private final List<ConditionalCriteria> criteriaList = new ArrayList<ConditionalCriteria>();

private ConditionalCriteriaBuilder() {
}

public static <T> ConditionalCriteriaBuilder<T> criteriaFor(Class<T> clazz) {
return new ConditionalCriteriaBuilder<T>();
}

public List<ConditionalCriteria> build() {
return criteriaList;
}

private void addCriteria(ConditionalCriteria criteria) {
criteriaList.add(criteria);
}

public CondditionProperty<T> withProperty(Property<T> property) {
return new PropBuilderImpl(property.getName());
}

public interface CondditionProperty<T> {
ConditionalCriteriaBuilder<T> eq(Object value);
}

private class PropBuilderImpl implements CondditionProperty<T> {
String propertyName;

PropBuilderImpl(String name) {
this.propertyName = name;
}

public ConditionalCriteriaBuilder<T> eq(Object value) {
addCriteria(ConditionalCriteria.equal(propertyName, value));
return ConditionalCriteriaBuilder.this;
}
}
}


Alright, let us refactor the model, and introduce a BasicType for the first and last name of the Person:


Entity Person {
- @PersonName name
}

BasicType PersonName {
String first
String last
}


Generate, and you will immediately detect compilation error in the criteria expression. Fix it and it looks like this:

List<ConditionalCriteria> conditionalCriteria = criteriaFor(Person.class)
.withProperty(name().last()).eq("Svensson").build();


The generated Properties classes defines refererences also, so it easy to navigate associations with full code completion support. Great, our goals of code completion and refactoring are fulfilled.

That is not the end of this series of articles. In next article I will illustrate some more advanced operators that requires some intellectual thoughts. See you tomorrow.

No comments:

Post a Comment