cocoon-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Jeremy Quinn <j...@media.demon.co.uk>
Subject Persisting SimpleLuceneQuery [Long]
Date Fri, 29 Oct 2004 10:41:37 GMT
Hi All

At the GT, I got the chance to meet Antonio (oh joy !!) and one of the 
many things we talked about was the possibility of him giving me some 
help learning Apache ORO. He very graciously accepted!!!

I have a project, the o.a.c.bean.query.SimpleLuceneQuery, in Cocoon, 
which is possibly an ideal candidate, as it comes out of a project for 
work and I have already made it persistable, using Hibernate.

What I hope to do is to make the Queries persistable in HSQLDB via ORO 
and add this to Cocoon as a new Block, both as a sample and as a useful 
module that should be easy to add to your own project.

Antonio's recommendation was that we discuss this conversion on the Dev 
list, as other members of the list could also benefit from this.

So, unless anyone has any objections ...... here goes !!!

Background:

The QueryBean is a simple Bean for allowing the User to assemble 
complex Queries on a Lucene index via CForms, without having to know 
the Lucene Query Language. Try it, it is in the Lucene Samples. I have 
actually completely re-written the flowscript in my local version to be 
much cleaner, but the current version in Cocoon allows you to do the 
following:

	Quick Queries: a simple query assembled from request params
	Complex Queries: a CForms repeater to allow multiple Queries and'd or 
or'd together
	Multiple match types (like, contains, exact etc.).
	Easy to setup field Queries.
	Query History kept in your Session, you can re-use and edit previous 
Queries

Structure:

A Query is made up of a single SimpleLuceneQueryBean, which has at 
least one SimpleLuceneCriterionBean(s) in it's Collection.

The (currently PostGres) DB Schema is very simple, it looks like this:

CREATE TABLE query (
	id serial unique,            /* the unique persistence ID */
	user_id text NOT NULL, /* the ID of the owner */
	q_date timestamp,        /* the date it was saved */
	q_bool text,                  /* how the criteria are combined */
	q_name text,                /* the name of the saved query */
	q_type text,                  /* the type of the saved query (ie. 
which CForm) */
	q_size integer               /* the number of results to show per page 
*/
);

CREATE TABLE criterion (
	id serial unique,            /*  the unique persistence ID  */
	query_id integer,          /* the query this belongs to */
	c_field text,                  /* the lucene field to search in */
	c_match text,               /* the type of match to perform */
	c_term text,                 /* the term to search for */
	c_position integer,       /* the index of this criterion in the 
query's criteria */
	foreign key ("query_id") references query("id")
);

GRANT INSERT, SELECT, UPDATE, DELETE ON query, query_id_seq, criterion, 
criterion_id_seq TO "cocoon";

As you can see from this, there are a bunch of text fields to hold info 
about the query, and a one-to-many relationship between the Query and 
the Criterion(s). If you compare this with the current state of the 
Beans in Cocoon, the following changes have been made:

	Added the property 'user' to the Query so we can see which are yours
	Changed the name of the property that hold the query text in the 
Criterion from 'value' to 'term' to make it clearer.

Mapping:

Below, is the Hibernate Mapping for the Query and Criterion Beans, 
adjusted to the Cocoon Package. As you can see, it is about as simple 
as you can get.

<hibernate-mapping package="org.apache.cocoon.bean.query">
   <class name="SimpleLuceneQueryBean" table="query">
     <id name="id" column="id" type="long">
       <generator class="sequence">
         <param name="sequence">query_id_seq</param>
       </generator>
     </id>
     <property name="bool" column="q_bool" type="string"/>
     <property name="date" column="q_date" type="timestamp"/>
     <property name="name" column="q_name" type="string"/>
     <property name="type" column="q_type" type="string"/>
     <property name="size" column="q_size" type="long"/>
     <property name="user" column="user_id" type="string"/>
     <list name="criteria" table="criterion" cascade="all-delete-orphan" 
lazy="false">
       <key column="query_id"/>
       <index column="c_position"/>
       <one-to-many class="SimpleLuceneCriterionBean"/>
     </list>
   </class>
</hibernate-mapping>

<hibernate-mapping package="org.apache.cocoon.bean.query">
   <class name="SimpleLuceneCriterionBean" table="criterion">
     <id name="id" column="id" type="long">
       <generator class="sequence">
         <param name="sequence">criterion_id_seq</param>
       </generator>
     </id>
     <property name="field" column="c_field" type="string"/>
     <property name="match" column="c_match" type="string"/>
     <property name="term" column="c_term" type="string"/>
   </class>
</hibernate-mapping>

The FlowScript:

This is the QueryFavourites JavaScript Object Library that handles most 
of the work, you will see that it uses our PersistanceFactory to manage 
the connection to PostGres. The error messages it outputs are i18n 
keys.

importClass(Packages.net.sf.hibernate.expression.Expression);
importClass(Packages.net.sf.hibernate.expression.Order);
importPackage(Packages.org.apache.cocoon.bean.query);

// QueryFavourites constructor
function QueryFavourites(user) {
   this._user = user;
   this._factory = 
cocoon.getComponent(co.uk.our.PersistanceFactory.ROLE);
}

// add a Query to the QueryFavourites
QueryFavourites.prototype.add = function(query) {
   query.user = this._user;
   query.date = new java.util.Date();
   var session = null;
   var tx = null;
   try {
     session = this._factory.createSession();
     tx = this._factory.startTransaction(session);
     var id = session.save(query);
     this._factory.endTransaction(tx);
   } catch (error) {
     cocoon.log.error(error);
     this._factory.undoTransaction(tx);
     throw (error);
   } finally {
     session.close();
   }
}

// remove a Query from the QueryFavourites, using the favourites ID
QueryFavourites.prototype.remove = function(id) {
   var session = null;
   var tx = null;
   var i;
   try {
     i = new java.lang.Long(id)
   } catch (error) {
     cocoon.log.error(error);
     throw("error.no.favourite");
   }
   try {
     session = this._factory.createSession();
     tx = this._factory.startTransaction(session);
     var query = session.load(SimpleLuceneQueryBean, i);
     if (query != null) {
       session["delete"](query);
     } else {
       throw ("error.no.favourite");
     }
     this._factory.endTransaction(tx);
   } catch (error) {
     cocoon.log.error(error);
     this._factory.undoTransaction(tx);
     throw (error);
   } finally {
     session.close();
   }
}

// get a Query from the QueryFavourites using the favourites ID
QueryFavourites.prototype.get = function(id) {
   var session = null;
   var i;
   try {
     i = new java.lang.Long(id)
   } catch (error) {
     cocoon.log.error(error);
     throw("error.no.favourite");
   }
   try {
     session = this._factory.createSession();
     var query = session.load(SimpleLuceneQueryBean, i);
     if (query == null) {
       throw("error.no.favourite");
     } else {
       return (query);
     }
   } catch (error) {
     cocoon.log.error(error);
     throw (error);
   } finally {
     session.close();
   }
}

// get a list of Queries from the QueryFavourites
QueryFavourites.prototype.list = function() {
   var session = null;
   try {
     session = this._factory.createSession();
     var query = session.createCriteria(SimpleLuceneQueryBean)
       .add(Expression.eq("user", this._user))
       .addOrder(Order.desc("date"));
     return (query.list());
   } catch (error) {
     cocoon.log.error(error);
     throw (error);
   } finally {
     session.close();
   }
}

// close the QueryFavourites
QueryFavourites.prototype.close = function() {
	cocoon.releaseComponent(this._factory);
}

Next, we have the FlowScript that is called from the Sitemap:

// display the User's Favourite Searches
function showFavourites() {
   var favourites = null;
   try {
     favourites = new QueryFavourites(cocoon.parameters["user-id"]);
     cocoon.sendPage(cocoon.parameters["screen"], {queries: 
favourites.list()});
   } catch (error) {
     cocoon.log.error(error);
     cocoon.sendPage("screen/error", {message: error});
   } finally {
     if (favourites != null) favourites.close();
   }
}

// add a history item to the User's Favourite Searches, using the 
History ID
function addFavourite() {
   var history = new QueryHistory(cocoon.parameters["history"]);
   var favourites = null;
   try {
     favourites = new QueryFavourites(cocoon.parameters["user-id"]);
     var query = history.get(cocoon.parameters["hid"]);
     if (query != null) {
       favourites.add(query);
     }
     cocoon.sendPage(cocoon.parameters["screen"], {queries: 
favourites.list()});
   } catch (error) {
     cocoon.log.error(error);
     cocoon.sendPage("screen/error", {message: error});
   } finally {
     if (favourites != null) favourites.close();
   }
}

// remove an item from the User's Favourite Searches, using it's ID
function removeFavourite() {
   var favourites = null;
   try {
     favourites = new QueryFavourites(cocoon.parameters["user-id"]);
     favourites.remove(cocoon.parameters["fid"]);
     cocoon.sendPage(cocoon.parameters["screen"], {queries: 
favourites.list()});
   } catch (error) {
     cocoon.log.error(error);
     cocoon.sendPage("screen/error", {message: error});
   } finally {
     if (favourites != null) favourites.close();
   }
}

And now some selected bits from the Sitemap:


<!-- list favourites -->
<map:match pattern="favourites.html">
   <map:call function="showFavourites">
     <map:parameter name="screen" value="screen/favourites"/>
     <map:parameter name="user-id" 
value="{session-context:authentication/authentication/data/ldap/uid}"/>
   </map:call>
</map:match>

<!-- add a history item to the favourites, using the history ID -->
<map:match pattern="add-favourite.html">
   <map:call function="addFavourite">
     <map:parameter name="screen" value="screen/favourites"/>
     <map:parameter name="user-id" 
value="{session-context:authentication/authentication/data/ldap/uid}"/>
     <map:parameter name="hid" value="{request-param:hid}"/>
     <map:parameter name="history" value="{global:history}"/>
   </map:call>
</map:match>

<!-- remove an item from the favourites, using the favourite ID -->
<map:match pattern="remove-favourite.html">
   <map:call function="removeFavourite">
     <map:parameter name="screen" value="screen/favourites"/>
     <map:parameter name="user-id" 
value="{session-context:authentication/authentication/data/ldap/uid}"/>
     <map:parameter name="fid" value="{request-param:fid}"/>
   </map:call>
</map:match>

Phew!!!!

I have left lots of bits out, but hopefully none of them are relevant 
to this discussion.

I suppose one of the first questions that needs asking before going 
ahead and making an Apache ORO equivalent to the above, is : is the 
structure of the SimpleLuceneQuery/SimpleLuceneQueryBean and 
SimpleLuceneCriterion/SimpleLuceneCriterionBean as good as it needs to 
be? Can it be improved? Is it easy enough to extend it? Is the 
Interface overkill? If it is desirable, is it properly done etc. etc.

The 'bool' and 'match' fields would probably be candidates for being 
enumerations, but I never worked out how to do that.

I assume there is already an equivalent to our PersistenceFactory for 
ORO in Cocoon.

Should QueryFavourites.js be rewritten in Java? Should it be in the 
style of a DAO?

I guess I need to add a new Block to Cocoon, eek I have never done one 
!!



Once again, I would like to express my thanks to Antonio for offering 
to help with this.

regards Jeremy

--------------------------------------------------------

                   If email from this address is not signed
                                 IT IS NOT FROM ME

                         Always check the label, folks !!!!!
--------------------------------------------------------


Mime
View raw message