MainOverviewWikiIssuesForumBuildFisheye

Chapter 12. Working with objects

12.1. Introduction

Lets assume you have download and configured Compass within your application and create some RSEM/OSEM/XSEM mappings. This section provides the basics of how you will use Compass from within the application to load, search and delete Compass searchable objects. All operations within Compass are accessed through the CompassSession interface. The interface provides Object and Resource method API's, giving the developer the choice to work directly with Compass internal representation (Resource) or application domain Objects.

12.2. Making Object/Resource Searchable

Newly instantiated objects (or Resources) are saved to the index using the save(Object) method. If you have created more than one mapping (alias) to the same object (in OSEM file), use the save(String alias, Object) instead.

Author author = new Author();
author.setId(new Long(1));
author.setName("Jack London");
compassSession.save(author);

When using OSEM and defining cascading on component/reference mappings, Compass will cascade save operations to the target referenced objects (if they are marked with save cascade). Non root objects are allowed to be saved in Compass if they have cascading save relationship defined.

12.3. Loading an Object/Resource

The load() method allows you to load an object (or a Resource) if you already know it's identifier. If you have one mapping for the object (hence one alias), you can use the load(Class, Object id) method. If you created more than one mapping (alias) to the same object, use the load(String alias, Object id) method instead.

Author author = (Author) session.load(Author.class,
     new Long(12));

load() will throw an exception if no object exists in the index. If you are not sure that there is an object that maps to the supplied id, use the get method instead.

12.4. Deleting an Object/Resource

If you wish to delete an object (or a Resource), you can use the delete() method on the CompassSession interface (note that only the identifiers need to be set on the corresponding object or Resource).

session.delete(Author.class, 12);
// or :
session.delete(Author.class, new Author(12));
// or :
session.delete(Author.class, "12"); // Everything in the search engine is a String at the end

When using OSEM and defining cascading on component/reference mappings, Compass will cascade delete operations to the target referenced objects (if they are marked with delete cascade). Non root objects are allowed to be deleted in Compass if they have cascading save relationship defined. Note, deleting objects by their id will not cause cascaded relationships to be deleted, only when the actual object is passed to be deleted, with the relationships initialized (the object can be loaded from the search engine).

12.5. Searching

For a quick way to query the index, use the find() method. The find() method returns a CompassHits object, which is an interface which encapsulates the search results. For more control over how the query will executed, use the CompassQuery interface, explained later in the section.

CompassHits hits = session.find("name:jack");

12.5.1. Query String Syntax

The free text query string has a specific syntax. The syntax is the same one Lucene uses, and is summarized here:

Table 12.1. 

ExpressionHits That
jackContain the term jack in the default search field
jack london (jack AND london)Contains the term jack and london in the default search field
jack OR londonContains the term jack or london, or both, in the default search field
+jack +london (jack AND london)Contains both jack and london in the default search field
name:jackContains the term jack in the name property (meta-data)
name:jack -city:london (name:jack AND NOT city:london)Have jack in the name property and don't have london in the city property
name:"jack london"Contains the exact phrase jack london in the name property
name:"jack london"~5Contain the term jack and london within five positions of one another
jack*Contain terms that begin with jack
jack~Contains terms that are close to the word jack
birthday:[1870/01/01 TO 1920/01/01]Have the birthday values between the specified values. Note that it is a lexicography range

The default search can be controlled using the Compass::Core configuration parameters and defaults to all meta-data.

12.5.2. Query String - Range Queries Extensions

Compass simplifies the usage of range queries when working with dates and numbers. When using numbers it is preferred to store the number if a lexicography correct value (such as 00001, usually using the format attribute). When using range queries, Compass allows to execute the following query: value:[1 TO 3] and internally Compass will automatically translate it to value:[0001 TO 0003].

When using dates, Compass allows to use several different formats for the same property. The format of the Date object should be sortable in order to perform range queries. This means, for example, that the format attribute should be: format="yyyy-MM-dd". This allows for range queries such as: date:[1980-01-01 TO 1985-01-01] to work. Compass also allows to use different formats for range queries. It can be configured within the format configuration: format="yyyy-MM-dd||dd-MM-yyyy" (the first format is the one used to store the String). And now the following range query can be executed: date:[01-01-1980 TO 01-01-1985].

Compass also allows for math like date formats using the now keyword. For example: "now+1year" will translate to a date with a year from now. For more information please refer to the DateMathParser javadoc.

12.5.3. CompassHits, CompassDetachedHits & CompassHitsOperations

All the search results are accessible using the CompassHits interface. It provides an efficient access to the search results and will only hit the index for "hit number N" when requested. Results are ordered by relevance (if no sorting is provided), in other words and by how well each resource matches the query.

CompassHits can only be used within a transactional context, if hits are needed to be accessed outside of a transactional context (like in a jsp view page), they have to be "detached", using one of CompassHits#detch methods. The detached hits are of type COmpassDetachedHits, and it is guaranteed that the index will not be accessed by any operation of the detached hits. CompassHits and CompassDetachedHits both share the same operations interface called CompassHitsOperations.

The following table lists the different CompassHitsOperations methods (note that there are many more, please view the javadoc):

Table 12.2. 

MethodDescription
getLength() or length()Number of resources in the hits.
score(n)Normalized score (based on the score of the topmost resource) of the n'th top-scoring resource. Guaranteed to be greater than 0 and less than or equal to 1.
resource(n)Resource instance of the n'th top-scoring resource.
data(n)Object instance of the n'th top-scoring resource.

12.5.4. CompassQuery and CompassQueryBuilder

Compass::Core comes with the CompassQueryBuilder interface, which provides programmatic API for building a query. The query builder creates a CompassQuery which can than be used to add sorting and executing the query.

Using the CompassQueryBuilder, simple queries can be created (i.e. eq, between, prefix, fuzzy), and more complex query builders can be created as well (such as a boolean query, multi-phrase, and query string).

The following code shows how to use a query string query builder and using the CompassQuery add sorting to the result.

CompassHits hits = session.createQueryBuilder()
  .queryString("+name:jack +familyName:london")
    .setAnalyzer("an1") // use a different analyzer
  .toQuery()
    .addSort("familyName", CompassQuery.SortPropertyType.STRING)
    .addSort("birthdate", CompassQuery.SortPropertyType.INT)
  .hits();

Another example for building a query that requires the name to be jack, and the familyName not to be london:

CompassQueryBuilder queryBuilder = session.createQueryBuilder();
CompassHits hits =  queryBuilder.bool()
    .addMust( queryBuilder.term("name", "jack") )
    .addMustNot( queryBuilder.term("familyName", "london") )
  .toQuery()
    .addSort("familyName", CompassQuery.SortPropertyType.STRING)
    .addSort("birthdate", CompassQuery.SortPropertyType.INT)
  .hits();

CompassQuery can also be created using the Compass instance, without the need to construct a CompassSession. They can then stored and used safely by multiple sessions (in a multi threaded environment) by attaching them to the current session using CompassQuery#attach(CompssSession) API.

Note that sorted resource properties / meta-data must be stored and not_analyzed. Also sorting requires more memory to keep sorting properties available. For numeric types, each property sorted requires four bytes to be cached for each resource in the index. For String types, each unique term needs to be cached.

When a query is built, most of the queries can accept an Object as a parameter, and the name part can be more than just a simple string value of the meta-data / resource-property. If we take the following mapping for example:

<class name="eg.A" alias="a">
  <id name="id" />

  <property name="familyName">
    <meta-data>family-name</meta-data>
  </property>

  <property name="date">
    <meta-data converter-param="YYYYMMDD">date-sem</meta-data>
  </property>

</class>

The mapping defines a simple class mapping, with a simple string property called familyName and a date property called date. With the CompassQueryBuilder, most of the queries can directly work with either level of the mappings. Here are some samples:

CompassQueryBuilder queryBuilder = session.createQueryBuilder();
// The following search will result in matching "london" against "familyName"
CompassHits hits =  queryBuilder.term("a.familyName.family-name", "london").hits();

// The following search will use the class property meta-data id, which in this case
// is the first one (family-name). If there was another meta-data with the family-name value,
// the internal meta-data that is created will be used ($/a/familyName).
CompassHits hits =  queryBuilder.term("a.familyName", "london").hits();

// Here, we provide the Date object as a parameter, the query builder will use the
// converter framework to convert the value (and use the given parameter)
CompassHits hits =  queryBuilder.term("a.date.date-sem", new Date()).hits();

// Remmember, that the alias constraint will not be added automatically, so
// the following query will cause only family-name with the value "london" of alias "a"
CompassHits hits =  queryBuilder.bool()
    .addMust( queryBuilder.alias("a") )
    .addMust( queryBuilder.term("a.familyName", "london") )
  .toQuery().hits();

When using query strings and query parsers, Compass enhances Lucene query parser to support custom formats (for dates and numbers, for example) as well as support dot path notation. The query: a.familyname.family-name:london will result in a query matching on familyName to london as well as wrapping the query with one that will only match the a alias.

12.5.5. Terms and Frequencies

Compass allows to easily get all the terms (possible values) for a property / meta-data name and their respective frequencies. This can be used to build a frequency based list of terms showing how popular are different tags (as different blogging sites do for example). Here is a simple example of how it can be used:

CompassTermFreq[] termFreqs = session.termFreqsBuilder(new String[]{"tag"}).toTermFreqs();
// iterate over the term freqs and display them

// a more complex example:
termFreqs = session.termFreqsBuilder(new String[]{"tag"}).setSize(10).
	.setSort(CompassTermFreqsBuilder.Sort.TERM).normalize(0, 1).toTermFreqs();

12.5.6. CompassSearchHelper

Compass provides a simple search helper providing support for pagination and automatic hits detach. The search helper can be used mainly to simplify search results display and can be easily integrated with different MVC frameworks. CompassSearchHelper is thread safe. Here is an example of how it can be used:

// constructs a new search helper with page size 10.
CompassSearchHelper searchHelper = new CompasssSearchHelper(compass, 10);
// ...
CompassSearchResults results = searchHelper.search(new CompassSearchCommand("test", new Integer(0)));
for (int i = 0; i < results.getHits().length; i++) {
  CompassHit hit = results.getHits()[i];
  // display the results
}
// iterate through the search results pages
for (int i = 0; i < results.getPages().length; i++) {
  Page page = results.getPages()[i];
  // display a page, for example 1-10, 11-20, 21-30
}

12.5.7. CompassHighlighter

Compass::Core comes with the CompassHighlighter interface. It provides ways to highlight matched text fragments based on a query executed. The following code fragment shows a simple usage of the highlighter functionality (please consult the javadoc for more information):

CompassHits hits =  session.find("london");
// a fragment highlighted for the first hit, and the description property name
String fragment = hits.highlighter(0).fragment("description");

Highlighting can only be used with CompassHits, which operations can only be used within a transactional context. When working with pure hits results, CompassHits can be detached, and then used outside of a transactional context, the question is: what can be done with highlighting?

Each highlighting operation (as seen in the previous code) is also cached within the hits object. When detaching the hits, the cache is passed to the detached hits, which can then be used outside of a transaction. Here is an example:

CompassHits hits =  session.find("london");
for (int i = 0.; i < 10; i++) {
    hits.highlighter(i).fragment("description"); // this will cache the highlighted fragment
}
CompassHit[] detachedHits = hits.detach(0, 10).getHits();

// outside of a transaction (maybe in a view technology)
for (int i = 0; i < detachedHits.length; i++) {
    // this will return the first fragment
    detachedHits[i].getHighlightedText().getHighlightedText();
    // this will return the description fragment, note that the implementation
    // implements the Map interface, which allows it to be used simply in JSTL env and others
    detachedHits[i].getHighlightedText().getHighlightedText("description"); 
}