001 /*
002 * Copyright 2004 Chris Nelson
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
007 * Unless required by applicable law or agreed to in writing,
008 * software distributed under the License is distributed on an "AS IS" BASIS,
009 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
010 * See the License for the specific language governing permissions and limitations under the License.
011 */
012 package org.trails.hibernate;
013
014 import org.apache.hivemind.util.PropertyUtils;
015 import org.hibernate.*;
016 import org.hibernate.criterion.*;
017 import org.hibernate.metadata.ClassMetadata;
018 import org.springframework.beans.BeansException;
019 import org.springframework.context.ApplicationContext;
020 import org.springframework.context.ApplicationContextAware;
021 import org.springframework.dao.DataAccessException;
022 import org.springframework.orm.hibernate3.HibernateCallback;
023 import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
024 import org.springframework.transaction.annotation.Propagation;
025 import org.springframework.transaction.annotation.Transactional;
026 import org.trails.descriptor.CollectionDescriptor;
027 import org.trails.descriptor.DescriptorService;
028 import org.trails.descriptor.IClassDescriptor;
029 import org.trails.descriptor.IPropertyDescriptor;
030 import org.trails.persistence.HibernatePersistenceService;
031 import org.trails.persistence.PersistenceException;
032 import org.trails.util.Utils;
033 import org.trails.validation.ValidationException;
034
035 import java.io.Serializable;
036 import java.sql.SQLException;
037 import java.util.ArrayList;
038 import java.util.Collection;
039 import java.util.List;
040
041 public class HibernatePersistenceServiceImpl extends HibernateDaoSupport implements
042 HibernatePersistenceService, ApplicationContextAware
043 {
044
045 ApplicationContext appContext = null;
046 private DescriptorService _descriptorService = null;
047
048 /**
049 * We need this because cylcic reference between HibernatePersistenceServiceImpl and TrailsDescriptorService
050 */
051 public DescriptorService getDescriptorService()
052 {
053 if (_descriptorService == null)
054 {
055 _descriptorService = (DescriptorService) appContext.getBean("descriptorService");
056 }
057 return _descriptorService;
058 }
059
060 /**
061 * https://trails.dev.java.net/servlets/ReadMsg?listName=users&msgNo=1226
062 * <p/>
063 * Very often I find myself writing: <code> Object example = new Object(); example.setProperty(uniqueValue); List
064 * objects = ((TrailsPage)getPage()).getPersistenceService().getInstances(example); (MyObject)objects.get(0); </code>
065 * when, in fact, I know that the single property I populated my example object with should be unique, and thus only
066 * one object should be returned
067 *
068 * @param type The type to use to check for security restrictions.
069 * @param detachedCriteria
070 * @return
071 */
072 @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
073 public <T> T getInstance(final Class<T> type, DetachedCriteria detachedCriteria)
074 {
075 final DetachedCriteria criteria = alterCriteria(type, detachedCriteria);
076
077 return (T) getHibernateTemplate().execute(new HibernateCallback()
078 {
079 public Object doInHibernate(Session session) throws HibernateException, SQLException
080 {
081 return criteria.getExecutableCriteria(session).uniqueResult();
082 }
083 });
084 }
085
086 /**
087 * (non-Javadoc)
088 *
089 * @see org.trails.persistence.PersistenceService#getInstance(Class,Serializable)
090 */
091 @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
092 public <T> T getInstance(final Class<T> type, final Serializable id)
093 {
094 DetachedCriteria criteria = DetachedCriteria.forClass(Utils.checkForCGLIB(type)).add(Expression.idEq(id));
095 return getInstance(type, criteria);
096 }
097
098
099 /**
100 * <strong>Description copied from:</strong> {@link org.springframework.orm.hibernate3.HibernateTemplate#load(Class,java.io.Serializable)}
101 * and {@link org.hibernate.Session#load(Class,java.io.Serializable)}
102 * <p/>
103 * Return the persistent instance of the given entity class with the given identifier, assuming that the instance
104 * exists, throwing an exception if not found.
105 * <p/>
106 * You should not use this method to determine if an instance exists (use get() instead). Use this only to retrieve an
107 * instance that you assume exists, where non-existence would be an actual error.
108 * <p/>
109 * <p>This method is a thin wrapper around {@link org.hibernate.Session#load(Class,java.io.Serializable)} for
110 * convenience. For an explanation of the exact semantics of this method, please do refer to the Hibernate API
111 * documentation in the first instance.
112 *
113 * @param type a persistent class
114 * @param id the identifier of the persistent instance
115 * @return the persistent instance
116 * @see org.springframework.orm.hibernate3.HibernateTemplate#load(Class,java.io.Serializable)
117 * @see org.hibernate.Session#load(Class,java.io.Serializable)
118 */
119 @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
120 public <T> T loadInstance(final Class<T> type, Serializable id)
121 {
122 return (T) getHibernateTemplate().load(type, id);
123 }
124
125 /**
126 * Execute an HQL query.
127 *
128 * @param queryString a query expressed in Hibernate's query language
129 * @return a List containing the results of the query execution
130 * @see org.springframework.orm.hibernate3.HibernateTemplate#find(String)
131 */
132 @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
133 public List find(String queryString)
134 {
135 return getHibernateTemplate().find(queryString);
136 }
137
138 /**
139 * Execute an HQL query, binding one value to a "?" parameter in the query string.
140 *
141 * @param queryString a query expressed in Hibernate's query language
142 * @param value the query parameter
143 * @return a List containing the results of the query execution
144 * @see org.springframework.orm.hibernate3.HibernateTemplate#find(String,Object)
145 */
146 @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
147 public List find(String queryString, Object value)
148 {
149 return getHibernateTemplate().find(queryString, value);
150 }
151
152 /**
153 * Execute an HQL query, binding a number of values to "?" parameters in the query string.
154 *
155 * @param queryString a query expressed in Hibernate's query language
156 * @param values the query parameters
157 * @return a List containing the results of the query execution
158 * @see org.springframework.orm.hibernate3.HibernateTemplate#find(String,Object[])
159 */
160 @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
161 public List find(String queryString, Object[] values)
162 {
163 return getHibernateTemplate().find(queryString, values);
164 }
165
166
167 /**
168 * (non-Javadoc)
169 *
170 * @see org.trails.persistence.PersistenceService#getAllInstances(java.lang.Class)
171 */
172 @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
173 public <T> List<T> getAllInstances(final Class<T> type)
174 {
175 DetachedCriteria criteria = DetachedCriteria.forClass(Utils.checkForCGLIB(type));
176 return getInstances(type, criteria);
177 }
178
179 @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
180 public <T> List<T> getInstances(final Class<T> type, int startIndex, int maxResults)
181 {
182 return getInstances(type, DetachedCriteria.forClass(type), startIndex, maxResults);
183 }
184
185 /**
186 * (non-Javadoc)
187 *
188 * @see org.trails.persistence.PersistenceService#save(java.lang.Object)
189 */
190 @Transactional
191 public <T> T save(T instance) throws ValidationException
192 {
193 try
194 {
195 IClassDescriptor iClassDescriptor = getDescriptorService().getClassDescriptor(instance.getClass());
196 /* check isTransient to avoid merging on entities not persisted yet. TRAILS-33 */
197 if (!iClassDescriptor.getHasCyclicRelationships() || isTransient(instance, iClassDescriptor))
198 {
199 getHibernateTemplate().saveOrUpdate(instance);
200 } else
201 {
202 instance = (T) getHibernateTemplate().merge(instance);
203 }
204 return instance;
205 }
206 catch (DataAccessException dex)
207 {
208 throw new PersistenceException(dex);
209 }
210 }
211
212 @Transactional
213 public void removeAll(Collection collection)
214 {
215 getHibernateTemplate().deleteAll(collection);
216 }
217
218 @Transactional
219 public void remove(Object instance)
220 {
221 getHibernateTemplate().delete(instance, LockMode.READ);
222 }
223
224 @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
225 public <T> List<T> getInstances(Class<T> type, DetachedCriteria criteria)
226 {
227 criteria = alterCriteria(type, criteria);
228 criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
229 return getHibernateTemplate().findByCriteria(criteria);
230 }
231
232 /**
233 * (non-Javadoc)
234 *
235 * @see org.trails.persistence.PersistenceService#getAllTypes()
236 */
237 public List<Class> getAllTypes()
238 {
239 ArrayList<Class> allTypes = new ArrayList<Class>();
240 for (Object classMetadata : getSessionFactory().getAllClassMetadata().values())
241 {
242 allTypes.add(((ClassMetadata) classMetadata).getMappedClass(EntityMode.POJO));
243 }
244 return allTypes;
245 }
246
247 @Transactional
248 public void reattach(Object model)
249 {
250 getSession().lock(model, LockMode.NONE);
251 }
252
253
254 /**
255 * (non-Javadoc)
256 *
257 * @see org.trails.persistence.PersistenceService#getInstance(Class<T>)
258 */
259 @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
260 public <T> T getInstance(final Class<T> type)
261 {
262 return (T) getInstance(type, DetachedCriteria.forClass(type));
263 }
264
265 /**
266 * Returns an entity's pk.
267 *
268 * @note (ascandroli): I tried to implement it using something like:
269
270 * <code>
271 *
272 * @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
273 * private Serializable getIdentifier(final Object data)
274 * {
275 * return (Serializable) getHibernateTemplate().execute(new HibernateCallback()
276 * {
277 * public Object doInHibernate(Session session) throws HibernateException, SQLException
278 * {
279 * return session.getIdentifier(data);
280 * }
281 * });
282 * }
283 *
284 * </code>
285 *
286 * but it didn't work.
287 * "session.getIdentifier(data)" thows TransientObjectException when the Entity is not loaded by the current session,
288 * which is pretty usual in Trails.
289 *
290 *
291 * @param data
292 * @param classDescriptor
293 * @return
294 */
295 public Serializable getIdentifier(final Object data, final IClassDescriptor classDescriptor)
296 {
297 return (Serializable) PropertyUtils.read(data, classDescriptor.getIdentifierDescriptor().getName());
298 }
299
300 public boolean isTransient(Object data, IClassDescriptor classDescriptor)
301 {
302 try
303 {
304 return getIdentifier(data, classDescriptor) == null;
305 } catch (TransientObjectException e)
306 {
307 return true;
308 }
309 }
310
311 @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
312 public List getInstances(final Object example, final IClassDescriptor classDescriptor)
313 {
314 return (List) getHibernateTemplate().execute(new HibernateCallback()
315 {
316 public Object doInHibernate(Session session) throws HibernateException, SQLException
317 {
318 //create Criteria instance
319 DetachedCriteria searchCriteria = DetachedCriteria.forClass(Utils.checkForCGLIB(example.getClass()));
320 searchCriteria = alterCriteria(example.getClass(), searchCriteria);
321
322 //loop over the example object's PropertyDescriptors
323 for (IPropertyDescriptor propertyDescriptor : classDescriptor.getPropertyDescriptors())
324 {
325 //only add a Criterion to the Criteria instance if this property is searchable
326 if (propertyDescriptor.isSearchable())
327 {
328 String propertyName = propertyDescriptor.getName();
329 Class propertyClass = propertyDescriptor.getPropertyType();
330 Object value = PropertyUtils.read(example, propertyName);
331
332 //only add a Criterion to the Criteria instance if the value for this property is non-null
333 if (value != null)
334 {
335 if (String.class.isAssignableFrom(propertyClass) && ((String) value).length() > 0)
336 {
337 searchCriteria
338 .add(Restrictions.like(propertyName, value.toString(), MatchMode.ANYWHERE));
339 }
340 /**
341 * 'one'-end of many-to-one, one-to-one
342 *
343 * Just match the identifier
344 */
345 else if (propertyDescriptor.isObjectReference())
346 {
347 Serializable identifierValue = getIdentifier(value,
348 getDescriptorService().getClassDescriptor(propertyDescriptor.getBeanType()));
349 searchCriteria.createCriteria(propertyName).add(Restrictions.idEq(identifierValue));
350 } else if (propertyClass.isPrimitive())
351 {
352 //primitive types: ignore zeroes in case of numeric types, ignore booleans anyway (TODO come up with something...)
353 if (!propertyClass.equals(boolean.class) && ((Number) value).longValue() != 0)
354 {
355 searchCriteria.add(Restrictions.eq(propertyName, value));
356 }
357 } else if (propertyDescriptor.isCollection())
358 {
359 //one-to-many or many-to-many
360 CollectionDescriptor collectionDescriptor =
361 (CollectionDescriptor) propertyDescriptor;
362 IClassDescriptor classDescriptor = getDescriptorService().getClassDescriptor(collectionDescriptor.getElementType());
363 if (classDescriptor != null)
364 {
365 String identifierName = classDescriptor.getIdentifierDescriptor().getName();
366 Collection<Serializable> identifierValues = new ArrayList<Serializable>();
367 Collection associatedItems = (Collection) value;
368 if (associatedItems != null && associatedItems.size() > 0)
369 {
370 for (Object o : associatedItems)
371 {
372 identifierValues.add(getIdentifier(o, classDescriptor));
373 }
374 //add a 'value IN collection' restriction
375 searchCriteria.createCriteria(propertyName)
376 .add(Restrictions.in(identifierName, identifierValues));
377 }
378 }
379 }
380 }
381 }
382 }
383 searchCriteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
384 return searchCriteria.getExecutableCriteria(session).list();
385 }
386 }, true);
387 }
388
389 @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
390 public int count(Class type, DetachedCriteria detachedCriteria)
391 {
392 // todo hacking useNative is a result of SPR-2499 and will be removed soon
393 boolean useNative = getHibernateTemplate().isExposeNativeSession();
394 getHibernateTemplate().setExposeNativeSession(true);
395 detachedCriteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
396 final DetachedCriteria criteria = alterCriteria(type, detachedCriteria);
397 Integer result = (Integer) getHibernateTemplate().execute(new HibernateCallback()
398 {
399 public Object doInHibernate(Session session) throws HibernateException, SQLException
400 {
401 Criteria executableCriteria =
402 criteria.getExecutableCriteria(session).setProjection(Projections.rowCount());
403 return executableCriteria.uniqueResult();
404 }
405 });
406 getHibernateTemplate().setExposeNativeSession(useNative);
407 return result;
408 }
409
410 @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
411 public <T> List<T> getInstances(Class<T> type, final DetachedCriteria detachedCriteria, final int startIndex, final int maxResults)
412 {
413 return getInstances(alterCriteria(type, detachedCriteria), startIndex, maxResults);
414 }
415
416 @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
417 public List getInstances(final DetachedCriteria detachedCriteria, final int startIndex, final int maxResults)
418 {
419 detachedCriteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
420 // todo hacking useNative is a result of SPR-2499 and will be removed soon
421 boolean useNative = getHibernateTemplate().isExposeNativeSession();
422 getHibernateTemplate().setExposeNativeSession(true);
423 List result = (List) getHibernateTemplate().execute(new HibernateCallback()
424 {
425 public Object doInHibernate(Session session) throws HibernateException, SQLException
426 {
427 Criteria executableCriteria = detachedCriteria.getExecutableCriteria(session);
428 if (startIndex >= 0)
429 {
430 executableCriteria.setFirstResult(startIndex);
431 }
432 if (maxResults > 0)
433 {
434 executableCriteria.setMaxResults(maxResults);
435 }
436 return executableCriteria.list();
437 }
438 });
439 getHibernateTemplate().setExposeNativeSession(useNative);
440 return result;
441 }
442
443 /**
444 * This hook allows subclasses to modify the query criteria, such as for security
445 *
446 * @param detachedCriteria The original Criteria query
447 * @return The modified Criteria query for execution
448 */
449 protected DetachedCriteria alterCriteria(Class type, DetachedCriteria detachedCriteria)
450 {
451 return detachedCriteria;
452 }
453
454 /**
455 * @see org.trails.persistence.HibernatePersistenceService#saveOrUpdate(java.lang.Object)
456 */
457 @Transactional
458 public <T> T merge(T instance)
459 {
460 try
461 {
462 return (T) getHibernateTemplate().merge(instance);
463 }
464 catch (DataAccessException dex)
465 {
466 throw new PersistenceException(dex);
467 }
468 }
469
470 /**
471 * @see org.trails.persistence.HibernatePersistenceService#saveOrUpdate(java.lang.Object)
472 */
473 @Transactional
474 public <T> T saveOrUpdate(T instance) throws ValidationException
475 {
476 try
477 {
478 getHibernateTemplate().saveOrUpdate(instance);
479 return instance;
480 }
481 catch (DataAccessException dex)
482 {
483 throw new PersistenceException(dex);
484 }
485 }
486
487 @Transactional
488 public <T> T saveCollectionElement(String addExpression, T member, Object parent)
489 {
490 T instance = save(member);
491 Utils.executeOgnlExpression(addExpression, member, parent);
492 save(parent);
493 return instance;
494 }
495
496 @Transactional
497 public void removeCollectionElement(String removeExpression, Object member, Object parent)
498 {
499 Utils.executeOgnlExpression(removeExpression, member, parent);
500 save(parent);
501 remove(member);
502 }
503
504 public void setApplicationContext(ApplicationContext arg0) throws BeansException
505 {
506 this.appContext = arg0;
507
508 }
509 }