Tuesday, December 1, 2009

Internal DSL for Criteria - part 3:3

In previous 2 articles I have described the basics of the new internal DSL for JPA/Hibernate criteria. Let us dive in to an advanced topic. We would like to be able to specify conditional expressions with logical operators, and with grouping of nested conditions.

// A and (B or C)
criteriaFor(Person.class).withProperty(aaa()).eq("A").and().lbrace()
.withProperty(bbb()).eq("B").or().withProperty(ccc()).eq("C").rbrace()
.build();


That should build a syntax tree like:

and
/ \
A or
/ \
B C


This means that I need to hold state of current and previous not completed operators in the builder. I hold the current operator in a stack and give braces a special meaning. The expression builder looks like this:


public class ConditionalCriteriaBuilder<T> {

private enum ExpressionOperator {
Not, Or, And, LBrace
}

private ConditionalCriteriaBuilder() {
}

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

public interface ConditionRoot<T> {
List<ConditionalCriteria> build();
CondditionProperty<T> withProperty(Property<T> property);
ConditionRoot<T> and();
ConditionRoot<T> or();
ConditionRoot<T> not();
ConditionRoot<T> lbrace();
ConditionRoot<T> rbrace();
}

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

public class RootBuilderImpl implements ConditionRoot<T> {
private final SimpleStack<ConditionalCriteria> criteriaStack = new SimpleStack<ConditionalCriteria>();
private final SimpleStack<ExpressionOperator> operatorStack = new SimpleStack<ExpressionOperator>();

/**
* End the expression with this build
*/
public List<ConditionalCriteria> build() {
return criteriaStack.asList();
}

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

public ConditionRoot<T> and() {
operatorStack.push(ExpressionOperator.And);
return this;
}

public ConditionRoot<T> or() {
operatorStack.push(ExpressionOperator.Or);
return this;
}

public ConditionRoot<T> not() {
operatorStack.push(ExpressionOperator.Not);
return this;
}

public ConditionRoot<T> lbrace() {
operatorStack.push(ExpressionOperator.LBrace);
return this;
}

public ConditionRoot<T> rbrace() {
operatorStack.pop();
if (criteriaStack.isEmpty()) {
return this;
}
ConditionalCriteria lastCriteria = popCriteria();
pushCriteria(lastCriteria);
return this;
}

private ConditionalCriteria popCriteria() {
return criteriaStack.pop();
}

private void pushCriteria(ConditionalCriteria criteria) {
ExpressionOperator currentOperator = operatorStack.peek();
if (currentOperator == ExpressionOperator.Or || currentOperator == ExpressionOperator.And) {
ConditionalCriteria compositeCriteria;
if (currentOperator == ExpressionOperator.Or) {
compositeCriteria = ConditionalCriteria.or(popCriteria(), criteria);
} else {
compositeCriteria = ConditionalCriteria.and(popCriteria(), criteria);
}
criteriaStack.push(compositeCriteria);
operatorStack.pop();
} else if (currentOperator == ExpressionOperator.Not) {
ConditionalCriteria notCriteria = ConditionalCriteria.not(criteria);
criteriaStack.push(notCriteria);
operatorStack.pop();
if (!operatorStack.isEmpty() && !criteriaStack.isEmpty()) {
pushCriteria(criteriaStack.pop());
}
} else if (currentOperator == ExpressionOperator.LBrace) {
criteriaStack.push(criteria);
} else {
criteriaStack.push(criteria);
}
}

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

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

public ConditionRoot<T> eq(Object value) {
pushCriteria(ConditionalCriteria.equal(propertyName, value));
return RootBuilderImpl.this;
}
}

}
}


The magic is in pushCriteria, which is invoked at the end of a condition, such as eq(), and also when right brace is reached. It is not a trivial problem, but thanks to the stack metaphor the solution is not that complicated.

A final thought about the syntax. Look at the expression again:

// A and (B or C)
criteriaFor(Person.class).withProperty(aaa()).eq("A").and().lbrace()
.withProperty(bbb()).eq("B").or().withProperty(ccc()).eq("C").rbrace()
.build();


I use special keywords for left brace and right brace. Why not use grouping with an single method instead and write the nested expression as method parameter?

// A and (B or C)
criteriaFor(Person.class).withProperty(aaa()).eq("A").and().group(
criteriaFor(Person.class).withProperty(bbb()).eq("B").or().withProperty(ccc()).eq("C"))
.buildSingle();


That would be possible, and probably easier to implement, but I see a few problems from end user perspective:
  1. There are already much clutter of braces due to the methods and it is hard to see and write the end brace of the group operation.
  2. You loose context and have to start the nested expression from scratch, i.e. with static criteriaFor method.

That was all of this exercise. I hope you find it useful as a practical example of how to develop a small Internal DSL. You find the full source code in subversion, including JUnit test.

1 comment:

  1. Nice tutorial. See here for a typesafe DSL for Criteria like queries : http://blog.mysema.com/2010/07/querying-hibernate-with-querydsl.html

    Querydsl uses a fluent Query interface and code generation to mirror the domain model to a Query model. The syntax is JPQL-like, typesafe and very compact.

    ReplyDelete