Grid datasource for hibernate queries. Abstract methods are used for creating a Query Object, so it is quite flexible; there is no requirement to use a DAO, HQL, or Criteria. It is compatible with any org.hibernate.Query object.
It was originally designed for use with HQL. It probably isn't as optimal for DaO as something specifically designed for that (unless your DaO object returns a org.hibernate.Query object).
To use it, extend it and implement createQuery(...) and createCountQuery(). Optionally use generateOrderBy(...) inside your createQuery(...) method to generate an ORDER BY clause supporting any weird property mapping.
It is intended to be extended for each page/component where it is used, and a new instance is created for each render. Theoretically, you could just call initAvailableRows() instead of creating a new one, or just remove the "if (this.rowCount == -1)" condition in getAvailableRows().
1 import java.util.ArrayList;
2 import java.util.List;
3 import java.util.Map;
4
5 import org.apache.log4j.Category;
6 import org.apache.tapestry.grid.ColumnSort;
7 import org.apache.tapestry.grid.GridDataSource;
8 import org.apache.tapestry.grid.SortConstraint;
9 import org.hibernate.Query;
10
11 /**
12 * Grid datasource for hibernate queries. Abstract methods are used for creating
13 * a Query Object, so it is quite flexible; there is no requirement to use a
14 * DAO, HQL, or Criteria. It is compatible with any org.hibernate.Query object.<br>
15 * <br>
16 * It was originally designed for use with HQL. It probably isn't as optimal for
17 * DaO as something specifically designed for that (unless your DaO object
18 * returns a org.hibernate.Query object). <br>
19 * <br>
20 * To use it, extend it and implement createQuery(...) and createCountQuery().
21 * Optionally use generateOrderBy(...) inside your createQuery(...) method to
22 * generate an ORDER BY clause supporting any weird property mapping. <br>
23 * <br>
24 * It is intended to be extended for each page/component where it is used, and a
25 * new instance is created for each render. Theoretically, you could just call
26 * initAvailableRows() instead of creating a new one, or just remove the
27 * "if (this.rowCount == -1)" condition in getAvailableRows().<br>
28 * <br>
29 * Tested with Tapestry 5.0.11, JDK1.6
30 *
31 * @author pmaloney, tli
32 * @version $Id: HibernateGridDataSource.java,v 1.6 2008/06/10 17:37:07 pmaloney
33 * Exp $
34 */
35 public abstract class HibernateGridDataSource implements GridDataSource {
36 private static Category log = Category.getInstance( HibernateGridDataSource.class );
37
38 private int rowCount = -1;
39 private int startIndex = -1;
40 private List<?> pageRowList;
41
42 private Class<?> rowType;
43
44
45 public HibernateGridDataSource(Class<?> rowType) {
46 this.rowType = rowType;
47 this.rowCount = -1;
48 }
49
50 /**
51 * @return a Query for fetching rows for a page. There should be no OFFSET
52 * or LIMIT clause, since setFirstResult(...) and setFetchSize(...)
53 * will be called by HibernateGridDataSource. Call
54 * HibernateGridDataSource.generateOrderBy(List,Map) to generate an
55 * order by clause.
56 */
57 public abstract Query createQuery(List<SortConstraint> sortConstraints);
58
59 /**
60 * @return a Query that would be suitable for finding a count. Its result
61 * should be one java.lang.Number (Such as Integer, or Long) (one
62 * row, one cell).
63 */
64 public abstract Query createCountQuery();
65
66 /**
67 * @param sortConstraints The List of SortConstraint objects to sort with
68 * @param propertyToHql maps property names from the grid bean model to hql
69 * expressions. If the map is not null and the property name is in
70 * the map, the expression is used, else the property name is used
71 * normally.
72 */
73 public static String generateOrderBy(List<SortConstraint> sortConstraints, Map<String,String> propertyToHql) {
74 StringBuilder hql = new StringBuilder();
75 ArrayList<String> orderByExpressions = new ArrayList<String>();
76
77 for( SortConstraint sc : sortConstraints ) {
78 if( sc.getColumnSort() == ColumnSort.UNSORTED )
79 continue;
80
81 String name = sc.getPropertyModel().getPropertyName();
82
83 if( propertyToHql != null ) {
84 String hqlEx = propertyToHql.get(name);
85 if( hqlEx != null ) {
86 name = hqlEx;
87 }
88 }
89
90 String ex;
91 if( sc.getColumnSort() == ColumnSort.DESCENDING )
92 ex = name + " desc";
93 else
94 ex = name;
95
96 orderByExpressions.add( ex );
97 }
98
99 int i=0;
100 for( String ex : orderByExpressions ) {
101 if( i == 0 )
102 hql.append(" ORDER BY ");
103 else
104 hql.append(", ");
105 hql.append( ex );
106 i++;
107 }
108
109 if( log.isDebugEnabled() )
110 log.debug( "In generateOrderBy(...), hql = " + hql );
111 return hql.toString();
112 }
113
114 /**
115 * Calls generateOrderBy(sortConstraints, null)
116 *
117 * @See generateOrderBy(List<SortConstraint>, Map<String,String>)
118 */
119 public static String generateOrderBy(List<SortConstraint> sortConstraints) {
120 return generateOrderBy(sortConstraints, null);
121 }
122
123 public void initAvailableRows(){
124 Query q = createCountQuery();
125 Number count = (Number) q.uniqueResult();
126
127 if( log.isDebugEnabled() )
128 log.debug("count = " + count);
129
130 this.rowCount = count.intValue();
131 }
132
133 /**
134 * Returns the number of rows available in the data source.
135 */
136 public int getAvailableRows() {
137 if (this.rowCount == -1)
138 initAvailableRows();
139 return rowCount;
140 }
141
142 /**
143 * Invoked to allow the source to prepare to present values. This gives the
144 * source a chance to pre-fetch data (when appropriate) and informs the
145 * source of the desired sort order.
146 *
147 * @param startIndex
148 * the starting index to be retrieved
149 * @param endIndex
150 * the ending index to be retrieved
151 * @param sortConstraints
152 * the property model that defines what data will be used for
153 * sorting, or null if no sorting is required (in which case,
154 * whatever natural order is provided by the underlying data
155 * source will be used)
156 */
157 public void prepare(int startIndex, int endIndex, List<SortConstraint> sortConstraints) {
158 if( log.isDebugEnabled() )
159 log.debug(
160 "Processing prepare(...) startIndex = " + startIndex + ", endIndex = " + endIndex );
161 int maxResults = endIndex - startIndex + 1;
162
163 Query q = createQuery(sortConstraints);
164 q.setFirstResult(startIndex);
165 q.setFetchSize(maxResults);
166 q.setMaxResults(maxResults);
167 List<?> rows = q.list();
168 pageRowList = rows;
169 this.startIndex = startIndex;
170 }
171
172 /**
173 * Returns the row value as rows on evt the provided index. This method will
174 * be invoked in sequential order.
175 *
176 * @param databaseIndex index based on all rows (superset of what was
177 * fetched in prepare), so subtract startIndex to get the index in
178 * the result set.
179 */
180 public Object getRowValue(int databaseIndex) {
181 int collectionIndex = databaseIndex - startIndex;
182 Object entityObject = null;
183
184 if (pageRowList.size() > collectionIndex)
185 entityObject = pageRowList.get(collectionIndex);
186 else {
187 // This else is a work around for getRowValue(...) with incorrect index.
188 // The theory is that the grid is caching the number of rows so if the row count
189 // on the previous request was larger, then index can be out of bounds.
190 try {
191 return getRowType().newInstance();
192 } catch (Exception e) {
193 throw new RuntimeException("index was invalid, and failed to create a dummy row.", e);
194 }
195 }
196 return entityObject;
197 }
198
199 /**
200 * Returns the type of value in the rows, or null if not known.
201 *
202 * @return the row type, or null
203 */
204 public Class<?> getRowType() {
205 return this.rowType;
206 }
207
208 }