View Javadoc

1   /*
2    * EL4J, the Extension Library for the J2EE, adds incremental enhancements to
3    * the spring framework, http://el4j.sf.net
4    * Copyright (C) 2010 by ELCA Informatique SA, Av. de la Harpe 22-24,
5    * 1000 Lausanne, Switzerland, http://www.elca.ch
6    *
7    * EL4J is published under the GNU Lesser General Public License (LGPL)
8    * Version 2.1. See http://www.gnu.org/licenses/
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13   * GNU Lesser General Public License for more details.
14   *
15   * For alternative licensing, please contact info@elca.ch
16   */
17  package ch.elca.el4j.services.persistence.jpa.criteria;
18  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Collection;
22  import java.util.List;
23  
24  import javax.persistence.EntityManager;
25  import javax.persistence.NonUniqueResultException;
26  import javax.persistence.Query;
27  
28  import org.apache.commons.collections.CollectionUtils;
29  import org.apache.commons.lang.ArrayUtils;
30  import org.apache.commons.lang.StringUtils;
31  
32  /**
33   * SQL Query Builder. 
34   * 
35   * TODO: adapt the following example such that it produces valid JPQL. (no ON clause, but AS instead)
36   * <p>
37   * Example query definition:
38   * <pre>
39   *      QueryBuilder builder = QueryBuilder.select("dep.NAME", "emp.name");
40   *      QueryBuilder subQueryBuilder = QueryBuilder.select("1");
41   *      subQueryBuilder.from("Dual ").startAnd().ifNotNull("1 <> {p}", 2).end().endBuilder();
42   *
43   *      builder.from("DEPARTMENT dep")
44   *          .innerJoin("EMPLOYEE emp", "dep.ID = emp.DEPARTMENT_ID")
45   *          .innerJoin("UNIVERSITY unv", "unv.ID = dep.UNV_ID")
46   *      .startOr()
47   *          .ifNotNull("emp.position = {p}", "pos")
48   *          .startAnd()
49   *              .ifNotNull("emp.SALARY > {p}", 200)
50   *              .ifNotNull("emp.SALARY < {p}", 300)
51   *          .end()
52   *          .exist(subQueryBuilder)
53   *          .ifNotNull("dep.ID in {p}", depIds)
54   *      .end()
55   *      .orderBy(SortOrder.ASC, "dep.ID").endBuilder();
56   * </pre>
57   * The above query is equivalent to the following SQL:
58   * <pre>
59   *     SELECT dep.NAME, emp.NAME
60   *     FROM DEPARTMENT dep
61   *         JOIN emp ON (dep.ID = emp.DEPARTMENT_ID)
62   *         JOIN UNIVERSITY unv ON (unv.ID = dep.UNV_ID)
63   *     WHERE emp.POSITION = {p}
64   *         OR (emp.SALARY > {p} AND emp.SALARY < {p})" 
65   *         OR EXISTS (SELECT 1 FROM DUAL WHERE 1 <> {p})
66   *     ORDER BY DEP.ID ASC";
67   * </pre>
68   *
69   * @svnLink $Revision: 4253 $;$Date: 2010-12-21 11:08:04 +0100 (Di, 21. Dez 2010) $;$Author: swismer $;$URL: https://el4j.svn.sourceforge.net/svnroot/el4j/branches/el4j_3_1/el4j/framework/modules/hibernate/src/main/java/ch/elca/el4j/services/persistence/jpa/criteria/QueryBuilder.java $
70   *
71   * @author Simon Stelling (SST)
72   * @author Huy Hung Nguyen (HUN)
73   */
74  public final class QueryBuilder implements Expression, SelectQuery, CountQuery {
75  
76  	private static final String PARAM_KEY = "{p}";
77  	private static final String PARAM = "param";
78  	
79  	/** SQL AND. */
80  	private static final String AND = " AND ";
81  	
82  	/** SQL OR. */
83  	private static final String OR = " OR ";
84  	
85  	/** SQL COMMA. */
86  	private static final String COMMA = " , ";
87  
88  	/** SQL INNER JOIN. */
89  	private static final String INNER_JOIN = "join";
90  	
91  	/** SQL LEFT JOIN. */
92  	private static final String LEFT_JOIN = "left outer join";
93  	
94  	/** SQL RIGHT JOIN. */
95  	private static final String RIGHT_JOIN = "right outer join";
96  	
97  	/** SQL TRUE. */
98  	private static final String DB_TRUE_VALUE = "T";
99  	
100 	/** SQL FALSE. */
101 	private static final String DB_FALSE_VALUE = "F";
102 
103 	/**
104 	 * TODO: where is this used?
105 	 */
106 	private String selectQuery;
107 	
108 	/**
109 	 * FROM elements.
110 	 */
111 	private List<String> froms = new ArrayList<String>();
112 	
113 	/**
114 	 * ORDER BY restrictions.
115 	 */
116 	private List<String> orderBy = new ArrayList<String>();
117 	
118 	/**
119 	 * JOIN elements.
120 	 */
121 	private List<String> joins = new ArrayList<String>();
122 	
123 	/**
124 	 * TODO: What's this?
125 	 */
126 	private List<QueryBuilder> subUnionQueryBuilders = new ArrayList<QueryBuilder>();
127 	
128 	/**
129 	 * WHERE Predicate as String. 
130 	 */
131 	private String where;
132 	
133 	/**
134 	 * Is this query completed and can therefore be used as an inner query?
135 	 */
136 	private boolean completed = false;
137 
138 	/**
139 	 * Parameters of the SQL query, except those of the SELECT clause.
140 	 */
141 	private List<Object> bodyParameters = new ArrayList<Object>();
142 	
143 	/**
144 	 * Parameters of the SELECT clause. They are omitted if a count query is executed.
145 	 */
146 	private List<Object> selectParameters = new ArrayList<Object>();
147 
148 	/**
149 	 * TODO: What's this?
150 	 */
151 	private Query query;
152 
153 	/** hidden constructor. 
154 	 * @param select select */
155 	private QueryBuilder(String... select) {
156 		this.selectQuery = StringUtils.join(select, COMMA);
157 	}
158 
159 	/** hidden constructor. 
160 	 * @param query query */
161 	private QueryBuilder(QueryBuilder... query) {
162 
163 	}
164 
165 	/**
166 	 * get SelectParameters.
167 	 * 
168 	 * @return the selectParameters
169 	 */
170 	public List<Object> getSelectParameters() {
171 		return selectParameters;
172 	}
173 	
174 	/**
175 	 * Creates a new Query with the given SELECT clause.
176 	 * @param select select clause
177 	 * @return a new query
178 	 */
179 	public static QueryBuilder select(String... select) {
180 		return new QueryBuilder(select);
181 	}
182 
183 	/**
184 	 * TODO: what's this?
185 	 */
186 	public QueryBuilder addSelectParameter(Object... parameters) {
187 		for (Object parameter : parameters) {
188 			if (isParameterAccepted(parameter)) {
189 				selectParameters.add(parameter);
190 			} else {
191 				throw new RuntimeException("Parameters for select statement must be not null or not empty.");
192 			}
193 		}
194 		return this;
195 	}
196 
197 	/**
198 	 * Specifies on which classes the query is based.
199 	 * @param entityClasses entities' classes
200 	 * @return this
201 	 */
202 	public QueryBuilder from(Class<?>... entityClasses) {
203 		String[] classNames = new String[entityClasses.length];
204 		for (int i = 0; i < entityClasses.length; i++) {
205 			classNames[i] = entityClasses[i].getSimpleName();
206 		}
207 
208 		return from(classNames);
209 	}
210 
211 	/**
212 	 * Specifies on which tables the query is based.
213 	 * @param from tables
214 	 * @return this
215 	 */
216 	public QueryBuilder from(String... from) {
217 		froms.addAll(Arrays.asList(from));
218 		return this;
219 	}
220 
221 	/**
222 	 * same as {@link QueryBuilder#from(String...)} except that
223 	 * from clause is added iff condition evaluates to true.
224 	 * @param fromExp from string to add
225 	 * @param cond predicate
226 	 * @return this
227 	 */
228 	public QueryBuilder fromIf(String fromExp, boolean cond) {
229 		if (cond) {
230 			return from(fromExp);
231 		}
232 		return this;
233 	}
234 
235 	/**
236 	 * TODO: what's this?
237 	 */
238 	private QueryBuilder join(String join, String type, String on, Object... params) {
239 		if (ArrayUtils.isEmpty(params)) {
240 			doJoin(join, type, on);
241 		} else {
242 			// have some parameters, join only if all parameters are accepted.
243 			boolean allParamsAccepted = true;
244 			for (Object param : params) {
245 				if (!isParameterAccepted(param)) {
246 					allParamsAccepted = false;
247 					break;
248 				}
249 			}
250 			if (allParamsAccepted) {
251 				doJoin(join, type, on);
252 				bodyParameters.addAll(Arrays.asList(params));
253 			}
254 		}
255 
256 		return this;
257 	}
258 
259 	/**
260 	 * Only used for native sql.
261 	 * 
262 	 * @param join
263 	 * @param on
264 	 * @param params
265 	 * @return
266 	 */
267 	public QueryBuilder innerJoin(String join, String on, Object... params) {
268 		return join(join, INNER_JOIN, on, params);
269 	}
270 
271 	/**
272 	 * Only used for native sql.
273 	 * 
274 	 * @param join
275 	 * @param on
276 	 * @param params
277 	 * @return
278 	 */
279 	public QueryBuilder innerJoin(String join, String on) {
280 		return innerJoin(join, on, new Object[] {});
281 	}
282 
283 	/**
284 	 * Only used for native sql.
285 	 * 
286 	 * @param join
287 	 * @param on
288 	 * @param params
289 	 * @return
290 	 */
291 	public QueryBuilder leftJoin(String join, String on, Object... params) {
292 		return join(join, LEFT_JOIN, on, params);
293 	}
294 
295 	public QueryBuilder leftJoin(String join, String on) {
296 		return leftJoin(join, on, new Object[] {});
297 	}
298 
299 	public QueryBuilder union(QueryBuilder builder) {
300 		if (builder != null) {
301 			subUnionQueryBuilders.add(builder);
302 		}
303 		return this;
304 	}
305 
306 	/**
307 	 * Only used for native sql.
308 	 * 
309 	 * @param join
310 	 * @param on
311 	 * @param params
312 	 * @return
313 	 */
314 	public QueryBuilder rightJoin(String join, String on, Object... params) {
315 		return join(join, RIGHT_JOIN, on, params);
316 	}
317 
318 	public QueryBuilder rightJoin(String join, String on) {
319 		return rightJoin(join, on, null, new Object[] {});
320 	}
321 
322 	/**
323 	 * TODO: what are the parameters here?
324 	 * adds "type join ON ( on )" to the joins field.
325 	 * @param join what?
326 	 * @param type what?
327 	 * @param on what?
328 	 */
329 	private void doJoin(String join, String type, String on) {
330 		String joinStr = " " + type + " " + join;
331 		if (StringUtils.isNotBlank(on)) {
332 			joinStr += " on (" + on + ")";
333 		}
334 		joins.add(joinStr);
335 	}
336 
337 	/**
338 	 * like {@link QueryBuilder#joinIf(String, String, boolean, Object...)} but join is
339 	 * performed iff cond evaluates to true.
340 	 * @param join join
341 	 * @param on on clause
342 	 * @param cond predicate
343 	 * @param params params
344 	 * @return this
345 	 */
346 	public QueryBuilder joinIf(String join, String on, boolean cond, Object... params) {
347 		if (cond) {
348 			return join(join, INNER_JOIN, on, params);
349 		}
350 		return this;
351 	}
352 
353 	/**
354 	 * Dont need 'on', used for JPQL.
355 	 * 
356 	 * @param join
357 	 * @param cond
358 	 * @return
359 	 */
360 	public QueryBuilder join(String join) {
361 		doJoin(join, INNER_JOIN, null);
362 		return this;
363 	}
364 
365 	/**
366 	 * Dont need 'on', used for JPQL.
367 	 * 
368 	 * @param join
369 	 * @param cond
370 	 * @return
371 	 */
372 	public QueryBuilder joinIf(String join, boolean cond) {
373 		if (cond) {
374 			join(join);
375 		}
376 		return this;
377 	}
378 
379 	/**
380 	 * marks the beginning of a list of predicates to be ANDed.
381 	 * @return condition object
382 	 */
383 	public ConditionList<QueryBuilder> startAnd() {
384 		return new ConditionList<QueryBuilder>(QueryBuilder.this, AND);
385 	}
386 	
387 	/**
388 	 * marks the beginning of a list of predicates to be ORed.
389 	 * @return condition object
390 	 */
391 	public ConditionList<QueryBuilder> startOr() {
392 		return new ConditionList<QueryBuilder>(QueryBuilder.this, OR);
393 	}
394 
395 	/**
396 	 * Mark this query as completed.
397 	 * 
398 	 * TODO: what is the role of completed vs. non-completed queries?
399 	 * @return this
400 	 */
401 	public QueryBuilder endBuilder() {
402 		completed = true;
403 		return this;
404 	}
405 
406 	/**
407 	 * appends the given column to the ordering restrictions
408 	 * iff not already ordered by the given column.
409 	 * 
410 	 * @param sortOrder ASC or DESC
411 	 * @param orderBy the column to order by
412 	 * @return this
413 	 */
414 	public QueryBuilder orderBy(SortOrder sortOrder, String orderBy) {
415 		if (StringUtils.isNotBlank(orderBy) && !isAlreadyOrderBy(orderBy)) {
416 			this.orderBy.add(orderBy + " " + sortOrder);
417 		}
418 		return this;
419 	}
420 
421 	/**
422 	 * is there already an ordering restriction for the given
423 	 * column?
424 	 * 
425 	 * @param column column
426 	 * @return answer
427 	 */
428 	private boolean isAlreadyOrderBy(final String column) {
429 		for (String order : orderBy) {
430 			if (StringUtils.startsWithIgnoreCase(order, column + " ")) {
431 				return true;
432 			}
433 		}
434 		return false;
435 	}
436 
437 	/**
438 	 * Appends given column to the ordering restrictions iff
439 	 * cond evaluates to true.
440 	 * 
441 	 * @param sortOrder ASC or DESC
442 	 * @param orderBy column
443 	 * @param cond predicate
444 	 * @return this
445 	 */
446 	public QueryBuilder orderByIf(SortOrder sortOrder, String orderBy, boolean cond) {
447 		if (cond) {
448 			return orderBy(sortOrder, orderBy);
449 		}
450 
451 		return this;
452 	}
453 
454 	/**
455 	 * Condition of QueryBuilder.
456 	 * 
457 	 * @author LLT
458 	 * @param <T>
459 	 */
460 	public class ConditionList<T extends Expression> implements Expression {
461 		
462 		/**
463 		 * the QueryBuilder or ConditionList which started this condition list.
464 		 * is returned when .end() is called.
465 		 */
466 		protected T parent;
467 		
468 		/**
469 		 * AND or OR.
470 		 */
471 		private String operation;
472 		
473 		/**
474 		 * The conditions included in this ConditionList.
475 		 */
476 		private List<String> conds = new ArrayList<String>();
477 
478 		/**
479 		 * Sole constructor.
480 		 * @param parent is returned when .end() is called.
481 		 * @param operation AND or OR
482 		 */
483 		public ConditionList(T parent, String operation) {
484 			this.parent = parent;
485 			this.operation = operation;
486 		}
487 
488 		/**
489 		 * adds condition to this condition list.
490 		 * @param predicate condition
491 		 * @return this
492 		 */
493 		public ConditionList<T> ifCond(String predicate) {
494 			conds.add(predicate);
495 			return this;
496 		}
497 
498 		/**
499 		 * adds queryPredicate iff cond evaluates to true.
500 		 * @param queryPredicate the query predicate
501 		 * @param cond condition
502 		 * @return this
503 		 */
504 		public ConditionList<T> ifCond(String queryPredicate, boolean cond) {
505 			if (cond) {
506 				return ifCond(queryPredicate);
507 			}
508 			return this;
509 		}
510 
511 		public ConditionList<T> ifNotNull(String condition, Object parameter) {
512 			if (isParameterAccepted(parameter)) {
513 				getBodyParameters().add(parameter);
514 				conds.add(condition);
515 			}
516 			return this;
517 		}
518 
519 		/**
520 		 * same as {@link ConditionList#ifNotNull(String, Object)} iff cond evaluates to true.
521 		 * @param queryPredicate query's predicate
522 		 * @param parameter parameter
523 		 * @param cond cond
524 		 * @return this
525 		 */
526 		public ConditionList<T> ifNotNull(String queryPredicate, Object parameter, boolean cond) {
527 			if (cond) {
528 				return ifNotNull(queryPredicate, parameter);
529 			}
530 			return this;
531 		}
532 
533 		/**
534 		 * same as {@link ConditionList#exist(QueryBuilder)} iff cond evaluates to true.
535 		 * @param subQueryBuilder subquery
536 		 * @param cond cond
537 		 * @return this
538 		 */
539 		public ConditionList<T> existIf(QueryBuilder subQueryBuilder, boolean cond) {
540 			if (cond) {
541 				return exist(subQueryBuilder);
542 			}
543 			return this;
544 		}
545 
546 		/**
547 		 * @param subQueryBuilder subquery
548 		 * @param obj obj which is tested for nullness.
549 		 * @return existIf(subQueryBuilder, obj != null)
550 		 */
551 		public ConditionList<T> existIfNotNull(QueryBuilder subQueryBuilder, Object obj) {
552 			return existIf(subQueryBuilder, obj != null);
553 		}
554 
555 		/**
556 		 * adds an EXISTS ( subquery ) to this condition.
557 		 * @param subQueryBuilder subquery
558 		 * @return this
559 		 */
560 		public ConditionList<T> exist(QueryBuilder subQueryBuilder) {
561 			if (!subQueryBuilder.completed) {
562 				throw new RuntimeException("Sub query must be completed first before being set in");
563 			}
564 			bodyParameters.addAll(subQueryBuilder.getSelectParameters());
565 			bodyParameters.addAll(subQueryBuilder.getBodyParameters());
566 			conds.add(" exists (" + subQueryBuilder.getQuerySelectStr() + ")");
567 
568 			return this;
569 		}
570 
571 		/**
572 		 * marks the end of an ANDed or ORed list of predicates.
573 		 * @return the QueryBuilder which started this condition
574 		 */
575 		public T end() {
576 			if (!conds.isEmpty()) {
577 				parent.append(StringUtils.join(conds, operation));
578 			}
579 			return parent;
580 		}
581 
582 		/**
583 		 * Marks the beginning of a new ANDed ConditionList.
584 		 * @return the nested ConditionList
585 		 */
586 		public ConditionList<ConditionList<T>> startAnd() {
587 			return new ConditionList<ConditionList<T>>(this, AND);
588 		}
589 		
590 		/**
591 		 * Marks the beginning of a new ORed ConditionList.
592 		 * @return the nested ConditionList
593 		 */
594 		public ConditionList<ConditionList<T>> startOr() {
595 			return new ConditionList<ConditionList<T>>(this, OR);
596 		}
597 
598 		/**
599 		 * {@inheritDoc}
600 		 */
601 		@Override
602 		public void append(String query) {
603 			if (!query.isEmpty()) {
604 				conds.add("(" + query + ")");
605 			}
606 		}
607 	}
608 
609 	/**
610 	 * TODO: what's this?
611 	 * @param parameter
612 	 * @return
613 	 */
614 	@SuppressWarnings("unchecked")
615 	private boolean isParameterAccepted(Object parameter) {
616 		if (parameter == null) {
617 			return false;
618 		}
619 		boolean paramAccepted = true;
620 		if (parameter instanceof String) {
621 			paramAccepted = (StringUtils.isNotBlank((String) parameter));
622 		} else if (parameter instanceof Collection) {
623 			paramAccepted = (CollectionUtils.isNotEmpty((Collection) parameter));
624 		}
625 		return paramAccepted;
626 	}
627 
628 	/**
629 	 * creates select query with the given EntityManager and stores the prepared query
630 	 * which is then executed through the getResultList() methods.
631 	 * @param entityManager em to use
632 	 * @return this
633 	 */
634 	public SelectQuery applySelect(EntityManager entityManager) {
635 		createJpaQuery(getQuerySelectStr(), entityManager, false, null, false);
636 		return this;
637 	}
638 
639 	/**
640 	 * creates select query with the given EntityManager and stores the prepared query
641 	 * which is then executed through the getResultList() methods.
642 	 * @param entityManager em to use
643 	 * @param resultClass class of result
644 	 * @return this
645 	 */
646 	public SelectQuery applySelect(EntityManager entityManager, Class<?> resultClass) {
647 		createJpaQuery(getQuerySelectStr(), entityManager, false, resultClass, false);
648 		return this;
649 	}
650 
651 	/**
652 	 * creates count query with the given EntityManager and stores the prepared query
653 	 * which is then executed through the getCount() method.
654 	 * @param entityManager em to use
655 	 * @return this
656 	 */
657 	public CountQuery applyCount(EntityManager entityManager) {
658 		createJpaQuery(getQueryCountStr(), entityManager, false, null, true);
659 		
660 		return this;
661 	}
662 
663 	// TODO: do we need these? 
664 //	public QuerySelect applyNativeSelect(EntityManager entityManager) {
665 //		createJpaQuery(getQuerySelectStr(), entityManager, true, null);
666 //		return this;
667 //	}
668 //
669 //	public QuerySelect applyNativeSelect(EntityManager entityManager, Class<?> resultClass) {
670 //		createJpaQuery(getQuerySelectStr(), entityManager, true, resultClass);
671 //		return this;
672 //	}
673 //
674 //	public QueryCount applyNativeCount(EntityManager entityManager) {
675 //		createJpaQuery(getQueryCountStr(), entityManager, true, null);
676 //		return this;
677 //	}
678 
679 //	public <T extends DefaultSearchCriteria, U extends AbstractEntity> SearchResult<T, U> getSearchResult(
680 //		Class<U> resultClass, EntityManager entityManager, T criteria) {
681 //
682 //		SearchResult<T, U> searchResult = new SearchResult<T, U>(criteria);
683 //		int total = applyCount(entityManager).getCount();
684 //		searchResult.setTotal(total);
685 //		if (total > 0) {
686 //			List<U> items = applySelect(entityManager).getResultList(resultClass, criteria.getFirstRowIdx(),
687 //				criteria.getNumberOfRows());
688 //			searchResult.getItems().addAll(items);
689 //		}
690 //
691 //		return searchResult;
692 //	}
693 
694 	
695 	private void createJpaQuery(String queryString, EntityManager entityManager, boolean nativeQuery,
696 		Class<?> resultClass, boolean isCountQuery) {
697 		initQuery(queryString, entityManager, nativeQuery, resultClass);
698 		applyParameters(getAllParameters(queryString, isCountQuery), nativeQuery);
699 	}
700 
701 	/**
702 	 * initializes the 'query' field.
703 	 * @param queryString query string
704 	 * @param entityManager em
705 	 * @param nativeQuery whether to use the em.createNativeQuery or em.createQuery
706 	 * @param resultClass class of the result
707 	 */
708 	private void initQuery(String queryString, EntityManager entityManager, boolean nativeQuery, Class<?> resultClass) {
709 		String queryStr = positionizeParameters(queryString);
710 		if (nativeQuery) {
711 			if (resultClass != null) {
712 				query = entityManager.createNativeQuery(queryStr, resultClass);
713 			} else {
714 				query = entityManager.createNativeQuery(queryStr);
715 			}
716 		} else {
717 			query = entityManager.createQuery(queryStr);
718 		}
719 	}
720 
721 	private List<Object> getAllParameters(String queryString, boolean isCountQuery) {
722 		// do not apply select param in count query
723 		List<Object> allParameters = new ArrayList<Object>();
724 		if (isCountQuery) {
725 			allParameters.addAll(selectParameters);
726 		}
727 		for (QueryBuilder unionQuery : this.subUnionQueryBuilders) {
728 			allParameters.addAll(unionQuery.getSelectParameters());
729 			allParameters.addAll(unionQuery.getBodyParameters());
730 		}
731 		allParameters.addAll(bodyParameters);
732 		return allParameters;
733 	}
734 
735 	private void applyParameters(List<Object> allParameters, boolean nativeQuery) {
736 		for (int i = 0; i < allParameters.size(); i++) {
737 			Object param = null;
738 			Object parameter = allParameters.get(i);
739 			if (nativeQuery && parameter instanceof Boolean) {
740 				param = ((Boolean) parameter).booleanValue() ? DB_TRUE_VALUE : DB_FALSE_VALUE;
741 			} else {
742 				param = parameter;
743 			}
744 			query.setParameter(PARAM + i, param);
745 		}
746 	}
747 
748 	private String positionizeParameters(String queryString) {
749 		if (StringUtils.isBlank(queryString)) {
750 			return queryString;
751 		}
752 		String result = queryString;
753 		int paramCount = 0;
754 		int curIdx = result.indexOf(PARAM_KEY);
755 		while (curIdx >= 0) {
756 			result = StringUtils.replaceOnce(result, PARAM_KEY, ":" + PARAM + String.valueOf(paramCount));
757 			paramCount++;
758 			curIdx = result.indexOf(PARAM_KEY, curIdx + 1);
759 		}
760 
761 		return result;
762 	}
763 
764 	/**
765 	 * Set parameters of the query.
766 	 * 
767 	 * @param parameters
768 	 *            the parameters to set
769 	 */
770 	public void setParameters(List<Object> parameters) {
771 		this.bodyParameters = parameters;
772 	}
773 
774 	/**
775 	 * gets at most maxNumOfRows results, first one being firstRow.
776 	 * 
777 	 * @param <T> entity type
778 	 * @param clazz entity class
779 	 * @param firstRow first row to receive
780 	 * @param maxNumOfRows max number of rows to receive
781 	 * @return retrieved entities
782 	 */
783 	@SuppressWarnings("unchecked")
784 	public <T> List<T> getResultList(Class<T> clazz, int firstRow, int maxNumOfRows) {
785 		query.setFirstResult(firstRow);
786 		query.setMaxResults(maxNumOfRows);
787 		return (List<T>) query.getResultList();
788 	}
789 
790 	/**
791 	 * @see QueryBuilder#getResultList(Class, int, int).
792 	 * @param firstRow first row
793 	 * @param maxNumOfRows maxNumOfRows
794 	 * @return list of results.
795 	 */
796 	@Override
797 	public List<?> getResultList(int firstRow, int maxNumOfRows) {
798 		query.setFirstResult(firstRow);
799 		query.setMaxResults(maxNumOfRows);
800 		return query.getResultList();
801 	}
802 
803 	/**
804 	 * returns all entities matching the query.
805 	 * @param <T> entity type
806 	 * @param clazz entity class
807 	 * @return results
808 	 */
809 	@Override
810 	@SuppressWarnings("unchecked")
811 	public <T> List<T> getResultList(Class<T> clazz) {
812 		return (List<T>) query.getResultList();
813 	}
814 
815 	/**
816 	 * @return query.getResultList()
817 	 */
818 	public List<?> getRawResultList() {
819 		return query.getResultList();
820 	}
821 
822 	/**
823 	 * @param <T> entity type
824 	 * @param clazz entity class
825 	 * @return the unique entity matching the query
826 	 */
827 	@Override
828 	public <T> T getSingleResult(Class<T> clazz) {
829 		List<T> resultList = getResultList(clazz);
830 		if (CollectionUtils.isEmpty(resultList)) {
831 			return null;
832 		}
833 
834 		if (resultList.size() > 1) {
835 			throw new NonUniqueResultException("Result for query '" + query + "' must contain exactly one item");
836 		}
837 
838 		return resultList.get(0);
839 	}
840 
841 	/**
842 	 * {@inheritDoc}
843 	 */
844 	@Override
845 	public int getCount() {
846 		Number count = (Number) query.getSingleResult();
847 		return count.intValue();
848 	}
849 
850 	/**
851 	 * TODO: this seems wrong. what's this for?
852 	 */
853 	@Override
854 	public void append(String query) {
855 		where = query;
856 	}
857 
858 	/**
859 	 * @return the select query as string.
860 	 */
861 	public String toString() {
862 		return getQuerySelectStr();
863 	}
864 
865 	/**
866 	 * @return the count query as string.
867 	 */
868 	private String getQueryCountStr() {
869 		return getQueryStr(true);
870 	}
871 
872 	/**
873 	 * @return the select query as string.
874 	 */
875 	private String getQuerySelectStr() {
876 		return getQueryStr(false);
877 	}
878 
879 	/**
880 	 * @param isCount is this a count query?
881 	 * @return the query as string
882 	 */
883 	private String getQueryStr(boolean isCount) {
884 		StringBuilder result = new StringBuilder();
885 		
886 		String selected;
887 		String orderStr;
888 		if (isCount) {
889 			selected = "count(*)";
890 			orderStr = "";
891 		} else {
892 			selected = selectQuery;
893 			if (orderBy.isEmpty()) {
894 				orderStr = "";
895 			} else {
896 				orderStr = " ORDER BY " + StringUtils.join(orderBy, COMMA);
897 			}
898 		}
899 		
900 		String fromStr;
901 		if (CollectionUtils.isEmpty(subUnionQueryBuilders)) {
902 			fromStr = StringUtils.join(froms, COMMA);
903 		} else {
904 			fromStr = getUnionStr();
905 		}
906 		
907 		result.append(" SELECT ");
908 		result.append(selected);
909 		result.append(" FROM ");
910 		result.append(fromStr);
911 		result.append(" ");
912 		result.append(StringUtils.join(joins, " "));
913 		if (StringUtils.isNotBlank(where)) {
914 			result.append(" WHERE ");
915 			result.append(where);
916 		}
917 		result.append(orderStr);
918 		
919 		return result.toString();
920 	}
921 
922 	/**
923 	 * TODO: what's this?
924 	 */
925 	private String getUnionStr() {
926 		StringBuilder result = new StringBuilder();
927 		List<String> subQueries = new ArrayList<String>();
928 		for (QueryBuilder builder : this.subUnionQueryBuilders) {
929 			String queryStr = builder.toString();
930 			// TODO CODEREVIEW IT1 NLN improve: remove ORDER BY
931 			int pos = queryStr.indexOf("ORDER BY");
932 			if (pos != -1) {
933 				queryStr = queryStr.substring(0, pos - 1);
934 			}
935 			subQueries.add(queryStr);
936 		}
937 		result.append(StringUtils.join(subQueries, " UNION "));
938 		return "(" + result.toString() + ")";
939 	}
940 
941 	/**
942 	 * Get all attached parameters.
943 	 * 
944 	 * @return the parameters
945 	 */
946 	public List<Object> getBodyParameters() {
947 		return bodyParameters;
948 	}
949 
950 	/**
951 	 * drops all ordering constraints.
952 	 * @return this
953 	 */
954 	public QueryBuilder clearOrderBy() {
955 		this.orderBy.clear();
956 		return this;
957 	}
958 }