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 }