Just another WordPress.com site

Latest

Quick Start Tapestry Hibernate Reusable Form, List, for Create, Read, Update, Delete (CRUD), Search (S), Query By Example (QBE): CRUDS-QBE

Objective: Create a single form interface for an entity, submitting to, and generating a single listing, of its persisted instances, supporting CRUDS-QBE (More….).

This toy application, crudsqbe, can be developed on any old machine supporting Java, just need a text editor, a browser and access to the cloud (for downloads). It builds on top of Hibernate, Lucence using Tapestry. Any database can be used, instructions below are for PostgreSQL.

IMPORTANT: Do not, obviously, publish this on a web-accessible (production) server, without PROPERLY securing it (NOT covered in this post). This will open up your database to the outside world. Matter of fact, use everything below AT YOUR OWN RISK…this is just a proof-of-concept, i.e. NOT meant to be thread-safe etc…MUCH work needed for prime-time. Comments welcome, may be posted below, will be moderated periodically, edited, perhaps even ignored. Link to source-code is somewhere at the end of this blog…so, read first ;-)…

Assume clean start (if not applicable, just skip, adapt as you go along):

1. Download the Java Development Kit (JDK) from

http://www.oracle.com/technetwork/java/javase/downloads/index.html

Java SE 6 Update 32 Windows x64 (64-bit) jdk-6u32-windows-x64.exe (59.66 MB). Installed to c:/jdk1.6.0_32.

2. Download Maven from

http://maven.apache.org

File apache-maven-3.0.4-bin.zip and extracted everything under apache-maven-3.0.4 folder to c:/maven3.0.4.

3. Set environment variables JAVA_HOME and M2_HOME, then add to path: Control Panel –> System and Maint –> System –> Advanced System Settings –> Environment Variables.   Click New under System Variables.  Enter variable JAVE_HOME with value c:\jdk1.6.0_32, M2_HOME with value c:\maven3.0.4. After that append %JAVA_HOME%\bin;%M2_HOME%\bin to the Path environment variable.

4. Verifying steps 1-3. Open a command shell. Type

mvn -version

Ensure all environment variables and paths are working, as follows:

Apache Maven 3.0.4 (r1232337; 2012-01-17 02:44:56-0600)
Maven home: c:\maven3.0.4
Java version: 1.6.0_32, vendor: Sun Microsystems Inc.
Java home: c:\jdk1.6.0_32\jre
Default locale: en_US, platform encoding: Cp1252
OS name: "windows vista", version: "6.0", arch: "amd64", family: "windows"

5. Create your projects directory, say, c:/projects and change into it, type

mvn archetype:generate -DarchetypeCatalog=http://tapestry.apache.org

(The highlights below indicate interactive user input with the maven build process to select the version 5 archetype, version 5.3.3, set groupId, artifactID etc)…


[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom >>
>
[INFO]
[INFO] <<< maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom <<
< [INFO] [INFO] --- maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom -- - [INFO] Generating project in Interactive mode [INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven. archetypes:maven-archetype-quickstart:1.0) Choose archetype: 1: http://tapestry.apache.org -> org.apache.tapestry:quickstart (Tapestry 5 Quickstart Project)
2: http://tapestry.apache.org -> org.apache.tapestry:tapestry-archetype (Tapestry 4.1.6 Archetype)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 1
Choose org.apache.tapestry:quickstart version:
1: 5.0.19
2: 5.1.0.5
3: 5.2.6
4: 5.3
5: 5.3.1
6: 5.3.2
7: 5.3.3
Choose a number: 7: 7
Downloading: http://tapestry.apache.org/org/apache/tapestry/quickstart/5.3.3/qui
ckstart-5.3.3.jar
Downloading: http://repo.maven.apache.org/maven2/org/apache/tapestry/quickstart/
5.3.3/quickstart-5.3.3.jar
Downloaded: http://repo.maven.apache.org/maven2/org/apache/tapestry/quickstart/5
.3.3/quickstart-5.3.3.jar (88 KB at 59.6 KB/sec)
Downloading: http://tapestry.apache.org/org/apache/tapestry/quickstart/5.3.3/qui
ckstart-5.3.3.pom
Downloading: http://repo.maven.apache.org/maven2/org/apache/tapestry/quickstart/
5.3.3/quickstart-5.3.3.pom
Downloaded: http://repo.maven.apache.org/maven2/org/apache/tapestry/quickstart/5
.3.3/quickstart-5.3.3.pom (402 B at 0.8 KB/sec)
Define value for property 'groupId': : org.ideademo
Define value for property 'artifactId': : crudsqbe
Define value for property 'version': 1.0-SNAPSHOT: :
Define value for property 'package': org.ideademo: : org.ideademo.crudsqbe
Confirm properties configuration:
groupId: org.ideademo
artifactId: crudsqbe
version: 1.0-SNAPSHOT
package: org.ideademo.crudsqbe
 Y: :
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: quickstart:5.3.3
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: org.ideademo
[INFO] Parameter: artifactId, Value: crudsqbe
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: org.ideademo.crudsqbe
[INFO] Parameter: packageInPathFormat, Value: org/ideademo/crudsqbe
[INFO] Parameter: package, Value: org.ideademo.crudsqbe
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: groupId, Value: org.ideademo
[INFO] Parameter: artifactId, Value: crudsqbe
[WARNING] Don't override file C:\projects\crudsqbe\src\test\java
[WARNING] Don't override file C:\projects\crudsqbe\src\main\webapp
[WARNING] Don't override file C:\projects\crudsqbe\src\main\resources\org\ideademo\crudsqbe
[WARNING] Don't override file C:\projects\crudsqbe\src\test\resources
[WARNING] Don't override file C:\projects\crudsqbe\src\test\conf
[WARNING] Don't override file C:\projects\crudsqbe\src\site
[INFO] project created from Archetype in dir: C:\projects\crudsqbe
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4:01.004s
[INFO] Finished at: Sat Apr 28 02:12:42 CDT 2012
[INFO] Final Memory: 9M/80M
[INFO] ------------------------------------------------------------------------

6. Change directory to c:\projects\crudsqbe, FIRST, and, THEN AND ONLY THEN, type mvn jetty:run [Enter]

[This will generate plenty of console output that ends in the jetty server running as below]

Application 'app' (version 1.0-SNAPSHOT-DEV) startup time: 296 ms to build IoC Registry, 1,497 ms overall.

 ______                  __             ____
/_  __/__ ____  ___ ___ / /_______ __  / __/
 / / / _ `/ _ \/ -_|_-</ __/ __/ // / /__ \
/_/  \_,_/ .__/\__/___/\__/_/  \_, / /____/
        /_/                   /___/  5.3.3 (development mode)

2012-04-28 02:28:57.467::INFO: Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server

7.  Open browser, Verify http://localhost:8080/crudsqbe.  

Within cmd shell, do CTRL+C, Yes [Enter] to stop Jetty, for now.

8. Setup Database Server.

Going with Postgres 9.1.3 from

http://www.postgresql.org/

Downloaded the 64-bit version: postgresql-9.1.3-1-windows-x64.exe

Installed in C:\PostgreSQL\9.1. Password for postgres super user is welcome123, rest defaults. No need for stackbuilder. Open pgAdmin and create a database called “crudsqbe”. Start –> PostgreSQL –> pgAdmin III. Right click on PostgresSQL, choose connect. Type the password welcome123, save it. Right click on Databases, choose New Database and type crudsqbe, OK. Close/minimize PgAdminIII

OPTIONAL (skip, just FYI). Since just command shell was promised, the equivalent pgAdmin creation of NEW crudsqbe database would be:

C:\PostgreSQL\9.1\bin\createdb --username postgres --no-password --host localhost --port 5432 -T template0 crudsqbe

9. Modify pom.xml with latest info from source like http://mvnrepository.com/ to activate tapestry hibernate, add jdbc driver and lucene search api. PARTIAL listing is shown below (note that you would simply CHANGE tapestry-core to tapestry-hibernate):


        <!-- Too set up an application with a database, change the artifactId below to
             tapestry-hibernate, and add a dependency on your JDBC driver. You'll also
             need to add Hibernate configuration files, such as hibernate.cfg.xml. -->
        <dependency>
            <groupId>org.apache.tapestry</groupId>
            <artifactId>tapestry-hibernate</artifactId>
            <version>${tapestry-release-version}</version>
        </dependency>

        <!--  JDBC driver -->
	<dependency>
 	 <groupId>postgresql</groupId>
         <artifactId>postgresql</artifactId>
         <version>9.1-901.jdbc4</version>
        </dependency>
            
	 <!-- Search API -->
        <dependency>
          <groupId>org.hibernate</groupId>
          <artifactId>hibernate-search</artifactId>
          <version>3.4.2.Final</version>
        </dependency>

10. Place the hibernate.cfg.xml under crudsqbe/src/main/resources directory (where you should also see a log4j.properties file):

<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
        <property name="hibernate.connection.url">jdbc:postgresql:crudsqbe</property>
        <property name="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</property>
        <property name="hibernate.connection.username">postgres</property>
        <property name="hibernate.connection.password">welcome123</property>
        <property name="hbm2ddl.auto">update</property>
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>
        <property name="hibernate.search.default.directory_provider">filesystem</property>
        <property name="hibernate.search.default.indexBase">lucene/indexes</property>
    </session-factory>
</hibernate-configuration>

11. Create directory called “entities” under C:\projects\crudsqbe\src\main\java\org\ideademo\crudsqbe. This folder will be scanned by Tapestry-hibernate for persistence.

 Directory of C:\projects\crudsqbe\src\main\java\org\ideademo\crudsqbe

04/28/2012  03:42 AM    <DIR>          .
04/28/2012  03:42 AM    <DIR>          ..
04/28/2012  02:12 AM    <DIR>          components
04/28/2012  03:42 AM    <DIR>          entities
04/28/2012  02:12 AM    <DIR>          pages
04/28/2012  02:12 AM    <DIR>          services

12. Create the example entity class Person.java within the new entities directory.

package org.ideademo.crudsqbe.entities;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;

import org.apache.tapestry5.beaneditor.NonVisual;


@Entity @Indexed
public class Person
{

	
  /////////////////////////////////////////////////////////////////////////////
  //  Internal auto generated Id field, also document id for lucene indexing 
  //
	
  @Id @GeneratedValue @DocumentId @NonVisual
  private Long id;

  
  ///////////////////////////////////////////////////////////
  //  Field annotation is used by Lucene to power text search
  //  Hibernate QBE search can also do  text over this field 
  
  @Field
  private String name="";

  
  ////////////////////////////////////////////////////////
  // QBE Search fields.  Lucene does not care about these
  
  private boolean politician = false;
  private boolean actor = false; 
  
  
  
  //////////////////////////////////
  //  Getters, Setters 
  //
  
  
  public Long getId() 
  {
    return id;
  }

  public void setId(Long Id) 
  {
    this.id = Id;
  }

  public String getName() 
  {
	return name;
  }
	
  public void setName(String name) 
  {
	this.name = name;
  }
	
  public boolean isPolitician() 
  {
	return politician;
  }
	
  public void setPolitician(boolean politician) 
  {
	this.politician = politician;
  }
	
  public boolean isActor() 
  {
	return actor;
  }
	
  public void setActor(boolean actor) 
  {
	this.actor = actor;
  }

  public String toString()
  {
	  return "Person.name = " + this.getName() + ",  actor? = " + this.isActor() + ", politician? = " + this.isPolitician(); 
  }

  
}

13. Create the input form for Person. As a tapestry template called EditPerson.tml within the following directory
C:\projects\crudsqbe\src\main\resources\org\ideademo\crudsqbe\pages\person
(need to mkdir the “person” under “pages” directory)

<html t:type="layout" title="Add/Update Person"
      xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
      xmlns:p="tapestry:parameter">


<t:form t:id="personForm">
  <t:errors />
  
   <label for="name">Name</label>
    <t:textarea t:id="name" t:validate="maxlength=255" rows="1" cols="50" value="person.name" /><br /><br />
    <t:checkbox t:id="politician"  t:value="person.politician" />
      <t:label for="politician">Politician</t:label>&nbsp;&nbsp;
    <t:checkbox t:id="actor"  t:value="person.actor" />
      <t:label for="actor">Actor</t:label><br /><br />
      
   <t:submit value="Add/Update" />&nbsp;<t:submit t:id="search" value="Search" />&nbsp;
   (<t:pagelink page="prop:componentResources.pageName">CLEAR - form</t:pagelink>)
   (<t:pagelink page="persons">CANCEL - go to list</t:pagelink>)

</t:form>
</html>

Without the need for reuse as QBE, i.e. the search button, this input form could have been as simple as

 <t:beaneditform object="person" />

It is based on Beaneditor, which could also be used, if there there was not much requirement regarding custom labels, styles etc. Aside: the style elements (class=appnitro etc will not work in this demo, which will just be using default styles.

 <t:form> 
   <t:errors />
   <t:beaneditor object="person" />
   <t:submit value="Create/Edit" /><t:submit t:id="search" value="Search"/>
 </t:form>

14. Create corresponding Tapestry java file EditPerson.java within
C:\projects\crudsqbe\src\main\java\org\ideademo\crudsqbe\pages\person
(need to mkdir the “person” under “pages” directory)

package org.ideademo.crudsqbe.pages.person;

import org.hibernate.Session;

import org.apache.tapestry5.annotations.InjectPage;
import org.apache.tapestry5.annotations.PageActivationContext;
import org.apache.tapestry5.annotations.Property;

import org.apache.tapestry5.hibernate.annotations.CommitAfter;

import org.apache.tapestry5.ioc.annotations.Inject;

import org.ideademo.crudsqbe.entities.Person;
import org.ideademo.crudsqbe.pages.Persons;

import org.apache.log4j.Logger;


public class EditPerson 
{

  private static Logger logger = Logger.getLogger(EditPerson.class);	
	
  @Inject
  private Session session;

  @PageActivationContext 
  @Property
  private Person person;
  
  @InjectPage
  private Persons persons;
  
  //////////////////////////////
  // Search Buttom pressed
  Object onSelectedFromSearch() 
  {
    logger.info("QBE: " + person);
    persons.setPersonExample(person);
    return persons;
  }
  
  
  @CommitAfter
  Object onSuccess()
  {
	logger.info("Created/Updated: " + person);
    session.saveOrUpdate(person);
    return persons;
  }


  
  ////////////////////////////////////////////////////////////////////////////////////
  //  The following event handlers permit reuse of Edit form as Create and also do QBE 
  //  by providing the form with person object to bind with (overlay) its data fields
  //  
  //  Not needed for BeanEditor which will instance the object if NULL when    
  //  rendering or handling a form submission
  
  void onPrepareForRender()
  {
    if (this.person == null) 
    {
      this.person = new Person();
    }
  }

  ///////////////////////////////////////////////
  // obviates persisting person object in session
  void onPrepareForSubmit()
  {
    if (this.person == null) 
    {
      this.person = new Person();
    }
  }
}

15. Persons.tml within pages directory under resources
C:\projects\crudsqbe\src\main\resources\org\ideademo\crudsqbe\pages\

<html t:type="layout" title="Persons List"
      xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
      xmlns:p="tapestry:parameter">

 <body>
   <div align="center">
       <t:form t:id="searchForm">
         <t:textField t:id="searchText" size="30" value="searchText" />    
         <t:submit t:id="search" value="Search" /><t:submit t:id="clear" value="Clear/Show All" />  
       </t:form>
   </div>
 
  <div style="background-color:orange;text-align:center;font-family:Verdana, Geneva, sans-serif">      
   <t:pagelink page="person/edit">Create NEW Personal Record (OR Advanced Search)</t:pagelink>
  </div>
  <div align="center">
  <t:grid source="persons" row="personRow" add="delete">
    <p:deleteCell>
      <t:actionlink t:id="delete" context="personRow.id">Delete</t:actionlink>
    </p:deleteCell>
    <p:nameCell>
     <t:pagelink page="person/edit" context="personRow.id">${personRow.name}</t:pagelink>
    </p:nameCell>
     <p:empty>
       <p>There are no people (matching the criteria); you can <t:pagelink page="person/edit">add some</t:pagelink> 
           (or <t:actionlink t:id="showAll">show all</t:actionlink>)</p>
     </p:empty>
  </t:grid>
  </div>
 </body>
</html>

16. Persons.java within the following pages directory under main/java
C:\projects\crudsqbe\src\main\java\org\ideademo\crudsqbe\pages\

package org.ideademo.crudsqbe.pages;

import java.util.List;

import org.apache.tapestry5.PersistenceConstants;

import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.annotations.Persist;

import org.apache.tapestry5.hibernate.annotations.CommitAfter;
import org.apache.tapestry5.hibernate.HibernateSessionManager;

import org.apache.tapestry5.ioc.annotations.Inject;


import org.hibernate.Session;

import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;

import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.hibernate.search.query.dsl.QueryBuilder;


import org.ideademo.crudsqbe.entities.Person;

import org.apache.log4j.Logger;



public class Persons 
{
	 
  private static Logger logger = Logger.getLogger(Persons.class);
  
  
  /////////////////////////////
  //  Drives QBE Search
  @Persist (PersistenceConstants.FLASH)
  private Person personExample;
  
  
  //////////////////////////////
  // Used in rendering Grid Row
  @SuppressWarnings("unused")
  @Property 
  private Person personRow;
  
  @Property
  @Persist (PersistenceConstants.FLASH)
  private String searchText;
  
  @Inject
  private Session session;
  
  @Inject
  private HibernateSessionManager sessionManager;

  
  ////////////////////////////////////////////////////////////////////////////////////////////////////////
  //  Entity List generator - QBE, Text Search or Show All 
  //

  @SuppressWarnings("unchecked")
  public List<Person> getPersons()
  {
	  
    if(personExample != null)
    {
       Example example = Example.create(personExample).excludeFalse().ignoreCase().enableLike(MatchMode.ANYWHERE);
       return session.createCriteria(Person.class).add(example).list();

    }
    else if (searchText != null && searchText.trim().length() > 0)
    {
      FullTextSession fullTextSession = Search.getFullTextSession(sessionManager.getSession());  
      try
      {
        fullTextSession.createIndexer().startAndWait();
      }
      catch (java.lang.InterruptedException e)
      {
        logger.warn("Lucene Indexing was interrupted by something " + e);
      }
      QueryBuilder qb = fullTextSession.getSearchFactory().buildQueryBuilder().forEntity( Person.class ).get();
      org.apache.lucene.search.Query luceneQuery = qb
			    .keyword()
			    .onFields("name")
			    .matching(searchText)
			    .createQuery();
      	  
       return fullTextSession.createFullTextQuery(luceneQuery, Person.class).list();
    }
    else
    {
       // default - unfiltered - all entitites list 
       return session.createCriteria(Person.class).list();
    }
 

  }
  
  
  
  ////////////////////////////////////////////////////
  //  Action/Event Handlers 
  //
  
  @CommitAfter
  void onActionFromDelete(long id)
  {
    Person a = (Person) session.get(Person.class, id);
    session.delete(a);
    logger.info("Deleted " + a.getName());
  }

  /**
   * resets the list
   */
  void onActionFromShowAll()
  {
    this.searchText = "";
    this.personExample = null;
  }

  Object onSelectedFromSearch() 
  {
    return null; 
  }

  Object onSelectedFromClear() 
  {
    onActionFromShowAll(); 
    return null; 
  }
  
  
  
  ///////////////////////////////////////////
  //  Setters
  // 

  public void setPersonExample(Person person) 
  {
	this.personExample = person;
  }

}

17. Need to add search capability to Hibernate to filter out boolean properties that are set to false (JIRA). Specifically, in the context of form-driven QBE, false corresponds to an empty checkbox. This is to be treated just like an empty text field – ignored.

This nuance was added by first downloading the source code from

http://grepcode.com/snapshot/repo1.maven.org/maven2/org.hibernate/hibernate-search/3.4.2.Final

Create package org.hibernate.criterion under the main/java. Include the following two source files as-is.

/**
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2010, Red Hat Inc. or third-party contributors as
 * indicated by the @author tags or express copyright attribution
 * statements applied by the authors.  All third-party contributions are
 * distributed under license by Red Hat Inc.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */
package org.hibernate.criterion;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.hibernate.Criteria;
import org.hibernate.EntityMode;
import org.hibernate.HibernateException;
import org.hibernate.engine.TypedValue;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.CompositeType;
import org.hibernate.type.Type;
import org.hibernate.util.StringHelper;

/**
 * Support for query by example.
 * <pre>
 * List results = session.createCriteria(Parent.class)
 *     .add( Example.create(parent).ignoreCase() )
 *     .createCriteria("child")
 *         .add( Example.create( parent.getChild() ) )
 *     .list();
 * </pre>
 * "Examples" may be mixed and matched with "Expressions" in the same <tt>Criteria</tt>.
 * @see org.hibernate.Criteria
 * @author Gavin King
 */

public class Example implements Criterion 
{

	private final Object entity;
	private final Set excludedProperties = new HashSet();
	private PropertySelector selector;
	private boolean isLikeEnabled;
	private Character escapeCharacter;
	private boolean isIgnoreCaseEnabled;
	private MatchMode matchMode;

	/**
	 * A strategy for choosing property values for inclusion in the query
	 * criteria
	 */

	public static interface PropertySelector extends Serializable {
		public boolean include(Object propertyValue, String propertyName, Type type);
	}

	private static final PropertySelector NOT_NULL = new NotNullPropertySelector();
	private static final PropertySelector ALL = new AllPropertySelector();
	private static final PropertySelector NOT_NULL_OR_ZERO = new NotNullOrZeroPropertySelector();
	
	////////////////////////////////////////////////////////////
	//   Edits for enabling excludeFalse()
	//
	private static final PropertySelector NOT_FALSE = new NotFalsePropertySelector();
	
	 static final class NotFalsePropertySelector implements PropertySelector 
	  {
	    public boolean include(Object object, String propertyName, Type type) 
	    {
	      if (object == null)
	      {
	        return false;
	      }
	      else if (object instanceof Boolean)
	      {
	        return ((Boolean) object).booleanValue();
	      }
	      else
	      {
	        return true;
	      }
	    }
	  }

	 /**
	   * Exclude false-valued properties
	   */
	  public Example excludeFalse() {
	    setPropertySelector(NOT_FALSE);
	    return this;
	  }

	 //
	 //  End 
	 /////////////////////////////////////////
	  
	static final class AllPropertySelector implements PropertySelector {
		public boolean include(Object object, String propertyName, Type type) {
			return true;
		}
		
		private Object readResolve() {
			return ALL;
		}
	}

	static final class NotNullPropertySelector implements PropertySelector {
		public boolean include(Object object, String propertyName, Type type) {
			return object!=null;
		}
		
		private Object readResolve() {
			return NOT_NULL;
		}
	}

	static final class NotNullOrZeroPropertySelector implements PropertySelector {
		public boolean include(Object object, String propertyName, Type type) {
			return object!=null && (
				!(object instanceof Number) || ( (Number) object ).longValue()!=0
			);
		}
		
		private Object readResolve() {
			return NOT_NULL_OR_ZERO;
		}
	}

	/**
	 * Set escape character for "like" clause
	 */
	public Example setEscapeCharacter(Character escapeCharacter) {
		this.escapeCharacter = escapeCharacter;
		return this;
	}

	/**
	 * Set the property selector
	 */
	public Example setPropertySelector(PropertySelector selector) {
		this.selector = selector;
		return this;
	}

	/**
	 * Exclude zero-valued properties
	 */
	public Example excludeZeroes() {
		setPropertySelector(NOT_NULL_OR_ZERO);
		return this;
	}

	/**
	 * Don't exclude null or zero-valued properties
	 */
	public Example excludeNone() {
		setPropertySelector(ALL);
		return this;
	}

	/**
	 * Use the "like" operator for all string-valued properties
	 */
	public Example enableLike(MatchMode matchMode) {
		isLikeEnabled = true;
		this.matchMode = matchMode;
		return this;
	}

	/**
	 * Use the "like" operator for all string-valued properties
	 */
	public Example enableLike() {
		return enableLike(MatchMode.EXACT);
	}

	/**
	 * Ignore case for all string-valued properties
	 */
	public Example ignoreCase() {
		isIgnoreCaseEnabled = true;
		return this;
	}

	/**
	 * Exclude a particular named property
	 */
	public Example excludeProperty(String name) {
		excludedProperties.add(name);
		return this;
	}

	/**
	 * Create a new instance, which includes all non-null properties
	 * by default
	 * @param entity
	 * @return a new instance of <tt>Example</tt>
	 */
	public static Example create(Object entity) {
		if (entity==null) throw new NullPointerException("null example");
		return new Example(entity, NOT_NULL);
	}

	protected Example(Object entity, PropertySelector selector) {
		this.entity = entity;
		this.selector = selector;
	}

	public String toString() {
		return "example (" + entity + ')';
	}

	private boolean isPropertyIncluded(Object value, String name, Type type) {
		return !excludedProperties.contains(name) &&
			!type.isAssociationType() &&
			selector.include(value, name, type);
	}

	public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery)
		throws HibernateException {

		StringBuffer buf = new StringBuffer().append('(');
		EntityPersister meta = criteriaQuery.getFactory().getEntityPersister( criteriaQuery.getEntityName(criteria) );
		String[] propertyNames = meta.getPropertyNames();
		Type[] propertyTypes = meta.getPropertyTypes();
		//TODO: get all properties, not just the fetched ones!
		Object[] propertyValues = meta.getPropertyValues( entity, getEntityMode(criteria, criteriaQuery) );
		for (int i=0; i<propertyNames.length; i++) {
			Object propertyValue = propertyValues[i];
			String propertyName = propertyNames[i];

			boolean isPropertyIncluded = i!=meta.getVersionProperty() &&
				isPropertyIncluded( propertyValue, propertyName, propertyTypes[i] );
			if (isPropertyIncluded) {
				if ( propertyTypes[i].isComponentType() ) {
					appendComponentCondition(
						propertyName,
						propertyValue,
						(CompositeType) propertyTypes[i],
						criteria,
						criteriaQuery,
						buf
					);
				}
				else {
					appendPropertyCondition(
						propertyName,
						propertyValue,
						criteria,
						criteriaQuery,
						buf
					);
				}
			}
		}
		if ( buf.length()==1 ) buf.append("1=1"); //yuck!
		return buf.append(')').toString();
	}

	private static final Object[] TYPED_VALUES = new TypedValue[0];

	public TypedValue[] getTypedValues(Criteria criteria, CriteriaQuery criteriaQuery)
	throws HibernateException {

		EntityPersister meta = criteriaQuery.getFactory()
				.getEntityPersister( criteriaQuery.getEntityName(criteria) );
		String[] propertyNames = meta.getPropertyNames();
		Type[] propertyTypes = meta.getPropertyTypes();
		 //TODO: get all properties, not just the fetched ones!
		Object[] values = meta.getPropertyValues( entity, getEntityMode(criteria, criteriaQuery) );
		List list = new ArrayList();
		for (int i=0; i<propertyNames.length; i++) {
			Object value = values[i];
			Type type = propertyTypes[i];
			String name = propertyNames[i];

			boolean isPropertyIncluded = i!=meta.getVersionProperty() &&
				isPropertyIncluded(value, name, type);

			if (isPropertyIncluded) {
				if ( propertyTypes[i].isComponentType() ) {
					addComponentTypedValues(name, value, (CompositeType) type, list, criteria, criteriaQuery);
				}
				else {
					addPropertyTypedValue(value, type, list);
				}
			}
		}
		return (TypedValue[]) list.toArray(TYPED_VALUES);
	}
	
	private EntityMode getEntityMode(Criteria criteria, CriteriaQuery criteriaQuery) {
		EntityPersister meta = criteriaQuery.getFactory()
				.getEntityPersister( criteriaQuery.getEntityName(criteria) );
		EntityMode result = meta.guessEntityMode(entity);
		if (result==null) {
			throw new ClassCastException( entity.getClass().getName() );
		}
		return result;
	}

	protected void addPropertyTypedValue(Object value, Type type, List list) {
		if ( value!=null ) {
			if ( value instanceof String ) {
				String string = (String) value;
				if (isIgnoreCaseEnabled) string = string.toLowerCase();
				if (isLikeEnabled) string = matchMode.toMatchString(string);
				value = string;
			}
			list.add( new TypedValue(type, value, null) );
		}
	}

	protected void addComponentTypedValues(
			String path, 
			Object component, 
			CompositeType type,
			List list, 
			Criteria criteria, 
			CriteriaQuery criteriaQuery)
	throws HibernateException {

		if (component!=null) {
			String[] propertyNames = type.getPropertyNames();
			Type[] subtypes = type.getSubtypes();
			Object[] values = type.getPropertyValues( component, getEntityMode(criteria, criteriaQuery) );
			for (int i=0; i<propertyNames.length; i++) {
				Object value = values[i];
				Type subtype = subtypes[i];
				String subpath = StringHelper.qualify( path, propertyNames[i] );
				if ( isPropertyIncluded(value, subpath, subtype) ) {
					if ( subtype.isComponentType() ) {
						addComponentTypedValues(subpath, value, (CompositeType) subtype, list, criteria, criteriaQuery);
					}
					else {
						addPropertyTypedValue(value, subtype, list);
					}
				}
			}
		}
	}

	protected void appendPropertyCondition(
		String propertyName,
		Object propertyValue,
		Criteria criteria,
		CriteriaQuery cq,
		StringBuffer buf)
	throws HibernateException {
		Criterion crit;
		if ( propertyValue!=null ) {
			boolean isString = propertyValue instanceof String;
			if ( isLikeEnabled && isString ) {
				crit = new LikeExpression(
						propertyName,
						( String ) propertyValue,
						matchMode,
						escapeCharacter,
						isIgnoreCaseEnabled
				);
			}
			else {
				crit = new SimpleExpression( propertyName, propertyValue, "=", isIgnoreCaseEnabled && isString );
			}
		}
		else {
			crit = new NullExpression(propertyName);
		}
		String critCondition = crit.toSqlString(criteria, cq);
		if ( buf.length()>1 && critCondition.trim().length()>0 ) buf.append(" and ");
		buf.append(critCondition);
	}

	protected void appendComponentCondition(
		String path,
		Object component,
		CompositeType type,
		Criteria criteria,
		CriteriaQuery criteriaQuery,
		StringBuffer buf)
	throws HibernateException {

		if (component!=null) {
			String[] propertyNames = type.getPropertyNames();
			Object[] values = type.getPropertyValues( component, getEntityMode(criteria, criteriaQuery) );
			Type[] subtypes = type.getSubtypes();
			for (int i=0; i<propertyNames.length; i++) {
				String subpath = StringHelper.qualify( path, propertyNames[i] );
				Object value = values[i];
				if ( isPropertyIncluded( value, subpath, subtypes[i] ) ) {
					Type subtype = subtypes[i];
					if ( subtype.isComponentType() ) {
						appendComponentCondition(
							subpath,
							value,
							(CompositeType) subtype,
							criteria,
							criteriaQuery,
							buf
						);
					}
					else {
						appendPropertyCondition(
							subpath,
							value,
							criteria,
							criteriaQuery,
							buf
						);
					}
				}
			}
		}
	}
}

And Criterion.java (no changes, just because Example assumes it is there)

/**
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
 * indicated by the @author tags or express copyright attribution
 * statements applied by the authors.  All third-party contributions are
 * distributed under license by Red Hat Middleware LLC.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 *
 */
package org.hibernate.criterion;

import java.io.Serializable;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.engine.TypedValue;

/**
 * An object-oriented representation of a query criterion that may be used 
 * as a restriction in a <tt>Criteria</tt> query.
 * Built-in criterion types are provided by the <tt>Restrictions</tt> factory 
 * class. This interface might be implemented by application classes that 
 * define custom restriction criteria.
 *
 * @see Restrictions
 * @see Criteria
 * @author Gavin King
 */
public interface Criterion extends Serializable 
{

	/**
	 * Render the SQL fragment
	 *
	 * @param criteria The local criteria
	 * @param criteriaQuery The overal criteria query
	 *
	 * @return The generated SQL fragment
	 * @throws org.hibernate.HibernateException Problem during rendering.
	 */
	public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException;
	
	/**
	 * Return typed values for all parameters in the rendered SQL fragment
	 *
	 * @param criteria The local criteria
	 * @param criteriaQuery The overal criteria query
	 *
	 * @return The types values (for binding)
	 * @throws HibernateException Problem determining types.
	 */
	public TypedValue[] getTypedValues(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException;

}

18. Do mvn jetty:run

19. Pull up Persons list (initially empty) http://localhost:8080/crudsqbe/persons

20. Click on “Create New” or “Add Some” http://localhost:8080/crudsqbe/person/edit

21. The edit form can be used create a new record or search using the QBE.

22. The links rendering the person names will pull up the particular record for edit/save.

22. Search field above the persons list will drive Lucene indexed text search.

23. Code (crudsqbe.zip) may be downloaded from Google Docs. File –> Download (Ctrl+S). If you have JDK, Maven setup as Steps 1-2 and database setup as per step 8, just do mvn jetty:run after unzipping to, say, c:/projects/crudsqbe.

24. Eclipse. These are just brief high-level instructions. From eclipse.org, download Eclipse IDE for Java EE Developers, 212 MB

Download eclipse-jee-indigo-SR2-win32-x86_64.zip

Use something like 7-zip to uncompress it into c:/eclipse, create shortcut to C:\eclipse\eclipse.exe

Change directory to projects/crudsqbe FIRST and THEN do the following:

mvn eclipse:clean
mvn eclipse:eclipse -DdownloadSources=true

Now open eclipse using shortcut, make c:/projects your workspace, add crudsqbe as a java project. Should be good to go.

For even more agility, you may consider eclipse add-ons:

1. Run Jetty Run Plugin for eclipse. After installing, right click on crudsqbe project to simply run Jetty. Enjoy the live class reloading.
Everytime you touch a class or template, the server will reboot, with the code changes immediately verifiable.
2. TML Code Completion. Auto completes your template code.
3. SQL Explorer. View what is happening in your database.

25. Among several things needed to scale this application would be to create RESTful service using Hibernate, specifically, Data Access Object (DAO), to do all the CRUDS-QBE. This would make the API reusable across all pages, including the form interface, which is typically an Admin interface.