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    }