View Javadoc

1   package ch.elca.el4j.services.persistence.hibernate.dao;
2   
3   import java.io.Serializable;
4   import java.util.ArrayList;
5   import java.util.Collection;
6   import java.util.List;
7   
8   import org.apache.lucene.analysis.standard.StandardAnalyzer;
9   import org.apache.lucene.queryParser.MultiFieldQueryParser;
10  import org.apache.lucene.queryParser.ParseException;
11  import org.hibernate.Criteria;
12  import org.hibernate.HibernateException;
13  import org.hibernate.Query;
14  import org.hibernate.Session;
15  import org.hibernate.SessionFactory;
16  import org.hibernate.criterion.DetachedCriteria;
17  import org.hibernate.criterion.Projections;
18  import org.hibernate.search.FullTextSession;
19  import org.hibernate.search.Search;
20  import org.springframework.dao.DataAccessException;
21  import org.springframework.dao.DataRetrievalFailureException;
22  import org.springframework.dao.OptimisticLockingFailureException;
23  import org.springframework.orm.hibernate3.HibernateCallback;
24  import org.springframework.orm.hibernate3.HibernateOptimisticLockingFailureException;
25  import org.springframework.orm.hibernate3.HibernateTemplate;
26  import org.springframework.util.Assert;
27  
28  import ch.elca.el4j.services.monitoring.notification.CoreNotificationHelper;
29  import ch.elca.el4j.services.monitoring.notification.PersistenceNotificationHelper;
30  import ch.elca.el4j.services.search.QueryObject;
31  import ch.elca.el4j.util.codingsupport.Reject;
32  
33  /**
34   * This is a convenience class for the Hibernate template.
35   *  Features:
36   *   <ul>
37   *      <li> improved paging support: allows to specify id of 1st element of a
38   *            query
39   *      <li> methods that signal an error if no element is found
40   *            (they use the <em>Strong</em> suffixes)
41   *   </ul>
42   *
43   * @svnLink $Revision: 4110 $;$Date: 2010-08-04 14:40:16 +0200 (Mi, 04. Aug 2010) $;$Author: swrelca $;$URL: https://el4j.svn.sourceforge.net/svnroot/el4j/branches/el4j_3_1/el4j/framework/modules/hibernate/src/main/java/ch/elca/el4j/services/persistence/hibernate/dao/ConvenienceHibernateTemplate.java $
44   *
45   * @author Alex Mathey (AMA)
46   */
47  public class ConvenienceHibernateTemplate extends HibernateTemplate {
48  	
49  	/**
50  	 * Constructor.
51  	 * @param sessionFactory SessionFactory to create Sessions
52  	 */
53  	public ConvenienceHibernateTemplate(SessionFactory sessionFactory) {
54  		super(sessionFactory);
55  	}
56  	
57  	/**
58  	 * Retrieves the persistent instance given by its identifier in a strong
59  	 * way: does the same as the <code>getById(Class, java.io.Serializable)</code>
60  	 * method, but throws a <code>DataRetrievalException</code> instead of
61  	 * <code>null</code> if the persistent instance could not be found.
62  	 *
63  	 * @see HibernateTemplate#get(Class, java.io.Serializable)
64  	 * @param entityClass
65  	 *            The class of the object which should be returned.
66  	 * @param id
67  	 *            An identifier of the persistent instance
68  	 * @param objectName
69  	 *            Name of the persistent object type.
70  	 * @return the persistent instance
71  	 * @throws org.springframework.dao.DataAccessException
72  	 *             in case of Hibernate errors
73  	 * @throws org.springframework.dao.DataRetrievalFailureException
74  	 *             in case the persistent instance is null
75  	 */
76  	public Object getByIdStrong(Class<?> entityClass, Serializable id, final String objectName)
77  		throws DataAccessException, DataRetrievalFailureException {
78  
79  		Reject.ifNull(id, "The identifier must not be null.");
80  		Reject.ifEmpty(objectName, "The name of the persistent object type "
81  			+ "must not be empty.");
82  		Object result = get(entityClass, id);
83  		
84  		if (result == null || !(entityClass.isInstance(result))) {
85  			PersistenceNotificationHelper.notifyObjectRetrievalFailure(entityClass, id, objectName);
86  		}
87  		return result;
88  	}
89  	
90  	/**
91  	 * Retrieves a persistent instance lazily.
92  	 * @see getByIdStrong
93  	 */
94  	public Object getByIdStrongLazy(Class<?> entityClass, Serializable id, final String objectName)
95  		throws DataAccessException, DataRetrievalFailureException {
96  		
97  		Reject.ifNull(id, "The identifier must not be null.");
98  		Reject.ifEmpty(objectName, "The name of the persistent object type "
99  			+ "must not be empty.");
100 		Object result = load(entityClass, id);
101 		
102 		if (result == null || !(entityClass.isInstance(result))) {
103 			PersistenceNotificationHelper.notifyObjectRetrievalFailure(entityClass, id, objectName);
104 		}
105 		return result;
106 	}
107 
108 	
109 	/**
110 	 * Retrieves a persistent instance with the help of a parameterized query:
111 	 * does the same as the
112 	 * <code>findByNamedParam(String, String, Object)</code> method, but
113 	 * returns a persistent instance instead of a list of persistent objects and
114 	 * throws a <code>DataRetrievalException</code> if the returned list does
115 	 * not contain exactly one element.
116 	 *
117 	 * @see HibernateTemplate#findByNamedParam(String, String, Object)
118 	 * @param queryString
119 	 *            The string corresponding to HQL query
120 	 * @param paramName
121 	 *            The name of the parameter
122 	 * @param value
123 	 *            The value of the parameter
124 	 * @param objectName
125 	 *            Name of the persistent object type.
126 	 * @return the persistent instance returned by the query
127 	 * @throws org.springframework.dao.DataAccessException
128 	 *             in case of Hibernate errors
129 	 * @throws org.springframework.dao.DataRetrievalFailureException
130 	 *             in case the list of persistent instances is empty, or if it
131 	 *             contains more than one object
132 	 */
133 	public Object findByNamedParamStrong(String queryString, String paramName, Object value, final String objectName)
134 		throws DataAccessException, DataRetrievalFailureException {
135 		
136 		Reject.ifEmpty(paramName);
137 		Reject.ifNull(value);
138 		Reject.ifEmpty(objectName, "The name of the persistent object type "
139 			+ "must not be empty.");
140 		List<?> result = findByNamedParam(queryString, paramName, value);
141 		if (result.size() != 1) {
142 			String message = "";
143 			if (result.isEmpty()) {
144 				message = "The desired " + objectName
145 					+ " does not exist.";
146 			} else if (result.size() > 1) {
147 				message = "The query resulted in more than one persistent "
148 					+ " instance.";
149 			}
150 			PersistenceNotificationHelper.notifyDataRetrievalFailure(message,
151 				objectName);
152 		}
153 		return result.get(0);
154 	}
155 	
156 	/**
157 	 * Saves or updates the given persistent instance in a strong way: does the
158 	 * same as the <code>saveOrUpdate(Object)</code> method, but throws a more
159 	 * specific <code>OptimisticLockingFailureException</code> in the case of
160 	 * an optimistic locking failure.
161 	 *
162 	 * @see HibernateTemplate#saveOrUpdate(Object)
163 	 * @param entity
164 	 *            the persistent entity to save or update
165 	 * @param objectName
166 	 *            Name of the persistent object type.
167 	 * @throws DataAccessException
168 	 *             in case of Hibernate errors
169 	 * @throws OptimisticLockingFailureException
170 	 *             in case optimistic locking fails
171 	 */
172 	public void saveOrUpdateStrong(Object entity, final String objectName)
173 		throws DataAccessException, OptimisticLockingFailureException {
174 		
175 		Reject.ifNull(entity);
176 		Reject.ifEmpty(objectName, "The name of the persistent object type "
177 			+ "must not be empty.");
178 		try {
179 			saveOrUpdate(entity);
180 		} catch (HibernateOptimisticLockingFailureException holfe) {
181 			String message = "The current " + objectName + " was modified or"
182 				+ " deleted in the meantime.";
183 			PersistenceNotificationHelper.notifyOptimisticLockingFailure(
184 				message, objectName, holfe);
185 		}
186 	}
187 	
188 	/**
189 	 * Deletes the persistent instance given by its identifier in a strong way:
190 	 * first, the persistent instance is retrieved with the help of the
191 	 * identifier. If it exists, it will be deleted, otherwise a
192 	 * <code>DataRetrievalFailureException</code> will be thrown.
193 	 *
194 	 * @see HibernateTemplate#delete(Object)
195 	 * @param entityClass
196 	 *            The class of the object which should be deleted.
197 	 * @param id
198 	 *            The identifier of the persistent instance to delete
199 	 * @param objectName
200 	 *            Name of the persistent object type.
201 	 * @throws org.springframework.dao.DataRetrievalFailureException
202 	 *             in case the persistent instance to delete is null
203 	 */
204 	public void deleteStrong(Class<?> entityClass, Serializable id, final String objectName)
205 		throws DataRetrievalFailureException {
206 		
207 		Reject.ifEmpty(objectName, "The name of the persistent object type "
208 			+ "must not be empty.");
209 		Object toDelete = null;
210 		try {
211 			toDelete = getByIdStrong(entityClass, id, objectName);
212 		} catch (DataRetrievalFailureException e) {
213 			String message = "The current " + objectName + " was "
214 				+ "deleted in the meantime.";
215 			PersistenceNotificationHelper.notifyOptimisticLockingFailure(
216 				message, objectName, null);
217 		}
218 		delete(toDelete);
219 	}
220 	
221 	//// paging support /////
222 	
223 	/**
224 	 * for paging: what is the id of the first result to return?
225 	 *  NO_CONSTRAINT means we do not constrain anything
226 	 */
227 	int m_firstResult = QueryObject.NO_CONSTRAINT;
228 	
229 	/**
230 	 * Overload parent class to support also a constraint
231 	 *  of the id of the first result to load.
232 	 * {@inheritDoc}
233 	 *
234 	 *  TODO shall we drop this and instead use the
235 	 *   findByCriteria(DetachedCriteria,int,int) method?
236 	 */
237 	@Override
238 	protected void prepareQuery(Query queryObject) {
239 		super.prepareQuery(queryObject);
240 		
241 		if (getFirstResult() != QueryObject.NO_CONSTRAINT) {
242 			queryObject.setFirstResult(getFirstResult());
243 		}
244 		
245 	}
246 
247 	/**
248 	 * {@inheritDoc}
249 	 */
250 	@Override
251 	protected void prepareCriteria(Criteria criteria) {
252 		super.prepareCriteria(criteria);
253 
254 		if (getFirstResult() != QueryObject.NO_CONSTRAINT) {
255 			criteria.setFirstResult(getFirstResult());
256 		}
257 		
258 	}
259 
260 	
261 	/**
262 	 * Counts the number of results of a search.
263 	 * @param criteria The criteria for the query.
264 	 * @return The number of results of the query.
265 	 * @throws DataAccessException
266 	 */
267 	public int findCountByCriteria(final DetachedCriteria criteria) throws DataAccessException {
268 
269 		Assert.notNull(criteria, "DetachedCriteria must not be null");
270 		Object result =  executeWithNativeSession(new HibernateCallback() {
271 			public Object doInHibernate(Session session) throws HibernateException {
272 				Criteria executableCriteria = criteria.getExecutableCriteria(session);
273 				executableCriteria.setProjection(Projections.rowCount());
274 								
275 				prepareCriteria(executableCriteria);
276 				
277 				return executableCriteria.uniqueResult();
278 			}
279 		});
280 		if (result == null) {
281 			result = 0;
282 		}
283 		
284 		if (result instanceof Long) { result = ((Long) result).intValue(); }
285 		
286 		return (Integer) result;
287 	}
288 	
289 	/**
290 	 * Gets the id of the first result to return.
291 	 * @return The id of the first result to return.
292 	 */
293 	public int getFirstResult() {
294 		return m_firstResult;
295 	}
296 
297 	/**
298 	 * Sets the id of the first result to return.
299 	 * @param firstResult The id of the first result to return.
300 	 */
301 	public void setFirstResult(int firstResult) {
302 		m_firstResult = firstResult;
303 	}
304 	
305 	// Hibernate Search
306 	
307 	/**
308 	 * Trigger Hibernate Search index process explicitly.
309 	 * 
310 	 * @param objects    objects to index
311 	 * @throws DataAccessException
312 	 * @throws DataRetrievalFailureException
313 	 */
314 	public void createHibernateSearchIndex(final Collection<?> objects)
315 		throws DataAccessException, DataRetrievalFailureException {
316 		
317 		Assert.notNull(objects, "Objects to index by Hibernate Search must not be null");
318 		executeWithNativeSession(new HibernateCallback() {
319 			public Object doInHibernate(Session session) throws HibernateException {
320 				FullTextSession fullTextSession = Search.getFullTextSession(session);
321 				
322 				//Transaction tx = fullTextSession.beginTransaction();
323 				for (Object object : objects) {
324 					fullTextSession.index(object);
325 				}
326 				//tx.commit();
327 				
328 				return null;
329 			}
330 		});
331 	}
332 	
333 	/**
334 	 * Search for objects having fields that match the search string.
335 	 * 
336 	 * @param <T>             the type of entities
337 	 * @param entityClass     the class of the entities to search
338 	 * @param fields          the names of the fields to consider while searching
339 	 * @param searchString    the search string
340 	 * @return                all entities having fields that match the search string.
341 	 * @throws DataAccessException
342 	 * @throws DataRetrievalFailureException
343 	 */
344 	@SuppressWarnings("unchecked")
345 	public <T> List<T> search(final Class<T> entityClass, final String[] fields, final String searchString)
346 		throws DataAccessException, DataRetrievalFailureException {
347 		
348 		Reject.ifEmpty(searchString);
349 		return (List) executeWithNativeSession(new HibernateCallback() {
350 			public Object doInHibernate(Session session) throws HibernateException {
351 				FullTextSession fullTextSession = Search.getFullTextSession(session);
352 				
353 				MultiFieldQueryParser parser = new MultiFieldQueryParser(fields, new StandardAnalyzer());
354 				org.apache.lucene.search.Query luceneQuery;
355 				try {
356 					luceneQuery = parser.parse(searchString);
357 				} catch (ParseException e) {
358 					return new ArrayList(0);
359 				}
360 				org.hibernate.Query query = fullTextSession.createFullTextQuery(luceneQuery, entityClass);
361 				
362 				List result = (List) query.list();
363 				return result;
364 			}
365 		});
366 	}
367 }