J4Fry Lookups

Characteristics

Attribute Value
Project name J4Fry Lookups
Prerequisites JDK >= 5.0, JSF 1.1 and 1.2 compliant implementation, JPA with webcontainer, EJB or OpenEJB, SQL compliant database
CVS module Lookups
Responsible developer Ganesh Jung
Alexander Bell
Michael Schneider

About

Configurable text output in Java is a nasty thing to use. The standard mechanism is the i18n support with message resource bundles. There are some grave drawbacks with this approach:

  • Message texts are rolled out with the application instead of residing in a database thus enforcing an application rollout to adjust some wording.
  • Aspects are restricted to language and country neglecting the need for further differentiation for user roles or other application environment parameters.

J4Fry Lookups close this gap for JSF environments by offering an aspect oriented aproach to arbitrary lookups with JPA backed database persistence and special JSF EL enhancements. Check yourself the conceptual power, the universal usability, the ease of use and the high performance of J4Fry lookups.

J4Fry Lookup prerequisites

To use J4Fry lookups in your JSF application you need to use

  • JPA with webcontainer, EJB or OpenEJ
  • JSF 1.1 or 1.2 compliant container
  • JDK >= 5.0

If you are using lookups outside of EJB you will need JSF and JPA support. JSF RI 1.2 and Hibernate 3 will do the job, these are your required jars:

  • antlr-2.7.6.jar
  • asm.jar
  • asm-attrs.jar
  • cglib-2.1.3.jar
  • commons-collections-3.2.jar
  • commons-logging-1.1.jar
  • ehcache-1.2.3.jar
  • ejb3-persistence.jar
  • hibernate-annotations.jar
  • hibernate-commons-annotations.jar
  • hibernate-entitymanager.jar
  • hibernate-validator.jar
  • hibernate3.jar
  • j4fry-jsf-jpa-bridge-1.2.jar
  • javassist.jar
  • jboss-archive-browsing.jar
  • jdbc2_0-stdext.jar
  • jta.jar
  • jsf-api.jar
  • jsf-impl.jar
  • oro-2.0.8.jar

J4Fry Lookup usage

Lookups incorporate a function that maps a set of aspects to a set of lookups. Let asx be a set of aspects and lsx a set of lookups:

lookup(asx) = lsx

with axi being the aspects and lxi the lookups:

asx = {ax1, ax2, ..., axn}) and
lsx = {lx1, lx2, ..., lxm}

where axi and lxj are key value pairs:

axi = {akeyxi, avaluexi}
lxj = {lkeyxj, lvaluexj}

And order upon {lx1, lx2, ..., lxm} is given by levels and alphanumeric order. Therefore each lxi has got a third property called level thus enhancing lxi to {lkeyxi, lvaluexi, llevelxi}.
lxi<lxj if and only if:

(llevelxi == null AND llevelxj == null AND lvaluexi<lvaluexj)
OR (llevelxi != null AND llevelxj == null)
OR (llevelxi != null AND llevelxj != null AND llevelxi<llevelxj)

Lookups are backed by a SQL database (see installation) that is provided with the appropriate data:

insert into t_aspectcollection (id, aspectcollectionkey, aspectkey, aspectvalue, version) values (nextval('seq_t_aspectCollection'), x, 'akeyx1', 'avaluex1', 1);
insert into t_aspectcollection (id, aspectcollectionkey, aspectkey, aspectvalue, version) values (nextval('seq_t_aspectCollection'), x, 'akeyx2', 'avaluex2', 1);
...
insert into t_aspectcollection (id, aspectcollectionkey, aspectkey, aspectvalue, version) values (nextval('seq_t_aspectCollection'), x, 'akeyxn', 'avaluexn', 1);

insert into t_lookup (id, aspectcollectionkey, key, value, level, version) values (nextval('seq_t_lookup'), x, 'lkeyx1', 'lvaluex1', llevelx1, 1);
insert into t_lookup (id, aspectcollectionkey, key, value, level, version) values (nextval('seq_t_lookup'), x, 'lkeyx2', 'lvaluex2', llevelx2, 1);
...
insert into t_lookup (id, aspectcollectionkey, key, value, level, version) values (nextval('seq_t_lookup'), x, 'lkeyxm', 'lvaluexm', llevelxm, 1);

Let the following be true:

asx = {ax1, ax2, ..., axn}
asy = {ay1, ay2, ..., ayn}
lsx = {lx1, lx2, ..., lxn}
lsy = {ly1, ly2, ..., lyn}
lookup(asx) = lsx
lookup(asy) = lsy

then lookups provide the concept of aspect collection mapping to define a union of both result sets. So, if asx if mapped onto asy then

lookup(asx) = lsx union lsy

A set of aspects asx can be mapped to more than one other set of aspects and if asx is mapped to asy and asz then

lookup(asx) = lsx union lsy union lsz

If asx is mapped to asy and asy is mapped to asz then equally

lookup(asx) = lsx union lsy union lsz

The mapping from asx to asy is defined in the SQL database:

insert into t_aspectcollectionmapping (id, aspectcollectionsource, aspectcollectiontarget, version) values (nextval('seq_t_aspectCollectionMapping'), x, y, 1);

The lookup retrieval language defines two dialects. One is embedded in the JSF EL to be used inside JSF pages and the other one provides a pure Java interface to be used within Java code. Both provide 3 types of lookup requests. Let {a1, a2, ..., an} be a set of aspects and lookup({a1, a2, ..., an})={l1, l2, ..., ln} with li={lkeyi,lvaluei} and ai={akeyi,avaluei}.

  • Type LOOKUP_LIST will return the set of lookups as a java.util.List<org.j4fry.lookup.model.Lookup> where each object of type org.j4fry.lookup.model.Lookup contains one of the key value pairs {lkeyi, lvaluei} so that lookup.getKey() == lkeyi and lookup.getValue() == lvaluei. Order of the returned list (l1, l2, ..., ln) is given by li<lj => i<j.
  • Type LOOKUP_VALUE will take an additional String parameter "key" and return a String. This will do the call to lookup({a1, a2, ..., an}) to retrieve {l1, l2, ..., ln}. If there exists li within {l1, l2, ..., ln} providing lkeyi == key the return value is lvaluei else the return value is key.
  • Type LOOKUP_SELECT_ITEMS is a convenience method that return a java.util.Map providing the value atribute to JSF's f:selectItems. To build the Map a java.util.List is retrieved as described for type LOOKUP_LIST. The elements of the List are iterated over and inserted into a java.util.LinkedHashMap m so that m.get(lvaluei) == lkeyi. Key and value are interchanged (normal maps would comply with m.get(key) == value) because JSF's h:selectOneMenu will interchange them back by displaying the maps keys and submitting the chosen keys value. Usage of java.util.LinkedHashMap will preserve the order of the original java.util.List.

These are the 3 request types expressed with JSL EL:

  • lookup.akey1[avalue1].akey2[avalue2]...akeyn[avaluen][LOOKUP_LIST]
  • lookup.akey1[avalue1].akey2[avalue2]...akeyn[avaluen][key][LOOKUP_VALUE]
  • lookup.akey1[avalue1].akey2[avalue2]...akeyn[avaluen][LOOKUP_SELECT_ITEMS]

And this is how to retrieve lookups within plain Java:

  • LookupBean.get().lookup(new Lookup().aspect(akey1,avalue1).aspect(akey2,avalue2)...aspect(akeyn,avaluen), "LIST");
  • LookupBean.get().lookup(new Lookup(key).aspect(akey1,avalue1).aspect(akey2,avalue2)...aspect(akeyn,avaluen), "VALUE");
  • LookupBean.get().lookup(new Lookup().aspect(akey1,avalue1).aspect(akey2,avalue2)...aspect(akeyn,avaluen), "SELECT_ITEMS");

If you use lookups with plain Java there is an extension that resolves JSF EL expressions inside a returned value. This extension is only available for type VALUE.

LookupBean.get().lookup(new Lookup(key).aspect(akey11,avalue11) .aspect(akey12,avalue12)...aspect(akey1n,avalue1n), "VALUE", Map params, new Lookup().aspect(akey21,avalue21) .aspect(akey22,avalue22)...aspect(akey2n,avalue2n));

will use aspect set as2 to lookup every value within map params. Each key value pair from map params will be used to create a ValueBinding based on it. With all of these ValueBindings set an attempt will be made to resolve the lookup value returned by as1 in combination with the key.

J4Fry Lookup Installation Guide

Depending on the environment you want lookups to run in you require different jar files to deploy with your application.

These are the database tables J4Fry lookups are based upon (this is postgres notation, other SQL databases will need slight modifications):

DROP TABLE t_aspectCollection;
DROP TABLE t_lookup;
DROP TABLE t_aspectCollectionMapping;

DROP SEQUENCE seq_t_aspectCollectionMapping;
DROP SEQUENCE seq_t_lookup;
DROP SEQUENCE seq_t_aspectCollection;

CREATE SEQUENCE seq_t_aspectCollectionMapping INCREMENT 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 CACHE 1;
CREATE SEQUENCE seq_t_lookup INCREMENT 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 CACHE 1;
CREATE SEQUENCE seq_t_aspectCollection INCREMENT 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 CACHE 1;

CREATE TABLE t_aspectCollectionMapping (
id NUMERIC(20) NOT NULL
, aspectCollectionSource NUMERIC(20) NOT NULL
, aspectCollectionTarget NUMERIC(20) NOT NULL
, version NUMERIC(5) NOT NULL
, PRIMARY KEY (id)
);

CREATE TABLE t_lookup (
id NUMERIC(20) NOT NULL
, aspectCollectionKey NUMERIC(20) NOT NULL
, key VARCHAR(100) NOT NULL
, value VARCHAR(1049) NOT NULL
, level NUMERIC(5)
, version NUMERIC(5) NOT NULL
, PRIMARY KEY (id)
);

CREATE TABLE t_aspectCollection (
id NUMERIC(20) NOT NULL
, aspectCollectionKey NUMERIC(20) NOT NULL
, aspectKey VARCHAR(100) NOT NULL
, aspectValue VARCHAR(100) NOT NULL
, version NUMERIC(5) NOT NULL
, PRIMARY KEY (id)
);

J4Fry lookups use JPA, the Java persistence API to access the database. Thus a persistence.xml for configuration of the database access is needed. If you are already using JPA please add these lines to your persistence.xml:

<class>org.j4fry.lookup.model.AspectCollection</class>
<class>org.j4fry.lookup.model.Lookup</class>
<class>org.j4fry.lookup.model.AspectCollectionMapping</class>

For a complete example please refer to our cookbook and follow the instructions for a quick setup with hibernate support. The examples persistence.xml is located at source/META-INF/persistence.xml.

If you use lookups outside of an EJB container, transactions are transparently provided by j4fry-jsf-jpa-bridge to your classpath. Each incoming HTTP request will automatically open a hibernate transaction that is automatically close before the response is written to the client. If you are using the J4Fry quick setup project with hibernate the jar is already included with the quick setup project.

View the datamodel

To view the clay-datamodel you have to install the Azzurri Clay Eclipse Plugin

Example

Let's say you want to retrieve a List of countries to fill a JSF component. To keep the example small our list only contains two countries: United Kingdom and Germany.

lookup.enum['countries'].language['english'][LOOKUP_VALUES]

should deliver the available countries in english language with United Kingdom displayed before Germany and:

lookup.enum['countries'].language['german'][LOOKUP_VALUES]

should deliver the available countries in german language with Germany displayed before United Kingdom. This is achieved with the following SQL data:

insert into t_aspectcollection (id, aspectcollectionkey, aspectkey, aspectvalue, version) values (nextval('seq_t_aspectCollection'), 1, 'enum', 'countries', 1);
insert into t_aspectcollection (id, aspectcollectionkey, aspectkey, aspectvalue, version) values (nextval('seq_t_aspectCollection'), 1, 'language', 'german', 1);
insert into t_aspectcollection (id, aspectcollectionkey, aspectkey, aspectvalue, version) values (nextval('seq_t_aspectCollection'), 2, 'enum', 'countries', 1);
insert into t_aspectcollection (id, aspectcollectionkey, aspectkey, aspectvalue, version) values (nextval('seq_t_aspectCollection'), 2, 'language', 'english', 1);
insert into t_lookup (id, aspectcollectionkey, key, value, level, version) values (nextval('seq_t_lookup'), 1, 'D', 'Deutschland', 10, 1);
insert into t_lookup (id, aspectcollectionkey, key, value, version) values (nextval('seq_t_lookup'), 2, 'D', 'Germany', 1);
insert into t_lookup (id, aspectcollectionkey, key, value, version) values (nextval('seq_t_lookup'), 1, 'GB', 'Großbrittanien', 1);
insert into t_lookup (id, aspectcollectionkey, key, value, level, version) values (nextval('seq_t_lookup'), 2, 'GB', 'United Kingdom', 10, 1);

When editing an address country is a required field, so we don't need a default value. When searching an address we want to use the same lookup, but we want to add an empty default value with key==-1. This is done through a new aspectcollection mapped to the old one that contains the default value in addition to the countries. The defaults get a lower level then the countries to get them displayed first:

lookup.enum['countries'].language['german'].default['search'][LOOKUP_VALUES]

and

lookup.enum['countries'].language['english'].default['search'][LOOKUP_VALUES]

which is done through these SQL data:

insert into t_aspectcollection (id, aspectcollectionkey, aspectkey, aspectvalue, version) values (nextval('seq_t_aspectCollection'), 3, 'enum', 'countries', 1);
insert into t_aspectcollection (id, aspectcollectionkey, aspectkey, aspectvalue, version) values (nextval('seq_t_aspectCollection'), 3, 'language', 'german', 1);
insert into t_aspectcollection (id, aspectcollectionkey, aspectkey, aspectvalue, version) values (nextval('seq_t_aspectCollection'), 3, 'default', 'search', 1);
insert into t_aspectcollection (id, aspectcollectionkey, aspectkey, aspectvalue, version) values (nextval('seq_t_aspectCollection'), 4, 'enum', 'countries', 1);
insert into t_aspectcollection (id, aspectcollectionkey, aspectkey, aspectvalue, version) values (nextval('seq_t_aspectCollection'), 4, 'language', 'english', 1);
insert into t_aspectcollection (id, aspectcollectionkey, aspectkey, aspectvalue, version) values (nextval('seq_t_aspectCollection'), 4, 'default', 'search', 1);
insert into t_aspectcollectionmapping (id, aspectcollectionsource, aspectcollectiontarget, version) values (nextval('seq_t_aspectCollectionMapping'), 3, 1, 1);
insert into t_aspectcollectionmapping (id, aspectcollectionsource, aspectcollectiontarget, version) values (nextval('seq_t_aspectCollectionMapping'), 4, 2, 1);
insert into t_lookup (id, aspectcollectionkey, key, value, level, version) values (nextval('seq_t_lookup'), 3, '-1', '', 5, 1);
insert into t_lookup (id, aspectcollectionkey, key, value, level, version) values (nextval('seq_t_lookup'), 4, '-1', '', 5, 1);