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.tynamo.hibernate.services;
013    
014    import org.apache.tapestry5.hibernate.HibernateSessionManager;
015    import org.hibernate.*;
016    import org.hibernate.criterion.*;
017    import org.tynamo.services.DescriptorService;
018    import org.tynamo.util.Utils;
019    import org.tynamo.descriptor.TynamoClassDescriptor;
020    import org.tynamo.descriptor.TynamoPropertyDescriptor;
021    import org.tynamo.descriptor.CollectionDescriptor;
022    import org.slf4j.Logger;
023    
024    import java.io.Serializable;
025    import java.util.ArrayList;
026    import java.util.Collection;
027    import java.util.List;
028    
029    @SuppressWarnings("unchecked")
030    public class HibernatePersistenceServiceImpl implements HibernatePersistenceService
031    {
032    
033            private Logger logger;
034            private DescriptorService descriptorService;
035            private Session session;
036            private HibernateSessionManager sessionManager;
037    
038            public HibernatePersistenceServiceImpl(Logger logger, DescriptorService descriptorService, Session session, HibernateSessionManager sessionManager)
039            {
040                    this.logger = logger;
041                    this.descriptorService = descriptorService;
042                    this.session = session;
043                    // we need a sessionmanager as well (only?) because Tapestry session proxy doesn't implement Hibernate's SessionImplementator interface
044                    this.sessionManager = sessionManager;
045            }
046    
047            /**
048             * https://trails.dev.java.net/servlets/ReadMsg?listName=users&msgNo=1226
049             * <p/>
050             * Very often I find myself writing:
051             * <code>
052             * Object example = new Object(); example.setProperty(uniqueValue);
053             * List objects = ((TynamoPage)getPage()).getPersistenceService().getInstances(example);
054             * (MyObject)objects.get(0);
055             * </code>
056             * when, in fact, I know that the single property I populated my example object with should be unique, and thus only
057             * one object should be returned
058             *
059             * @param type                   The type to use to check for security restrictions.
060             * @param detachedCriteria
061             * @return
062             */
063            public <T> T getInstance(final Class<T> type, DetachedCriteria detachedCriteria)
064            {
065                    final DetachedCriteria criteria = alterCriteria(type, detachedCriteria);
066                    return (T) criteria.getExecutableCriteria(sessionManager.getSession()).uniqueResult();
067            }
068    
069            /**
070             * (non-Javadoc)
071             *
072             * @see org.tynamo.services.PersistenceService#getInstance(Class,Serializable)
073             */
074    
075            public <T> T getInstance(final Class<T> type, final Serializable id)
076            {
077                    DetachedCriteria criteria = DetachedCriteria.forClass(Utils.checkForCGLIB(type)).add(Restrictions.idEq(id));
078                    return getInstance(type, criteria);
079            }
080    
081    
082            /**
083             * <strong>Description copied from:</strong> {@link org.hibernate.Session#load(Class,java.io.Serializable)}
084             * <p/>
085             * Return the persistent instance of the given entity class with the given identifier, assuming that the instance
086             * exists, throwing an exception if not found.
087             * <p/>
088             * You should not use this method to determine if an instance exists (use get() instead). Use this only to retrieve an
089             * instance that you assume exists, where non-existence would be an actual error.
090             * <p/>
091             * <p>This method is a thin wrapper around {@link org.hibernate.Session#load(Class,java.io.Serializable)} for
092             * convenience. For an explanation of the exact semantics of this method, please do refer to the Hibernate API
093             * documentation in the first instance.
094             *
095             * @param type a persistent class
096             * @param id   the identifier of the persistent instance
097             * @return the persistent instance
098             * @see org.hibernate.Session#load(Class,java.io.Serializable)
099             */
100    
101            public <T> T loadInstance(final Class<T> type, Serializable id)
102            {
103                    return (T) session.load(type, id);
104            }
105    
106            /**
107             * Execute an HQL query.
108             *
109             * @param queryString a query expressed in Hibernate's query language
110             * @return a List of entities containing the results of the query execution
111             */
112            public List findByQuery(String queryString)
113            {
114                    return findByQuery(queryString, new QueryParameter[0]);
115            }
116    
117            /**
118             * Execute an HQL query.
119             *
120             * @param queryString a query expressed in Hibernate's query language
121             * @param parameters  the (optional) parameters for the query.
122             * @return a List of entities containing the results of the query execution
123             */
124            public List findByQuery(String queryString, QueryParameter... parameters)
125            {
126                    return findByQuery(queryString, 0, 0, parameters);
127            }
128    
129            /**
130             * Execute an HQL query.
131             *
132             * @param queryString a query expressed in Hibernate's query language
133             * @param startIndex  the index of the first item to be retrieved
134             * @param maxResults  the number of items to be retrieved, if <code>0</code> it retrieves ALL the items
135             * @param parameters  the (optional) parameters for the query.
136             * @return a List of entities containing the results of the query execution
137             */
138            public List findByQuery(String queryString, int startIndex, int maxResults, QueryParameter... parameters)
139            {
140                    Query query = session.createQuery(queryString);
141                    for (QueryParameter parameter : parameters)
142                    {
143                            parameter.applyNamedParameterToQuery(query);
144                    }
145    
146                    if (maxResults > 0)
147                            query.setMaxResults(maxResults);
148    
149                    if (startIndex > 0)
150                            query.setFirstResult(startIndex);
151    
152                    if (logger.isDebugEnabled())
153                            logger.debug(query.getQueryString());
154    
155                    return query.list();
156            }
157    
158    
159            /**
160             * (non-Javadoc)
161             *
162             * @see org.tynamo.services.PersistenceService#getInstances(java.lang.Class)
163             */
164    
165            public <T> List<T> getInstances(final Class<T> type)
166            {
167                    return session.createCriteria(type).list();
168            }
169    
170    
171            public <T> List<T> getInstances(final Class<T> type, int startIndex, int maxResults)
172            {
173                    return getInstances(type, DetachedCriteria.forClass(type), startIndex, maxResults);
174            }
175    
176            /**
177             * (non-Javadoc)
178             *
179             * @see org.tynamo.services.PersistenceService#save(java.lang.Object)
180             */
181            public <T> T save(T instance) // throws ValidationException
182            {
183    /*
184                    try
185                    {
186    */
187                    TynamoClassDescriptor TynamoClassDescriptor = descriptorService.getClassDescriptor(instance.getClass());
188                    /* check isTransient to avoid merging on entities not persisted yet. TRAILS-33 */
189                    if (!TynamoClassDescriptor.getHasCyclicRelationships() || isTransient(instance, TynamoClassDescriptor))
190                    {
191                            session.saveOrUpdate(instance);
192                    } else
193                    {
194                            instance = (T) session.merge(instance);
195                    }
196                    return instance;
197    //              }
198    /*
199                    catch (DataAccessException dex)
200                    {
201                            throw new PersistenceException(dex);
202                    }
203    */
204            }
205    
206    
207            public void removeAll(Collection collection)
208            {
209    //              session.deleteAll(collection);
210            }
211    
212    
213            public void remove(Object instance)
214            {
215                    session.delete(instance);
216            }
217    
218    
219            public <T> List<T> getInstances(Class<T> type, DetachedCriteria criteria)
220            {
221                    criteria = alterCriteria(type, criteria);
222                    criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
223                    return criteria.getExecutableCriteria(sessionManager.getSession()).list();
224            }
225    
226            /**
227             * (non-Javadoc)
228             *
229             * @see org.tynamo.services.PersistenceService#getAllTypes()
230             */
231    /*
232            public List<Class> getAllTypes()
233            {
234                    ArrayList<Class> allTypes = new ArrayList<Class>();
235                    for (Object classMetadata : getSessionFactory().getAllClassMetadata().values())
236                    {
237                            allTypes.add(((ClassMetadata) classMetadata).getMappedClass(EntityMode.POJO));
238                    }
239                    return allTypes;
240            }
241    */
242            public void reattach(Object model)
243            {
244                    session.lock(model, LockMode.NONE);
245            }
246    
247    
248            /**
249             * (non-Javadoc)
250             *
251             * @see org.tynamo.services.PersistenceService#getInstance(Class<T>)
252             */
253    
254            public <T> T getInstance(final Class<T> type)
255            {
256                    return (T) getInstance(type, DetachedCriteria.forClass(type));
257            }
258    
259            /**
260             * Returns an entity's pk.
261             *
262             * @param data
263             * @param classDescriptor
264             * @return
265             * @note (ascandroli): I tried to implement it using something like:
266             * <p/>
267             * <code>
268             * <p/>
269             * <p/>
270             * private Serializable getIdentifier(final Object data)
271             * {
272             * return (Serializable) session.execute(new HibernateCallback()
273             * {
274             * public Object doInHibernate(Session session) throws HibernateException, SQLException
275             * {
276             * return session.getIdentifier(data);
277             * }
278             * });
279             * }
280             * <p/>
281             * </code>
282             * <p/>
283             * but it didn't work.
284             * "session.getIdentifier(data)" thows TransientObjectException when the Entity is not loaded by the current session,
285             * which is pretty usual in Tynamo.
286             */
287            public Serializable getIdentifier(final Object data, final TynamoClassDescriptor classDescriptor)
288            {
289    //              return (Serializable) PropertyUtils.read(data, classDescriptor.getIdentifierDescriptor().getName());
290                    return 0;
291            }
292    
293            public boolean isTransient(Object data, TynamoClassDescriptor classDescriptor)
294            {
295                    try
296                    {
297                            return getIdentifier(data, classDescriptor) == null;
298                    } catch (TransientObjectException e)
299                    {
300                            return true;
301                    }
302            }
303    
304    
305            public List getInstances(final Object example, final TynamoClassDescriptor classDescriptor)
306            {
307                    //create Criteria instance
308                    DetachedCriteria searchCriteria = DetachedCriteria.forClass(Utils.checkForCGLIB(example.getClass()));
309                    searchCriteria = alterCriteria(example.getClass(), searchCriteria);
310    
311                    //loop over the example object's PropertyDescriptors
312                    for (TynamoPropertyDescriptor propertyDescriptor : classDescriptor.getPropertyDescriptors())
313                    {
314                            //only add a Criterion to the Criteria instance if this property is searchable
315                            if (propertyDescriptor.isSearchable())
316                            {
317                                    String propertyName = propertyDescriptor.getName();
318                                    Class propertyClass = propertyDescriptor.getPropertyType();
319                                    Object value = null; //PropertyUtils.read(example, propertyName);
320    
321                                    //only add a Criterion to the Criteria instance if the value for this property is non-null
322                                    if (value != null)
323                                    {
324                                            if (String.class.isAssignableFrom(propertyClass) && ((String) value).length() > 0)
325                                            {
326                                                    searchCriteria
327                                                                    .add(Restrictions.like(propertyName, value.toString(), MatchMode.ANYWHERE));
328                                            }
329                                            /**
330                                             * 'one'-end of many-to-one, one-to-one
331                                             *
332                                             * Just match the identifier
333                                             */
334                                            else if (propertyDescriptor.isObjectReference())
335                                            {
336                                                    Serializable identifierValue = getIdentifier(value,
337                                                                    descriptorService.getClassDescriptor(propertyDescriptor.getBeanType()));
338                                                    searchCriteria.createCriteria(propertyName).add(Restrictions.idEq(identifierValue));
339                                            } else if (propertyClass.isPrimitive())
340                                            {
341                                                    //primitive types: ignore zeroes in case of numeric types, ignore booleans anyway (TODO come up with something...)
342                                                    if (!propertyClass.equals(boolean.class) && ((Number) value).longValue() != 0)
343                                                    {
344                                                            searchCriteria.add(Restrictions.eq(propertyName, value));
345                                                    }
346                                            } else if (propertyDescriptor.isCollection())
347                                            {
348                                                    //one-to-many or many-to-many
349                                                    CollectionDescriptor collectionDescriptor =
350                                                                    (CollectionDescriptor) propertyDescriptor;
351                                                    TynamoClassDescriptor collectionClassDescriptor = descriptorService.getClassDescriptor(collectionDescriptor.getElementType());
352                                                    if (collectionClassDescriptor != null)
353                                                    {
354                                                            String identifierName = collectionClassDescriptor.getIdentifierDescriptor().getName();
355                                                            Collection<Serializable> identifierValues = new ArrayList<Serializable>();
356                                                            Collection associatedItems = (Collection) value;
357                                                            if (associatedItems != null && associatedItems.size() > 0)
358                                                            {
359                                                                    for (Object o : associatedItems)
360                                                                    {
361                                                                            identifierValues.add(getIdentifier(o, collectionClassDescriptor));
362                                                                    }
363                                                                    //add a 'value IN collection' restriction
364                                                                    searchCriteria.createCriteria(propertyName)
365                                                                                    .add(Restrictions.in(identifierName, identifierValues));
366                                                            }
367                                                    }
368                                            }
369                                    }
370                            }
371                    }
372                    searchCriteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
373                    // FIXME This won't work because the shadow proxy doesn't implement SessionImplementor
374                    // that session is casted to. Maybe we should inject SessionManager instead 
375                    // and obtain the Session from it
376                    return searchCriteria.getExecutableCriteria(sessionManager.getSession()).list();
377            }
378    
379    
380            public int count(Class type, DetachedCriteria detachedCriteria)
381            {
382                    detachedCriteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
383                    final DetachedCriteria criteria = alterCriteria(type, detachedCriteria);
384                    Criteria executableCriteria = criteria.getExecutableCriteria(sessionManager.getSession()).setProjection(Projections.rowCount());
385                    return (Integer) executableCriteria.uniqueResult();
386            }
387    
388    
389            public <T> List<T> getInstances(Class<T> type, final DetachedCriteria detachedCriteria, final int startIndex, final int maxResults)
390            {
391                    return getInstances(alterCriteria(type, detachedCriteria), startIndex, maxResults);
392            }
393    
394    
395            public List getInstances(final DetachedCriteria detachedCriteria, final int startIndex, final int maxResults)
396            {
397                    detachedCriteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
398                    Criteria executableCriteria = detachedCriteria.getExecutableCriteria(sessionManager.getSession());
399                    if (startIndex >= 0)
400                    {
401                            executableCriteria.setFirstResult(startIndex);
402                    }
403                    if (maxResults > 0)
404                    {
405                            executableCriteria.setMaxResults(maxResults);
406                    }
407                    return executableCriteria.list();
408            }
409    
410            /**
411             * This hook allows subclasses to modify the query criteria, such as for security
412             *
413             * @param detachedCriteria The original Criteria query
414             * @return The modified Criteria query for execution
415             */
416            protected DetachedCriteria alterCriteria(Class type, DetachedCriteria detachedCriteria)
417            {
418                    return detachedCriteria;
419            }
420    
421            /**
422             * @see org.tynamo.hibernate.services.HibernatePersistenceService#saveOrUpdate(java.lang.Object)
423             */
424    
425            public <T> T merge(T instance)
426            {
427                    return (T) session.merge(instance);
428            }
429    
430            /**
431             * @see org.tynamo.hibernate.services.HibernatePersistenceService#saveOrUpdate(java.lang.Object)
432             */
433    
434            public <T> T saveOrUpdate(T instance)  // throws ValidationException
435            {
436                    session.saveOrUpdate(instance);
437                    return instance;
438    
439    /*
440                    catch (DataAccessException dex)
441                    {
442                            throw new PersistenceException(dex);
443                    }
444    */
445            }
446    
447    
448            public <T> T saveCollectionElement(String addExpression, T member, Object parent)
449            {
450                    T instance = save(member);
451                    Utils.executeOgnlExpression(addExpression, member, parent);
452                    save(parent);
453                    return instance;
454            }
455    
456    
457            public void removeCollectionElement(String removeExpression, Object member, Object parent)
458            {
459                    Utils.executeOgnlExpression(removeExpression, member, parent);
460                    save(parent);
461                    remove(member);
462            }
463    
464    }