We will go back to the OrderComposite example;
First we need to create (or also find in a library) the mechanics of the audit trail. It could be something like this;
public interface AuditTrail<M>
{
List<Operation<M>> getOperations();
}
public interface Trailable<M>
{
void itemAdded( M item );
void itemRemoved( M item );
void completed();
}
public class AuditTrailMixin<M>
implements AuditTrail<M>, Trailable<M>
{
private K container;
private List<Operation<M>> operations;
public AuditTrailMixin()
{
operations = new ArrayList<Operation<M>>();
}
public void itemAdded( M item )
{
Operation op = new Operation( item, Operation.added );
operations.add( op );
}
public void itemRemoved( M item )
{
Operation op = new Operation( item, Operation.removed );
operations.add( op );
}
public void completed( M item )
{
Operation op = new Operation( item, Operation.completed );
operations.add( op );
}
public List<Operation<M>> getOperations()
{
return Collections.unmodifiableList( operations );
}
}
public class Operation<T>
{
public static enum Type added, removed, completed;
private T item;
private Type type;
private Date date;
public Operation( T item, Type type )
{
this.item = item;
this.type = type;
this.date = new Date();
}
public T getItem()
{
return item;
}
public Type getOperationType()
{
return type;
}
publc Date getDatePerformed()
{
return date;
}
}
We also need a Concern to hang into the methods of the Order interface.
public void addLineItem( LineItem item )
{
next.addLineItem( item );
trail.itemAdded( item );
}
public void removeLineItem( LineItem item )
{
next.removeLineItem( item );
trail.itemRemoved( item );
}
public void completed()
{
next.completed();
trail.completed();
}
}
But the AuditTrail subsystem could provide a Generic Concern, that operates on a naming pattern (for instance). In this case, we would move the coding of the concern from the application developer to the library developer, again increasing the re-use value. It could look like this;
public void invoke( Object proxy, Method m, Object[] args )
throws Exception
{
next.invoke( proxy, m, args );
String methodName = m.getName();
if( methodName.startsWith( "add" ) )
{
trail.itemAdded( args[0] );
}
else if( methodName.startsWith( "remove" ) )
{
trail.itemRemoved( args[0] );
}
else if( methodName.startsWith( "complete" ) ||
methodName.startsWith( "commit" ) )
{
trail.completed();
}
}
}
Finally, we need to declare the Concern in the OrderComposite;
So let's move on to something more complicated. As we have mentioned, EntityComposite is automatically persisted to an underlying store (provided the Runtime is setup with one at bootstrap initialization), but how do I locate an Order?
Glad you asked. It is done via the Query API. It is important to understand that Indexing and Query are separated from the persistence concern of storage and retrieval. This enables many performance optimization opportunities as well as a more flexible Indexing strategy. The other thing to understand is that the Query API is using the domain model, in Java, and not some String based query language. We have made this choice to ensure refactor safety. In rare cases, the Query API is not capable enough, in which case Qi4j still provides the ability to look up and execute native queries.
Let's say that we want to find a particular Order from its OrderNumber.
String orderNumber = "12345";
Order orderParam = builder.parameter( Order.class );
builder.where( eq( orderParam.getOrderNumber(), orderNumber ) );
Query<Order> query = builder.newQuery();
Iterator result = query.iterator();
if( result.hasNext() )
{
Order order = result.next();
}
else
{
String message = "Unable to locate Order " + orderNumber;
throw new EntityNotFoundException( message );
}
Another example,
Calendar cal = Calendar.getInstance();
cal.setTime( new Date() );
Date last90days = cal.roll( Calendar.DAY_OF_MONTH, -90 );
Order orderParam = builder.parameter( Order.class );
builder.where( gt( orderParam.getCreatedDate(), last90days ) );
Query<Order> query = builder.newQuery();
for( Order order : query )
{
report.addOrderToReport( order );
}
Now, Orders has a relation to the CustomerComposite which is also an Entity. Let's create a query for all customers that has made an Order in the last 30 days;
Calendar cal = Calendar.getInstance();
cal.setTime( new Date() );
Date lastMonth = cal.roll( Calendar.MONTH, -1 );
Order orderParam = builder.parameter( Order.class );
builder.where( gt( orderParam.getCreatedDate(), lastMonth ) );
QueryBuilder<Customer> builder2 =
builder.resultSet( orderParam.getCustomer() );
Query<Customer> query = builder2.newQuery();
for( Customer customer : query )
{
report.addCustomerToReport( customer );
}
It can be a bit confusing to see Qi4j use Java itself as a Query language, but since we have practically killed the classes and only operates with interfaces, it is possible to do a lot of seemingly magic stuff. Just keep in mind that it is pure Java, albeit heavy use of dynamic proxies to capture the intent of the query.
Note: Query API is not yet implemented!
We have now explored a couple more intricate features of Qi4j, hopefully without being overwhelmed with details on how to create applications from scratch, how to structure applications, and how the entire Qi4j Extension system works.
We have looked at how to add a Concern that uses a private Mixin, we have touched a bit on Generic Concerns, and finally a short introduction to the Query API.