-
Notifications
You must be signed in to change notification settings - Fork 102
Using Boon's data repo by example
Boon Home | Boon Source | If you are new to boon, you might want to start here. Simple opinionated Java for the novice to expert level Java Programmer. Low Ceremony. High Productivity. A real boon to Java to developers!
Boon's data repo allows you to quickly query complex trees of Java objects that are stored inside of Java collections.
Let's start with some simple examples.
First here are the imports:
//Sample Test model
import com.examples.model.test.Email;
import com.examples.security.model.User;
import static com.examples.security.model.User.user;
//Data repo classes
import org.boon.datarepo.DataRepoException;
import org.boon.datarepo.Repo;
import org.boon.datarepo.RepoBuilder;
import org.boon.datarepo.Repos;
//Boon utility methods
import org.boon.Lists;
import org.boon.core.Typ;
import static org.boon.Boon.putl;
import static org.boon.Boon.puts;
import static org.boon.Exceptions.die;
import static org.boon.Lists.idx;
import static org.boon.criteria.CriteriaFactory.eq;
import static org.boon.criteria.CriteriaFactory.eqNested;
import static org.boon.criteria.CriteriaFactory.notEq;
We will create a repository of user objects and then perform some basic queries against them.
Some house keeping. Let's create a constant for email so we don't keep using it over an over.
private static String EMAIL = "email";
Let's get going.
public static void main ( String... args ) {
boolean test = true;
Next create a repo builder that creates a user repo. User is a sample domain object, and it has one property called email.
A repo is core concept in Boon data repo. Think of a repo as a collection that allows queries. The queries can be very fast as they use search indexes (TreeMap), and lookup indexes (HashMap).
RepoBuilder repoBuilder = Repos.builder ();
repoBuilder.primaryKey ( EMAIL );
The above creates a repo whose primary key is the property email.
Next we create a repo of that has a key of type String.class and an item class of a User.class.
final Repo< String, User > userRepo = repoBuilder.build ( Typ.string, user );
The above creates the userRepo using the builder. Note that Typ.string is just equal to String.class.
Now lets add some users to our repo so we can test it out.
final List<User> users = Lists.list (
user ( "rick.hightower@foo.com" ),
user ( "bob.jones@foo.com" ),
user ( "sam.jones@google.com" )
);
The method user is a static method from the class User. I will show you that in a second. List.list is a helper method that creates java.util.List (see wiki for more detail on Boon).
The userRepo allows you to add a list of users to it with the addAll method as follows:
userRepo.addAll ( users );
Now that we have a repo with users let's run some queries against it.
Here is a query using the eq criteria.
List<User> results =
userRepo.query ( eq ( EMAIL, "rick.hightower@foo.com") );
The method userRepo.query returns a java.util.List of Users.
Let's print out all of the users.
putl ( "Simple Query using Equals Results", results );
Boon has two utility methods puts, and putl. The puts method is similar to Ruby puts. The putl method puts out lines per item.
Output:
[User{email='rick.hightower@foo.com'}]
Now let's make sure we got what we expected.
/** Same as results.get(0) */
User rick = idx (results, 0);
/* Make sure we got what we wanted. */
test |= Objects.equals (rick.getEmail (), "rick.hightower@foo.com") ||
die( "Rick's email not equal to 'rick.hightower@foo.com' " );
The idx method is a core concept in Boon but not in Boon data repo. The idx method indexes things. There are quite a few wiki pages on slice notation and indexing in the Boon wiki.
Now let's try some different queries on our repo collection, let's try a notEq criteria:
results =
userRepo.query ( notEq( EMAIL, "rick.hightower@foo.com" ) );
There are many criteria operators in DataRepo, here is partial list:
static Group and(Criteria... expressions)
static Criterion between(java.lang.Class clazz, java.lang.Object name, java.lang.String svalue, java.lang.String svalue2)
static Criterion between(java.lang.Object name, java.lang.Object value, java.lang.Object value2)
static Criterion between(java.lang.Object name, java.lang.String svalue, java.lang.String svalue2)
static Criterion contains(java.lang.Object name, java.lang.Object value)
static Criterion empty(java.lang.Object name)
static Criterion endsWith(java.lang.Object name, java.lang.Object value)
static Criterion eq(java.lang.Object name, java.lang.Object value)
static Criterion eqNested(java.lang.Object value, java.lang.Object... path)
static Criterion gt(java.lang.Object name, java.lang.Object value)
static Criterion gt(java.lang.Object name, java.lang.String svalue)
static Criterion gte(java.lang.Object name, java.lang.Object value)
static Criterion implementsInterface(java.lang.Class<?> cls)
static Criterion in(java.lang.Object name, java.lang.Object... values)
static Criterion instanceOf(java.lang.Class<?> cls)
static Criterion isNull(java.lang.Object name)
static Criterion lt(java.lang.Object name, java.lang.Object value)
static Criterion lte(java.lang.Object name, java.lang.Object value)
static Not not(Criteria expression)
static Criterion notContains(java.lang.Object name, java.lang.Object value)
static Criterion notEmpty(java.lang.Object name)
static Criterion notEq(java.lang.Object name, java.lang.Object value)
static Criterion notIn(java.lang.Object name, java.lang.Object... values)
static Criterion notNull(java.lang.Object name)
static Group or(Criteria... expressions)
static Criterion startsWith(java.lang.Object name, java.lang.Object value)
static Criterion typeOf(java.lang.String className)
We will not cover them all in detail, but you can guess what they do by their name.
Let's continue our notEq example.
Now we have to make sure we get users who are not Rick. No one wants Rick. :(
putl ( "Simple Query using Not Equals Results", results );
/** Same as results.get(0) */
User notRick = idx (results, 0);
putl ( notRick );
/* Make sure we got what we wanted, i.e. no Rick! */
test |= !Objects.equals (notRick.getEmail (), "rick.hightower@foo.com") ||
die( "User Not Rick's email should NOT be equal " +
"to 'rick.hightower@foo.com' " );
The above shows that Rick is not in the results of the not Rick query. :)
output
[User{email='bob.jones@foo.com'}, User{email='sam.jones@google.com'}]
User{email='bob.jones@foo.com'}
Sometimes, you know that you only want one result from a query or sometimes you just want one result period. For that you use the results method which return a ResultSet as follows:
rick = userRepo.results ( eq ( EMAIL, "rick.hightower@foo.com" ) ).firstItem ();
Notice that we are using _**userRepo.results(...).firstItem() **_to get one item instead of a list of items.
What if we only expect one item and only one item then we would use a results(...).expectOne().firstItem() as follows:
rick = (User) //expectOne is not generic
userRepo.results ( eq ( EMAIL, "rick.hightower@foo.com" ) )
.expectOne ().firstItem ();
Notice that we have to use a cast. You can use results with selects, which means you can select any part of an object so firstItem may not always return user. If you don't want to cast, you can use the following form.
rick = userRepo.results ( eq ( EMAIL, "rick.hightower@foo.com" ) )
.expectOne (user).firstItem ();
Notice we pass user to expectOne (user is of type Class and is set to User.class).
You could write the above like this instead:
rick = userRepo.results ( eq ( EMAIL, "rick.hightower@foo.com" ) )
.expectOne (User.class).firstItem ();
I prefer having types that are easier on the eyes since they get used a bit in Boon data repo support.
If you expect one and only one, then you can use the expectOne method and if there are more than one item or zero items, then it will throw an exception demonstrated as follows:
/** Example 6: Expect only one item with expectOne(user).firstItem() and we have many. */
try {
putl ( "Example 6: Failure case, we have more than one for",
"query using ResultSet.expectOne(user).firstItem");
rick = userRepo.results ( notEq ( EMAIL, "rick.hightower@foo.com" ) )
.expectOne (user).firstItem ();
die("We should never get here!");
} catch (DataRepoException ex) {
puts ("success for Example 6");
}
By the way, you have seen this die(...) method use. It basically just throws a runtime exception. It is a boon helper method. Boon got the die concept from Perl (http://perldoc.perl.org/functions/die.html). Boon endeavors to take good ideas from other languages, and keep them to make a productive sets of APIs.
Boon gets indexing and slicing from Python, puts from Ruby and die from Perl. There is more to come. Boon's older brother was called EasyJava (later named Facile), and EasyJava/Facile borrow many more ideas from Perl, Python and Ruby. But EasyJava/Facile were more experimental whilst Boon is better designed and meant to be curated and used on projects. Boon is not new per se. Boon is an evolution of DataRepo, EasyJava/Facile and Crank. When Boon grows up, it will be a very capable library. But I digress....
Let's take a look at our User object to show that is is just a plain old Java Object:
User class for example
package com.examples.security.model;
public class User {
public static final Class<User> user = User.class;
public static User user (String email) {
return new User( email );
}
private final String email;
...
public String getEmail () {
return email;
}
User is a pretty simple POJO.
Boon's data repo can work with complex object hierarchies and relationships and it can setup search indexes (TreeMap), lookup indexes (HashMap), non-unique indexes (TreeMap with multi-value), and non unique lookup indexes, but that is for another time.
By default Boon works with the fields in the class, but you can instruct it to only use properties (getters and setters).
Let's see how Boon's data repo handles composite objects and property access as follows, but first let's define some more classes:
Email class*
package com.examples.model.test;
public class Email {
private String content;
public String getEmail () {
return content;
}
public void setEmail ( String content ) {
this.content = content;
}
public Email ( String content ) {
this.content = content;
}
public Email ( ) {
}
@Override
public boolean equals ( Object o ) {
if ( this == o ) return true;
if ( !( o instanceof Email ) ) return false;
Email email = ( Email ) o;
if ( content != null ? !content.equals ( email.content ) : email.content != null ) return false;
return true;
}
@Override
public int hashCode () {
return content != null ? content.hashCode () : 0;
}
}
The new User object does not have a simple email field but a complex email field (Email).
UserEmail class that uses email instead of a simple string
package com.examples.model.test;
public class UserEmail {
private Email email;
public UserEmail ( String email ) {
this.email = new Email ( email ) ;
}
public Email getEmail () {
return email;
}
}
Let's setup a repo again, but this time we will only use properties not fields:
boolean ok = true;
RepoBuilder repoBuilder = Repos.builder ();
repoBuilder.usePropertyForAccess ( true );
putl ("The primary key is set to email");
repoBuilder.primaryKey ( "email" );
putl ("For ease of use you can setup nested properties ",
"UserEmail.email property is a Email object not a string",
"Email.email is a string.");
//You can index component objects if you want
repoBuilder.nestedIndex ( "email", "email" );
Read the above comments and putl calls, and puts method calls for more instruction.
Next we define our userRepo. Notice that I opted to use the Email.class, and UserEmail.class to show you exactly what we are passing. Email.class is the primary key class, and UserEmail.class is the item type.
/** Create a repo of type String.class and User.class */
final Repo<Email, UserEmail> userRepo = repoBuilder.build (
Email.class, UserEmail.class );
Let's add some test data using the repo.add method.
puts("Adding three test objects for bob, sam and joe ");
userRepo.add ( new UserEmail ( "bob@bob.com" ) );
userRepo.add ( new UserEmail ( "sam@bob.com" ) );
userRepo.add ( new UserEmail ( "joe@bob.com" ) );
Now we can query userRepo and look for Bob's email:
UserEmail bob = (UserEmail) userRepo.results (
eqNested ( "bob@bob.com", "email", "email" ) )
.expectOne ().firstItem ();
Notice that we are using an eqNested operator. So in effect we are looking for the property path of user.email.email. Since the root object of userRepo are users (UserEmail to be precise) then we are looking for root.email.email. This is because UserEmail has a property of type Email called email, and Email has a property called email.
We can test we got the right email address:
ok |= bob.getEmail ().getEmail ().equals ( "bob@bob.com" ) || die();
The above is boon speak for if Bob's email's property called email is not equal to "bob@bob.com", then die (which just means throw a runtime exception).
We can avoid the cast as follows (read putl and comments):
putl("Avoid the cast with using nested query Repo.eqNested(UserEmail.class)");
bob = userRepo.results ( eqNested ( "bob@bob.com", "email", "email" ) )
.expectOne (UserEmail.class).firstItem ();
ok |= bob.getEmail ().getEmail ().equals ( "bob@bob.com" ) || die();
You are not stuck with strings, you can query primitives and any complex object that implements an equals method and a hashCode method as follows:
Email email = new Email ( "bob@bob.com" );
bob = (UserEmail) userRepo.results ( eq ( EMAIL, email ) )
.expectOne ().firstItem ();
ok |= bob.getEmail ().getEmail ().equals ( "bob@bob.com" ) || die();
puts("success=", ok);
Notice you can query with email (complex object) direct and remember that the email property is not a string but a class called Email that has an equals and a hashCode method.
That is all for part 1 as far as examples go (it goes on and on).
There is a lot more to dataRepo than meets the eye. It can do many things. This was just an introductory article to whet your appetite, and hopefully, you want to learn more.
Here are some of the classes in Boon's data repo package.
Interfaces
Bag //Like a java.util.List
CollectionDecorator //Decorates built-in collecitons to add indexes and searches
Filter //The filter interface
LookupIndex //LookupIndex implemented with HashMaps or equiv
ObjectEditor //Abilty to edit fields and peform Select Queries against collecitons
Repo //Like a data repo but uses collections api to sync one or more indexes
RepoBuilder //Allows you to build a Repo, setup indexes, etc.
ResultSet //The results from a query.
SearchableCollection //A collection that is searchable.
Classes
Collections //converts regular collections to SearchableCollections with indexes to/for
Repos //Helper class for creating RepBuilders
DataRepoException //Exception class for DataRepo
There is also a Criteria API that works with the DataRepo queries. DataRepo queries start by using the index, and then when an item is not in the index or if the index query did not fully satisfy the criteria, DataRepo can then use the criteria API to perform the rest in a linear search:
Criteria //Criteria
CriteriaFactory //Criteria Factory, we say this earlier, eq, notEq, etc.
Criterion //Concrete classes
Criterion.PrimitiveCriterion //Primitive criteria to avoid wrappers and lots of temp objects
Group //Group of Criteria, Group is also a Criteria so it is nested
Group.And //And criteria together
Group.Or //Or Criteria
Not //Inverse the logic
ProjectedSelector //Perform projections over a Criteria
QueryFactory //Create queries
Selector //Selectors like SQL select
Sort //Define how you want the data Sorted
Update //Ability to select and update objects based on criteria
Enums
Grouping
Operator
SortType
To learn more see the JavaDocs.
http://richardhightower.github.io/site/javadocs/index.html
Let me know what you think, and if you have issues, please file a bug.
YourKit supports Boon open source project with its full-featured Java Profiler. YourKit, LLC is the creator of innovative and intelligent tools for profiling Java and .NET applications. Take a look at YourKit's leading software products: YourKit Java Profiler and YourKit .Net profiler.