JBoss Community Archive (Read Only)

RHQ 4.9

Design-Criteria

Overview

The Criteria API in RHQ is somewhat similar in principal to the Criteria API in Hibernate. They both provide an object-oriented approach to building queries. However, whereas Hibernate's solution must remain generic so that it can be used against any object model, RHQ's solution can and is very specific to its domain model.

Core Functionality

Pagination

There are two methods to manipulate the pagination.

1) criteria.setPaging(int pageNumber, int pageSize)

This strategy is generally used when you are a remote client talking over web services or via the CLI.

2) criteria.setPageControl(PageControl pc)

This strategy is used by the RHQ GUI. There are mechanisms to extract the pagination and sorting information from tabular displays and create PageControl objects. These objects can then be set directly on the Criteria object, which means that any calls to setPaging(pageNumber, pageSize) and addSortField(fieldName) will be ignored.

Filter Semantics

To simplify the Criteria API, it only supports two methods for result filtering - conjunctive (AND) or disjunctive (OR). In other words, all of the filters you set on each Criteria object must either be AND'ed together or OR'ed with one another - you can not mix and match these styles. The AND-style is the default, but you can switch to the OR style by calling "criteria.setFiltersOptional(true)".

Comparison Operators

All data types except string (Boolean, Long, Integer, even Enum) will use exact matching. Strings, on the other hand, have two ways their matching algorithms can be modified:

1) Strict

By default, strings will be matched using 'like' comparison. Calling "criteria.setStrict(true)" will turn it into an equal comparison.

2) Case-sensitivity

By defaults, strings will be matched insensitively. Calling "criteria.setCaseSensitive(true)" will turn it into a case-sensitive match.

Projection

In RHQ, each Criteria object corresponds to the entity returned by "criteria.getPersistentClass()". Thus, when you use that Criteria object to search, it will return a list of those entities.

However, sometimes you only need to return a fraction of that data...and sometimes you need to augment your entities with additional data. In both of these cases, the CriteriaQueryGenerator allows you to set a new select expression list. By default, the generated query will have the form:

SELECT {alias}
FROM {getPersistentClass} {alias}
WHERE {filter1} AND {filter2}...

But by setting the projection, you can alter what data gets returned by the generated query:

SELECT {projection}
FROM {getPersistentClass} {alias}
WHERE {filter1} AND {filter2}...

Extensible Functionality

Filtering

By default, the private fields with the naming convention filterXXX map to the XXX field on the corresponding entity returned from getPersistentClass on the Criteria. However, you can have any number of "virtual fields" which don't implicitly map to any real field on the entity. In cases like this, the CriteriaQueryGenerator doesn't know how to generate the JPQL fragment if that field is set with data, so you need to explicitly provide an 'override' for it. The generated query will have the form:

SELECT {alias | projection}
FROM {getPersistentClass} {alias}
WHERE {filter1} AND {filter2}...

This 'override' fragment therefore needs to be a valid conditional term in JPQL, with one caveat. Instead of using the style ":colonPrefixedArgument" to denote where the filter value should be inserted, all you have to use is '?'.

Take, for instance, AlertCriteria. If I wanted to be able to find all alerts by type then I would:

1) add "private String filterResourceTypeName;" to the field declarations
2) expose a setter method for it
3) add "filterOverrides.put("resourceTypeName", "alertDefinition.resource.resourceType.name like ?");" to the constructor

Notice how if we map "filterResourceTypeName" from step (1) back to the "filterXXX" convention explained above, then the underlying field name (in this case a virtual field) becomes "resourceTypeName". Thus, "resourceTypeName" becomes the key into the map in step (3) that you're supplying the JPQL override fragment for.

There's virtually no limit to what you can do with overrides because you can use anything that is a valid conditional expression including:

  • between expressions

  • collection member expressions (entityExpression [not] member [of] collectionValueExpression)

  • comparison expressions

  • empty collection comparison expressions

  • exists expressions

  • in-clause expressions

  • like expressions

  • null comparison expressions

Fetching

The RHQ domain model generally (but not always) marks entity relationships as lazy. This allows for queries to be efficient and only return the minimal amount of data necessary by default. However, there are instances where you want that additional data:

  • you might want all the members of some group

  • you might want all the configuration history for some resource

  • you might want all the individual results for some compatible group operation

Fetching is the way you can return additional data with an Criteria-based query. The framework will make sure to load and return that entity-related data, for each element in the result set.

If, when search for roles, I wanted to be able to fetch all the subject under each role I would do:

1) add "private boolean fetchSubjects;" to the field declarations
2) expose a setter method for it

Currently, there is no way to add fetch methods for data that isn't directly associated with the entity represented by the Criteria object.

Sorting

By default, the private fields with the naming convention sortXXX map to the XXX field on the corresponding entity returned from getPersistentClass on the Criteria. However, you can have any number of "virtual fields" which don't implicitly map to any real field on the entity. In cases like this, the CriteriaQueryGenerator doesn't know how to generate the JPQL fragment if that field is set with data, so you need to explicitly provide an 'override' for it. The generated query will have the form:

SELECT {alias | projection}
FROM {getPersistentClass} {alias}
...
ORDER BY {order1 ASC|DESC}, {order2 ASC|DESC}

This 'override' fragment therefore needs to be a valid path expression in JPQL, with one caveat.

Take, for instance, ResourceCriteria. If I wanted to be able to sort resources by their current availability then I would:

1) add "private PageOrdering sortCurrentAvailability;" to the field declarations
2) expose a setter method for it (see below)
3) add "sortOverrides.put("currentAvailability", "currentAvailability.availabilityType");" to the constructor

public void addSortCurrentAvailability(PageOrdering sortCurrentAvailability) {
    addSortField("currentAvailability"); // relative ordering of sort fields
    this.sortCurrentAvailability = sortCurrentAvailability; // set the ordering
}

Notice how if we map "sortCurrentAvailability" from step (1) back to the "sortXXX" convention explained above, then the underlying field name (in this case a virtual field) becomes "currentAvailability". Thus, "currentAvailability" becomes the key into the map in step (3) that you're supplying the JPQL override fragment for.

The actual ordering information for this virtual field - ascending or descending - is taken care of by the value of the PageOrdering object passed to the method in (2).

Query Generation

The abstract Criteria class is only half of the dynamic query equation - it holds all the data but, by itself, does nothing with it. The other half concerns how the information set on the Criteria objects is leverage to generate the effective JPQL statement. The general usage is as follows:

SomeEntityCriteria criteria = SomeEntityCriteria();
// call various setters on criteria

CriteriaQueryGenerator generator = new CriteriaQueryGenerator(criteria);
generator.setAuthorizationResourceFragment(...); // do not return what users shouldn't see

EntityManager em = ...; // injected in the SLSB context
Query query = generator.getQuery(em);
Query countQuery = generator.getCountQuery(em);
long count = (Long) countQuery.getSingleResult();
List<SomeEntity> results = query.getResultList();

PageList<SomeEntity> results = null;
results = return new PageList<SomeEntity>(results, (int) count, generator.getPageControl());
return results;

However, nearly everything you see above is boilerplate. In other words, nearly the exact same lines will be repeated over and over again each time you need to create a generator to build the query for the data contained in some Criteria object. As a result, we've created a class that simplifies the use:

SomeEntityCriteria criteria = new SomeEntityCriteria();
CriteriaQueryGenerator generator = new CriteriaQueryGenerator(criteria);
if (authorizationManager.isInventoryManager(subject) == false) {
   generator.setAuthorizationResourceFragment(
      CriteriaQueryGenerator.AuthorizationTokenType.RESOURCE, 
      subject.getId());
}

CriteriaQueryRunner<SomeEntity> queryRunner =
   new CriteriaQueryRunner(criteria, generator, entityManager);
   return queryRunner.execute();

Authorization

TODO

Data Query / Count Query

The paginated displays used in the RHQ user interface require the size of the entire data set to be known, even though only one page of data will be displayed at a time. This is required to either:

1) conditionally render pagination controls - whether forward/backward should be enabled, which page number you're on, the size of the visible page
2) for scrollable tables, the relative size and placement of the scrollbar

To support this, the CriteriaQueryGenerator has two generation strategies encompassed in a single method "generator.getQueryString(boolean countQuery)".

If "false" is passed, you get the paginated, sorted data results:

SELECT {alias | projection}
FROM {getPersistentClass} {alias}
LEFT JOIN FETCH ...
WHERE ...
ORDER BY ...

If "true" is passed, you get a version of the query that gets the size of the entire result set:

SELECT COUNT({alias})
FROM {getPersistentClass} {alias}
WHERE ...

Usage

Searching for subjects

SubjectCriteria criteria = new SubjectCriteria();
criteria.addFilterFirstName("joe"); // filter by subjects whose first name is "joe"
criteria.fetchRoles(true); // fetch all role entities for each returned subject
criteria.addSortName(PageOrdering.ASC); // order by username ascending

CriteriaQueryGenerator generator = new CriteriaQueryGenerator(criteria);
System.out.println(generator.getQueryString(false)); // get data query string
System.out.println(generator.getQueryString(true)); // get count query string

Searching for resources

ResourceCriteria criteria = new ResourceCriteria();
criteria.addFilterResourceCategory(ResourceCategory.SERVER);
criteria.addFilterName("marques");
criteria.fetchAgent(true);
criteria.addSortResourceTypeName(PageOrdering.ASC);
criteria.setCaseSensitive(true); // all string filters will require an exact match
criteria.setFiltersOptional(true); // return category or name matches

CriteriaQueryGenerator generator = new CriteriaQueryGenerator(criteria);
System.out.println(generator.getQueryString(false)); // get data query string
System.out.println(generator.getQueryString(true)); // get count query string

Searching for alerts

AlertCriteria criteria = new AlertCriteria();
criteria.addFilterStartTime(System.currentTimeMillis() - 24*60*60*1000); // last 24 hrs
criteria.addFilterResourceIds(1, 2, 3);
criteria.addSortPriority(PageOrdering.DESC); // sort by priority first, HIGH->LOW
criteria.addSortCtime(PageOrdering.DESC); // then by ctime, most->least recent
criteria.setPaging(0, 100); // only the last 20 alerts

// setAuthorizationResourceFragments tells the generator how to join to resources
// to filter results down to what the user is authorized to see only
CriteriaQueryGenerator generator = new CriteriaQueryGenerator(criteria);
generator.setAuthorizationResourceFragment(AuthorizationTokenType.RESOURCE, "definition.resource", 1);
System.out.println(generator.getQueryString(false)); // get data query string
System.out.println(generator.getQueryString(true)); // get count query string
JBoss.org Content Archive (Read Only), exported from JBoss Community Documentation Editor at 2020-03-13 08:06:29 UTC, last content change 2013-09-18 19:41:36 UTC.