1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.krad.data.jpa;
17
18 import java.lang.reflect.InvocationTargetException;
19 import java.util.Collection;
20 import java.util.HashSet;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.concurrent.Callable;
24
25 import javax.persistence.EntityManager;
26 import javax.persistence.NonUniqueResultException;
27 import javax.persistence.PersistenceException;
28 import javax.persistence.metamodel.ManagedType;
29
30 import org.apache.commons.beanutils.PropertyUtils;
31 import org.apache.commons.lang.ArrayUtils;
32 import org.eclipse.persistence.jpa.JpaEntityManager;
33 import org.eclipse.persistence.sessions.CopyGroup;
34 import org.kuali.rice.core.api.CoreConstants;
35 import org.kuali.rice.core.api.config.property.ConfigContext;
36 import org.kuali.rice.core.api.criteria.QueryByCriteria;
37 import org.kuali.rice.core.api.criteria.QueryResults;
38 import org.kuali.rice.core.api.exception.RiceRuntimeException;
39 import org.kuali.rice.core.api.mo.common.GloballyUnique;
40 import org.kuali.rice.core.api.mo.common.Versioned;
41 import org.kuali.rice.krad.data.CompoundKey;
42 import org.kuali.rice.krad.data.CopyOption;
43 import org.kuali.rice.krad.data.DataObjectService;
44 import org.kuali.rice.krad.data.DataObjectWrapper;
45 import org.kuali.rice.krad.data.KradDataServiceLocator;
46 import org.kuali.rice.krad.data.PersistenceOption;
47 import org.kuali.rice.krad.data.metadata.DataObjectCollection;
48 import org.kuali.rice.krad.data.metadata.DataObjectMetadata;
49 import org.kuali.rice.krad.data.metadata.DataObjectRelationship;
50 import org.kuali.rice.krad.data.provider.PersistenceProvider;
51 import org.springframework.beans.BeansException;
52 import org.springframework.beans.factory.BeanFactory;
53 import org.springframework.beans.factory.BeanFactoryAware;
54 import org.springframework.beans.factory.BeanFactoryUtils;
55 import org.springframework.beans.factory.ListableBeanFactory;
56 import org.springframework.dao.DataAccessException;
57 import org.springframework.dao.support.ChainedPersistenceExceptionTranslator;
58 import org.springframework.dao.support.DataAccessUtils;
59 import org.springframework.dao.support.PersistenceExceptionTranslator;
60 import org.springframework.orm.jpa.EntityManagerFactoryUtils;
61 import org.springframework.transaction.annotation.Transactional;
62
63 import com.google.common.collect.Sets;
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 public class JpaPersistenceProvider implements PersistenceProvider, BeanFactoryAware {
87
88 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(JpaPersistenceProvider.class);
89
90
91
92
93
94
95
96
97 public static final String AUTO_FLUSH = "rice.krad.data.jpa.autoFlush";
98
99 private EntityManager sharedEntityManager;
100 private DataObjectService dataObjectService;
101
102 private PersistenceExceptionTranslator persistenceExceptionTranslator;
103
104 private Set<Class<?>> managedTypesCache;
105
106
107
108
109 private static final class LazyConfigHolder {
110 private static final boolean autoFlush = ConfigContext.getCurrentContextConfig().getBooleanProperty(AUTO_FLUSH, false);
111 }
112
113
114
115
116
117
118 public EntityManager getSharedEntityManager() {
119 return sharedEntityManager;
120 }
121
122
123
124
125
126
127 public void setSharedEntityManager(EntityManager sharedEntityManager) {
128 this.sharedEntityManager = sharedEntityManager;
129 }
130
131
132
133
134
135
136 public void setDataObjectService(DataObjectService dataObjectService) {
137 this.dataObjectService = dataObjectService;
138 }
139
140
141
142
143
144
145 public DataObjectService getDataObjectService() {
146 return this.dataObjectService;
147 }
148
149
150
151
152 @Override
153 public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
154 if (!(beanFactory instanceof ListableBeanFactory)) {
155 throw new IllegalArgumentException(
156 "Cannot use PersistenceExceptionTranslator autodetection without ListableBeanFactory");
157 }
158 this.persistenceExceptionTranslator = detectPersistenceExceptionTranslators((ListableBeanFactory)beanFactory);
159 }
160
161
162
163
164
165
166
167
168 protected PersistenceExceptionTranslator detectPersistenceExceptionTranslators(ListableBeanFactory beanFactory) {
169
170 Map<String, PersistenceExceptionTranslator> pets = BeanFactoryUtils.beansOfTypeIncludingAncestors(beanFactory,
171 PersistenceExceptionTranslator.class, false, false);
172 ChainedPersistenceExceptionTranslator cpet = new ChainedPersistenceExceptionTranslator();
173 for (PersistenceExceptionTranslator pet : pets.values()) {
174 cpet.addDelegate(pet);
175 }
176
177 cpet.addDelegate(new DefaultPersistenceExceptionTranslator());
178 return cpet;
179 }
180
181
182
183
184 @Override
185 @Transactional
186 public <T> T save(final T dataObject, final PersistenceOption... options) {
187 return doWithExceptionTranslation(new Callable<T>() {
188 @Override
189 public T call() {
190 verifyDataObjectWritable(dataObject);
191
192 Set<PersistenceOption> optionSet = Sets.newHashSet(options);
193
194 T mergedDataObject = sharedEntityManager.merge(dataObject);
195
196
197
198
199
200
201
202 if(optionSet.contains(PersistenceOption.FLUSH) || optionSet.contains(PersistenceOption.LINK_KEYS) ||
203 LazyConfigHolder.autoFlush){
204 sharedEntityManager.flush();
205 }
206
207 if (sharedEntityManager.getEntityManagerFactory().getCache() != null) {
208 try {
209 Object dataObjectKey = sharedEntityManager.getEntityManagerFactory().getPersistenceUnitUtil()
210 .getIdentifier(mergedDataObject);
211 if (dataObjectKey != null) {
212 sharedEntityManager.getEntityManagerFactory().getCache()
213 .evict(dataObject.getClass(), dataObjectKey);
214 }
215 } catch (PersistenceException ex) {
216
217 }
218 }
219
220 return mergedDataObject;
221 }
222 });
223 }
224
225
226
227
228 @Override
229 @Transactional(readOnly = true)
230 public <T> T find(final Class<T> type, final Object id) {
231 return doWithExceptionTranslation(new Callable<T>() {
232 @Override
233 public T call() {
234 if (id instanceof CompoundKey) {
235 QueryResults<T> results = findMatching(type,
236 QueryByCriteria.Builder.andAttributes(((CompoundKey) id).getKeys()).build());
237 if (results.getResults().size() > 1) {
238 throw new NonUniqueResultException("Error Compound Key: " + id + " on class " + type.getName()
239 + " returned more than one row.");
240 }
241 if (!results.getResults().isEmpty()) {
242 return results.getResults().get(0);
243 }
244 return null;
245 } else {
246 return sharedEntityManager.find(type, id);
247 }
248 }
249 });
250 }
251
252
253
254
255 @Override
256 @Transactional(readOnly = true)
257 public <T> QueryResults<T> findMatching(final Class<T> type, final QueryByCriteria queryByCriteria) {
258 return doWithExceptionTranslation(new Callable<QueryResults<T>>() {
259 @Override
260 public QueryResults<T> call() {
261 return new JpaCriteriaQuery(sharedEntityManager).lookup(type, queryByCriteria);
262 }
263 });
264 }
265
266
267
268
269 @Override
270 @Transactional(readOnly = true)
271 public <T> QueryResults<T> findAll(final Class<T> type) {
272 return doWithExceptionTranslation(new Callable<QueryResults<T>>() {
273 @Override
274 public QueryResults<T> call() {
275 return new JpaCriteriaQuery(getSharedEntityManager()).lookup(type, QueryByCriteria.Builder.create().build());
276 }
277 });
278 }
279
280
281
282
283 @Override
284 @Transactional
285 public void delete(final Object dataObject) {
286 doWithExceptionTranslation(new Callable<Object>() {
287 @Override
288 public Object call() {
289 verifyDataObjectWritable(dataObject);
290
291
292 if (sharedEntityManager.getEntityManagerFactory().getCache() != null) {
293 try {
294 Object dataObjectKey = sharedEntityManager.getEntityManagerFactory().getPersistenceUnitUtil()
295 .getIdentifier(dataObject);
296 if (dataObjectKey != null) {
297 sharedEntityManager.getEntityManagerFactory().getCache()
298 .evict(dataObject.getClass(), dataObjectKey);
299 }
300 } catch (PersistenceException ex) {
301
302 }
303 }
304 Object mergedDataObject = sharedEntityManager.merge(dataObject);
305 sharedEntityManager.remove(mergedDataObject);
306 return null;
307 }
308 });
309 }
310
311
312
313
314 @Override
315 @Transactional
316 public <T> void deleteMatching(final Class<T> type, final QueryByCriteria queryByCriteria) {
317 doWithExceptionTranslation(new Callable<Object>() {
318 @Override
319 public Object call() {
320 new JpaCriteriaQuery(getSharedEntityManager()).deleteMatching(type, queryByCriteria);
321
322
323 if (sharedEntityManager.getEntityManagerFactory().getCache() != null) {
324 sharedEntityManager.getEntityManagerFactory().getCache().evict(type);
325 }
326 return null;
327 }
328 });
329 }
330
331
332
333
334 @Override
335 @Transactional
336 public <T> void deleteAll(final Class<T> type) {
337 doWithExceptionTranslation(new Callable<Object>() {
338 @Override
339 public Object call() {
340 new JpaCriteriaQuery(getSharedEntityManager()).deleteAll(type);
341
342
343 if (sharedEntityManager.getEntityManagerFactory().getCache() != null) {
344 sharedEntityManager.getEntityManagerFactory().getCache().evict(type);
345 }
346 return null;
347 }
348 });
349 }
350
351
352
353
354 @Override
355 @Transactional
356 public <T> T copyInstance(final T dataObject, CopyOption... options) {
357 final CopyGroup copyGroup = new CopyGroup();
358 if (ArrayUtils.contains(options, CopyOption.RESET_PK_FIELDS)) {
359 copyGroup.setShouldResetPrimaryKey(true);
360 }
361 final boolean shouldResetVersionNumber = ArrayUtils.contains(options, CopyOption.RESET_VERSION_NUMBER);
362 if (shouldResetVersionNumber) {
363 copyGroup.setShouldResetVersion(true);
364 }
365 final boolean shouldResetObjectId = ArrayUtils.contains(options, CopyOption.RESET_OBJECT_ID);
366 return doWithExceptionTranslation(new Callable<T>() {
367 @SuppressWarnings("unchecked")
368 @Override
369 public T call() {
370 T copiedObject = (T) sharedEntityManager.unwrap(JpaEntityManager.class).getDatabaseSession()
371 .copy(dataObject, copyGroup);
372 if (shouldResetObjectId) {
373 clearObjectIdOnUpdatableObjects(copiedObject, new HashSet<Object>());
374 }
375 if (shouldResetVersionNumber) {
376 clearVersionNumberOnUpdatableObjects(copiedObject, new HashSet<Object>());
377 }
378 return copiedObject;
379 }
380 });
381 }
382
383
384
385
386
387
388
389
390
391
392
393 protected void clearObjectIdOnUpdatableObjects(Object dataObject, Set<Object> visitedObjects) {
394 if (dataObject == null) {
395 return;
396 }
397
398 if (visitedObjects.contains(dataObject)) {
399 return;
400 }
401 visitedObjects.add(dataObject);
402 if (dataObject instanceof GloballyUnique) {
403 try {
404 PropertyUtils.setSimpleProperty(dataObject, CoreConstants.CommonElements.OBJECT_ID, null);
405 } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
406
407 LOG.warn("Unable to clear the objectId from copyInstance on an object: " + dataObject, ex);
408 }
409 }
410 DataObjectWrapper<Object> wrapper = KradDataServiceLocator.getDataObjectService().wrap(dataObject);
411 if (wrapper.getMetadata() != null) {
412 for (DataObjectRelationship rel : wrapper.getMetadata().getRelationships()) {
413 if (rel.isSavedWithParent()) {
414
415 clearObjectIdOnUpdatableObjects(wrapper.getPropertyValue(rel.getName()), visitedObjects);
416 }
417 }
418 for (DataObjectCollection rel : wrapper.getMetadata().getCollections()) {
419 if (rel.isSavedWithParent()) {
420 Collection<?> collection = (Collection<?>) wrapper.getPropertyValue(rel.getName());
421 if (collection != null) {
422 for (Object element : collection) {
423 clearObjectIdOnUpdatableObjects(element, visitedObjects);
424 }
425 }
426 }
427 }
428
429 }
430 }
431
432
433
434
435
436
437
438
439
440
441
442 protected void clearVersionNumberOnUpdatableObjects(Object dataObject, Set<Object> visitedObjects) {
443 if (dataObject == null) {
444 return;
445 }
446
447 if (visitedObjects.contains(dataObject)) {
448 return;
449 }
450 visitedObjects.add(dataObject);
451 if (dataObject instanceof Versioned) {
452 try {
453 PropertyUtils.setSimpleProperty(dataObject, CoreConstants.CommonElements.VERSION_NUMBER, null);
454 } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
455
456 LOG.warn("Unable to clear the objectId from copyInstance on an object: " + dataObject, ex);
457 }
458 }
459 DataObjectWrapper<Object> wrapper = KradDataServiceLocator.getDataObjectService().wrap(dataObject);
460 if (wrapper.getMetadata() != null) {
461 for (DataObjectRelationship rel : wrapper.getMetadata().getRelationships()) {
462 if (rel.isSavedWithParent()) {
463
464 clearVersionNumberOnUpdatableObjects(wrapper.getPropertyValue(rel.getName()), visitedObjects);
465 }
466 }
467 for (DataObjectCollection rel : wrapper.getMetadata().getCollections()) {
468 if (rel.isSavedWithParent()) {
469 Collection<?> collection = (Collection<?>) wrapper.getPropertyValue(rel.getName());
470 if (collection != null) {
471 for (Object element : collection) {
472 clearVersionNumberOnUpdatableObjects(element, visitedObjects);
473 }
474 }
475 }
476 }
477
478 }
479 }
480
481
482
483
484 @Override
485 public boolean handles(final Class<?> type) {
486 if (managedTypesCache == null) {
487 managedTypesCache = new HashSet<Class<?>>();
488
489 Set<ManagedType<?>> managedTypes = sharedEntityManager.getMetamodel().getManagedTypes();
490 for (ManagedType managedType : managedTypes) {
491 managedTypesCache.add(managedType.getJavaType());
492 }
493 }
494
495 if (managedTypesCache.contains(type)) {
496 return true;
497 } else {
498 return false;
499 }
500 }
501
502
503
504
505 @Override
506 @Transactional(readOnly = true)
507 public void flush(final Class<?> type) {
508 doWithExceptionTranslation(new Callable<Object>() {
509 @Override
510 public Object call() {
511 sharedEntityManager.flush();
512
513
514
515
516
517
518
519
520
521 return null;
522 }
523 });
524 }
525
526
527
528
529
530
531 protected void verifyDataObjectWritable(Object dataObject) {
532 DataObjectMetadata metaData = dataObjectService.getMetadataRepository().getMetadata(dataObject.getClass());
533 if (metaData == null) {
534 throw new IllegalArgumentException("Given data object class is not loaded into the MetadataRepository: " + dataObject.getClass());
535 }
536 if (metaData.isReadOnly()) {
537 throw new UnsupportedOperationException(dataObject.getClass() + " is read-only");
538 }
539 }
540
541
542
543
544
545
546
547
548
549
550 protected <T> T doWithExceptionTranslation(Callable<T> callable) {
551 try {
552 return callable.call();
553 }
554 catch (RuntimeException ex) {
555 throw DataAccessUtils.translateIfNecessary(ex, this.persistenceExceptionTranslator);
556 } catch (Exception ex) {
557
558 throw new RiceRuntimeException("Unexpected checked exception during data access.", ex);
559 }
560 }
561
562
563
564
565 private static final class DefaultPersistenceExceptionTranslator implements PersistenceExceptionTranslator {
566
567
568
569
570 @Override
571 public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
572 return EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(ex);
573 }
574
575 }
576
577 }