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 com.google.common.collect.Sets;
19 import org.eclipse.persistence.jpa.JpaEntityManager;
20 import org.kuali.rice.core.api.config.property.ConfigContext;
21 import org.kuali.rice.core.api.criteria.QueryByCriteria;
22 import org.kuali.rice.core.api.criteria.QueryResults;
23 import org.kuali.rice.core.api.exception.RiceRuntimeException;
24 import org.kuali.rice.krad.data.CompoundKey;
25 import org.kuali.rice.krad.data.DataObjectService;
26 import org.kuali.rice.krad.data.PersistenceOption;
27 import org.kuali.rice.krad.data.metadata.DataObjectMetadata;
28 import org.kuali.rice.krad.data.provider.PersistenceProvider;
29 import org.springframework.beans.BeansException;
30 import org.springframework.beans.factory.BeanFactory;
31 import org.springframework.beans.factory.BeanFactoryAware;
32 import org.springframework.beans.factory.BeanFactoryUtils;
33 import org.springframework.beans.factory.ListableBeanFactory;
34 import org.springframework.dao.DataAccessException;
35 import org.springframework.dao.support.ChainedPersistenceExceptionTranslator;
36 import org.springframework.dao.support.DataAccessUtils;
37 import org.springframework.dao.support.PersistenceExceptionTranslator;
38 import org.springframework.orm.jpa.EntityManagerFactoryUtils;
39 import org.springframework.transaction.annotation.Transactional;
40
41 import javax.persistence.EntityManager;
42 import javax.persistence.NonUniqueResultException;
43 import javax.persistence.metamodel.ManagedType;
44 import java.util.Map;
45 import java.util.Set;
46 import java.util.concurrent.Callable;
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 @Transactional
70 public class JpaPersistenceProvider implements PersistenceProvider, BeanFactoryAware {
71
72 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(JpaPersistenceProvider.class);
73
74
75
76
77
78
79
80
81 public static final String AUTO_FLUSH = "rice.krad.data.jpa.autoFlush";
82
83 private EntityManager sharedEntityManager;
84 private DataObjectService dataObjectService;
85
86 private PersistenceExceptionTranslator persistenceExceptionTranslator;
87
88
89
90
91 private static final class LazyConfigHolder {
92 private static final boolean autoFlush = ConfigContext.getCurrentContextConfig().getBooleanProperty(AUTO_FLUSH, false);
93 }
94
95
96
97
98
99
100 public EntityManager getSharedEntityManager() {
101 return sharedEntityManager;
102 }
103
104
105
106
107
108
109 public void setSharedEntityManager(EntityManager sharedEntityManager) {
110 this.sharedEntityManager = sharedEntityManager;
111 }
112
113
114
115
116
117
118 public void setDataObjectService(DataObjectService dataObjectService) {
119 this.dataObjectService = dataObjectService;
120 }
121
122
123
124
125
126
127 public DataObjectService getDataObjectService() {
128 return this.dataObjectService;
129 }
130
131
132
133
134 @Override
135 public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
136 if (!(beanFactory instanceof ListableBeanFactory)) {
137 throw new IllegalArgumentException(
138 "Cannot use PersistenceExceptionTranslator autodetection without ListableBeanFactory");
139 }
140 this.persistenceExceptionTranslator = detectPersistenceExceptionTranslators((ListableBeanFactory)beanFactory);
141 }
142
143
144
145
146
147
148
149
150 protected PersistenceExceptionTranslator detectPersistenceExceptionTranslators(ListableBeanFactory beanFactory) {
151
152 Map<String, PersistenceExceptionTranslator> pets = BeanFactoryUtils.beansOfTypeIncludingAncestors(beanFactory,
153 PersistenceExceptionTranslator.class, false, false);
154 ChainedPersistenceExceptionTranslator cpet = new ChainedPersistenceExceptionTranslator();
155 for (PersistenceExceptionTranslator pet : pets.values()) {
156 cpet.addDelegate(pet);
157 }
158
159 cpet.addDelegate(new DefaultPersistenceExceptionTranslator());
160 return cpet;
161 }
162
163
164
165
166 @Override
167 public <T> T save(final T dataObject, final PersistenceOption... options) {
168 return doWithExceptionTranslation(new Callable<T>() {
169 @Override
170 public T call() {
171 verifyDataObjectWritable(dataObject);
172
173 Set<PersistenceOption> optionSet = Sets.newHashSet(options);
174
175 T mergedDataObject = sharedEntityManager.merge(dataObject);
176
177
178
179
180
181
182
183 if(optionSet.contains(PersistenceOption.FLUSH) || optionSet.contains(PersistenceOption.LINK_KEYS) ||
184 LazyConfigHolder.autoFlush){
185 sharedEntityManager.flush();
186 }
187
188 return mergedDataObject;
189 }
190 });
191 }
192
193
194
195
196 @Override
197 public <T> T find(final Class<T> type, final Object id) {
198 return doWithExceptionTranslation(new Callable<T>() {
199 @Override
200 public T call() {
201 if (id instanceof CompoundKey) {
202 QueryResults<T> results = findMatching(type,
203 QueryByCriteria.Builder.andAttributes(((CompoundKey) id).getKeys()).build());
204 if (results.getResults().size() > 1) {
205 throw new NonUniqueResultException("Error Compound Key: " + id + " on class " + type.getName()
206 + " returned more than one row.");
207 }
208 if (!results.getResults().isEmpty()) {
209 return results.getResults().get(0);
210 }
211 return null;
212 } else {
213 return sharedEntityManager.find(type, id);
214 }
215 }
216 });
217 }
218
219
220
221
222 @Override
223 public <T> QueryResults<T> findMatching(final Class<T> type, final QueryByCriteria queryByCriteria) {
224 return doWithExceptionTranslation(new Callable<QueryResults<T>>() {
225 @Override
226 public QueryResults<T> call() {
227 return new JpaCriteriaQuery(sharedEntityManager).lookup(type, queryByCriteria);
228 }
229 });
230 }
231
232
233
234
235 @Override
236 public <T> QueryResults<T> findAll(final Class<T> type) {
237 return doWithExceptionTranslation(new Callable<QueryResults<T>>() {
238 @Override
239 public QueryResults<T> call() {
240 return new JpaCriteriaQuery(getSharedEntityManager()).lookup(type, QueryByCriteria.Builder.create().build());
241 }
242 });
243 }
244
245
246
247
248 @Override
249 public void delete(final Object dataObject) {
250 doWithExceptionTranslation(new Callable<Object>() {
251 @Override
252 public Object call() {
253 verifyDataObjectWritable(dataObject);
254 sharedEntityManager.remove(sharedEntityManager.merge(dataObject));
255 return null;
256 }
257 });
258 }
259
260
261
262
263 @Override
264 public <T> void deleteMatching(final Class<T> type, final QueryByCriteria queryByCriteria) {
265 doWithExceptionTranslation(new Callable<Object>() {
266 @Override
267 public Object call() {
268 new JpaCriteriaQuery(getSharedEntityManager()).deleteMatching(type, queryByCriteria);
269 return null;
270 }
271 });
272 }
273
274
275
276
277 @Override
278 public <T> void deleteAll(final Class<T> type) {
279 doWithExceptionTranslation(new Callable<Object>() {
280 @Override
281 public Object call() {
282 new JpaCriteriaQuery(getSharedEntityManager()).deleteAll(type);
283 return null;
284 }
285 });
286 }
287
288
289
290
291 @Override
292 public <T> T copyInstance(final T dataObject) {
293 return doWithExceptionTranslation(new Callable<T>() {
294 @Override
295 public T call() {
296 return (T) sharedEntityManager.unwrap(JpaEntityManager.class).getDatabaseSession().copy(dataObject);
297 }
298 });
299 }
300
301
302
303
304 @Override
305 public boolean handles(final Class<?> type) {
306 return doWithExceptionTranslation(new Callable<Boolean>() {
307 @Override
308 public Boolean call() {
309 try {
310 ManagedType<?> managedType = sharedEntityManager.getMetamodel().managedType(type);
311 return Boolean.valueOf(managedType != null);
312 } catch (IllegalArgumentException iae) {
313 return Boolean.FALSE;
314 } catch (IllegalStateException ex) {
315
316 LOG.warn("sharedEntityManager " + sharedEntityManager + " is not in a state to be used: "
317 + ex.getMessage());
318 return Boolean.FALSE;
319 }
320 }
321 }).booleanValue();
322 }
323
324
325
326
327 @Override
328 public void flush(final Class<?> type) {
329 doWithExceptionTranslation(new Callable<Object>() {
330 @Override
331 public Object call() {
332 sharedEntityManager.flush();
333 return null;
334 }
335 });
336 }
337
338
339
340
341
342
343 protected void verifyDataObjectWritable(Object dataObject) {
344 DataObjectMetadata metaData = dataObjectService.getMetadataRepository().getMetadata(dataObject.getClass());
345 if (metaData == null) {
346 throw new IllegalArgumentException("Given data object class is not loaded into the MetadataRepository: " + dataObject.getClass());
347 }
348 if (metaData.isReadOnly()) {
349 throw new UnsupportedOperationException(dataObject.getClass() + " is read-only");
350 }
351 }
352
353
354
355
356
357
358
359
360
361
362 protected <T> T doWithExceptionTranslation(Callable<T> callable) {
363 try {
364 return callable.call();
365 }
366 catch (RuntimeException ex) {
367 throw DataAccessUtils.translateIfNecessary(ex, this.persistenceExceptionTranslator);
368 } catch (Exception ex) {
369
370 throw new RiceRuntimeException("Unexpected checked exception during data access.", ex);
371 }
372 }
373
374
375
376
377 private static final class DefaultPersistenceExceptionTranslator implements PersistenceExceptionTranslator {
378
379
380
381
382 @Override
383 public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
384 return EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(ex);
385 }
386
387 }
388
389 }