001 /**
002 * Copyright 2005-2011 The Kuali Foundation
003 *
004 * Licensed under the Educational Community 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
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.krad.dao.impl;
017
018 import java.util.ArrayList;
019 import java.util.Collection;
020 import java.util.Iterator;
021 import java.util.List;
022 import java.util.Map;
023
024 import javax.persistence.EntityManager;
025 import javax.persistence.EntityNotFoundException;
026 import javax.persistence.PersistenceContext;
027 import javax.persistence.PersistenceException;
028
029 import org.hibernate.FlushMode;
030 import org.hibernate.Session;
031 import org.hibernate.ejb.HibernateEntityManager;
032 import org.hibernate.proxy.HibernateProxy;
033 import org.kuali.rice.core.framework.persistence.jpa.OrmUtils;
034 import org.kuali.rice.core.framework.persistence.jpa.criteria.Criteria;
035 import org.kuali.rice.core.framework.persistence.jpa.criteria.QueryByCriteria;
036 import org.kuali.rice.core.framework.persistence.jpa.criteria.QueryByCriteria.QueryByCriteriaType;
037 import org.kuali.rice.core.framework.persistence.jpa.metadata.MetadataManager;
038 import org.kuali.rice.krad.bo.BusinessObject;
039 import org.kuali.rice.krad.bo.PersistableBusinessObject;
040 import org.kuali.rice.krad.bo.PersistableBusinessObjectExtension;
041 import org.kuali.rice.krad.dao.BusinessObjectDao;
042 import org.kuali.rice.krad.service.PersistenceStructureService;
043 import org.kuali.rice.krad.util.KRADPropertyConstants;
044 import org.kuali.rice.krad.util.OjbCollectionHelper;
045 import org.springframework.dao.DataAccessException;
046
047 /**
048 * This class is the JPA implementation of the BusinessObjectDao interface.
049 */
050 @SuppressWarnings("unchecked")
051 public class BusinessObjectDaoJpa implements BusinessObjectDao {
052
053 @PersistenceContext
054 private EntityManager entityManager;
055
056 private PersistenceStructureService persistenceStructureService;
057
058 private OjbCollectionHelper ojbCollectionHelper;
059
060 public BusinessObjectDaoJpa(EntityManager entityManager, PersistenceStructureService persistenceStructureService) {
061 this.entityManager = entityManager;
062 this.persistenceStructureService = persistenceStructureService;
063 }
064
065 /**
066 * @see org.kuali.rice.krad.dao.BusinessObjectDao#findBySinglePrimaryKey(java.lang.Class, java.lang.Object)
067 */
068 public <T extends BusinessObject> T findBySinglePrimaryKey(Class<T> clazz, Object primaryKey) {
069 return (T) entityManager.find(clazz, primaryKey);
070 }
071
072 /**
073 * @see org.kuali.rice.krad.dao.BusinessObjectDao#findByPrimaryKey(java.lang.Class,
074 * java.util.Map)
075 */
076 public <T extends BusinessObject> T findByPrimaryKey(Class<T> clazz, Map<String, ?> primaryKeys) {
077 if (primaryKeys == null || primaryKeys.isEmpty()) {
078 return null;
079 }
080 T bo = null;
081 try {
082 bo = (T) new QueryByCriteria(entityManager, buildJpaCriteria(clazz, primaryKeys)).toQuery().getSingleResult();
083 } catch (PersistenceException e) {}
084 return bo;
085 }
086
087 /**
088 * Retrieves an object, based on its PK object
089 *
090 * @param clazz the class of the object to retrieve
091 * @param pkObject the value of the primary key
092 * @return the retrieved PersistableBusinessObject
093 */
094 public <T extends BusinessObject> T findByPrimaryKeyUsingKeyObject(Class<T> clazz, Object pkObject) {
095 if (pkObject == null) {
096 return null;
097 }
098 T bo = null;
099 try {
100 bo = (T) entityManager.find(clazz, pkObject);
101 } catch (PersistenceException e) {}
102 return bo;
103 }
104
105 /**
106 * Retrieves all of the records for a given class name.
107 *
108 * @param clazz -
109 * the name of the object being used, either KualiCodeBase or a
110 * subclass
111 * @return Collection
112 * @see org.kuali.rice.krad.dao.BusinessObjectDao#findAll(java.lang.Class)
113 */
114 public <T extends BusinessObject> Collection<T> findAll(Class<T> clazz) {
115 return (Collection<T>) new QueryByCriteria(entityManager, new Criteria(clazz.getName())).toQuery().getResultList();
116 }
117
118 /**
119 * @see org.kuali.rice.krad.dao.BusinessObjectDao#findAllOrderBy(java.lang.Class,
120 * java.lang.String, boolean)
121 */
122 public <T extends BusinessObject> Collection<T> findAllOrderBy(Class<T> clazz, String sortField, boolean sortAscending) {
123 Criteria criteria = new Criteria(clazz.getName());
124 criteria.orderBy(sortField, sortAscending);
125 return new QueryByCriteria(entityManager, criteria).toQuery().getResultList();
126 }
127
128 /**
129 * This is the default impl that comes with Kuali - uses OJB.
130 *
131 * @see org.kuali.rice.krad.dao.BusinessObjectDao#findMatching(java.lang.Class,
132 * java.util.Map)
133 */
134 public <T extends BusinessObject> Collection<T> findMatching(Class<T> clazz, Map<String, ?> fieldValues) {
135 return (Collection<T>)new QueryByCriteria(entityManager, buildJpaCriteria(clazz, fieldValues)).toQuery().getResultList();
136 }
137
138 /**
139 * Uses the passed query to form a Rice QueryByCriteria, which then translates to a JPA query and retrieves results
140 * @see org.kuali.rice.krad.dao.BusinessObjectDao#findMatching(org.kuali.rice.core.framework.persistence.jpa.criteria.Criteria)
141 */
142 public <T extends BusinessObject> Collection<T> findMatching(Criteria criteria) {
143 return (List<T>)new QueryByCriteria(entityManager, criteria).toQuery().getResultList();
144 }
145
146 /**
147 * @see org.kuali.rice.krad.dao.BusinessObjectDao#findAllActive(java.lang.Class)
148 */
149 public <T extends BusinessObject> Collection<T> findAllActive(Class<T> clazz) {
150 return (Collection<T>)new QueryByCriteria(entityManager, buildActiveJpaCriteria(clazz)).toQuery().getResultList();
151 }
152
153 /**
154 * @see org.kuali.rice.krad.dao.BusinessObjectDao#findAllActive(java.lang.Class)
155 */
156 public <T extends BusinessObject> Collection<T> findAllInactive(Class<T> clazz) {
157 return (Collection<T>)new QueryByCriteria(entityManager, buildInactiveJpaCriteria(clazz)).toQuery().getResultList();
158 }
159
160 /**
161 * @see org.kuali.rice.krad.dao.BusinessObjectDao#findAllActiveOrderBy(java.lang.Class,
162 * java.lang.String, boolean)
163 */
164 public <T extends BusinessObject> Collection<T> findAllActiveOrderBy(Class<T> clazz, String sortField, boolean sortAscending) {
165 Criteria criteria = buildActiveJpaCriteria(clazz);
166 criteria.orderBy(sortField, sortAscending);
167 return (Collection<T>)new QueryByCriteria(entityManager, criteria).toQuery().getResultList();
168 }
169
170 /**
171 * @see org.kuali.rice.krad.dao.BusinessObjectDao#findMatchingActive(java.lang.Class,
172 * java.util.Map)
173 */
174 public <T extends BusinessObject> Collection<T> findMatchingActive(Class<T> clazz, Map<String, ?> fieldValues) {
175 Criteria criteria = buildJpaCriteria(clazz, fieldValues);
176 criteria.and(buildActiveJpaCriteria(clazz));
177 return (Collection<T>)new QueryByCriteria(entityManager, criteria).toQuery().getResultList();
178 }
179
180 /**
181 * This is the default impl that comes with Kuali - uses OJB.
182 *
183 * @see org.kuali.rice.krad.dao.BusinessObjectDao#countMatching(java.lang.Class,
184 * java.util.Map)
185 */
186 public int countMatching(Class clazz, Map<String, ?> fieldValues) {
187 return ((Long) new QueryByCriteria(entityManager, buildJpaCriteria(clazz, fieldValues)).toCountQuery().getSingleResult()).intValue();
188 }
189
190 /**
191 * This is the default impl that comes with Kuali - uses OJB.
192 *
193 * @see org.kuali.rice.krad.dao.BusinessObjectDao#countMatching(java.lang.Class,
194 * java.util.Map, java.util.Map)
195 */
196 public int countMatching(Class clazz, Map<String, ?> positiveFieldValues, Map<String, ?> negativeFieldValues) {
197 Criteria criteria = buildJpaCriteria(clazz, positiveFieldValues);
198 criteria.and(buildNegativeJpaCriteria(clazz, negativeFieldValues));
199 return ((Long) new QueryByCriteria(entityManager, criteria).toCountQuery().getSingleResult()).intValue();
200 }
201
202 /**
203 * This is the default impl that comes with Kuali - uses OJB.
204 *
205 * @see org.kuali.rice.krad.dao.BusinessObjectDao#findMatching(java.lang.Class,
206 * java.util.Map)
207 */
208 public <T extends BusinessObject> Collection<T> findMatchingOrderBy(Class<T> clazz, Map<String, ?> fieldValues, String sortField, boolean sortAscending) {
209 Criteria criteria = buildJpaCriteria(clazz, fieldValues);
210 criteria.orderBy(sortField, sortAscending);
211 return (Collection<T>)new QueryByCriteria(entityManager, criteria).toQuery().getResultList();
212 }
213
214 /**
215 * Saves a business object.
216 *
217 * @see org.kuali.rice.krad.dao.BusinessObjectDao#save(org.kuali.rice.krad.bo.PersistableBusinessObject)
218 */
219 public PersistableBusinessObject save(PersistableBusinessObject bo) throws DataAccessException {
220 /* KC determined this is not needed for JPA
221 // if collections exist on the BO, create a copy and use to process the
222 // collections to ensure
223 // that removed elements are deleted from the database
224 Set<String> boCollections = getPersistenceStructureService().listCollectionObjectTypes(bo.getClass()).keySet();
225 PersistableBusinessObject savedBo = null;
226 if (!boCollections.isEmpty()) {
227 // refresh bo to get db copy of collections
228 savedBo = (PersistableBusinessObject) ObjectUtils.deepCopy(bo);
229 for (String boCollection : boCollections) {
230 if (getPersistenceStructureService().isCollectionUpdatable(savedBo.getClass(), boCollection)) {
231 savedBo.refreshReferenceObject(boCollection);
232 }
233 }
234 }
235 */
236 if (entityManager.contains(bo) && ((HibernateEntityManager)entityManager).getSession().isReadOnly(bo)) {
237 ((HibernateEntityManager)entityManager).getSession().setReadOnly(bo, false); // are we read only? turn that off...
238 }
239 return reattachAndSave(bo);
240 }
241
242
243
244 /**
245 * Saves a business object.
246 *
247 * @see org.kuali.rice.krad.dao.BusinessObjectDao#save(org.kuali.rice.krad.bo.PersistableBusinessObject)
248 */
249 public List<? extends PersistableBusinessObject> save(List businessObjects) throws DataAccessException {
250 List<PersistableBusinessObject> savedBOs = new ArrayList<PersistableBusinessObject>();
251 for (Iterator i = businessObjects.iterator(); i.hasNext();) {
252 Object bo = i.next();
253 final PersistableBusinessObject savedBusinessObject = reattachAndSave((PersistableBusinessObject) bo);
254 savedBOs.add(savedBusinessObject);
255 }
256 return savedBOs;
257 }
258
259 /**
260 * Deletes the business object passed in.
261 *
262 * @param bo
263 * @throws DataAccessException
264 * @see org.kuali.rice.krad.dao.BusinessObjectDao#delete(org.kuali.rice.krad.bo.PersistableBusinessObject)
265 */
266 public void delete(PersistableBusinessObject bo) {
267 final PersistableBusinessObject realPBO = materialize(bo);
268 if (realPBO != null) {
269 if (realPBO.getExtension() != null) {
270 delete(realPBO.getExtension());
271 }
272 if (entityManager.contains(realPBO)) {
273 entityManager.remove(realPBO);
274 } else {
275 final PersistableBusinessObject foundBO = (PersistableBusinessObject)entityManager.find(realPBO.getClass(), MetadataManager.getEntityPrimaryKeyObject(realPBO));
276 if (foundBO != null) {
277 entityManager.remove(foundBO);
278 }
279 }
280 }
281 }
282
283 /**
284 * If the object is a proxy, materializes it
285 *
286 * @param bo the business object, which may be a sneaky proxy
287 * @return the materialized non-proxied business object
288 */
289 protected PersistableBusinessObject materialize(PersistableBusinessObject bo) {
290 try {
291 if (bo instanceof HibernateProxy) {
292 return (PersistableBusinessObject)((HibernateProxy)bo).getHibernateLazyInitializer().getImplementation();
293 }
294 return bo;
295 } catch (EntityNotFoundException enfe) {
296 return null; // could not find the entity - just return null
297 }
298 }
299
300 /**
301 * @see org.kuali.rice.krad.dao.BusinessObjectDao#delete(java.util.List)
302 */
303 public void delete(List<? extends PersistableBusinessObject> boList) {
304 for (PersistableBusinessObject bo : boList) {
305 delete(bo);
306 }
307 }
308
309 /**
310 * @see org.kuali.rice.krad.dao.BusinessObjectDao#deleteMatching(java.lang.Class,
311 * java.util.Map)
312 */
313 public void deleteMatching(Class clazz, Map<String, ?> fieldValues) {
314 // Rice JPA MetadataManager
315 new QueryByCriteria(entityManager, buildJpaCriteria(clazz, fieldValues), QueryByCriteriaType.DELETE).toQuery().executeUpdate();
316 }
317
318 /**
319 * @see org.kuali.rice.krad.dao.BusinessObjectDao#retrieve(org.kuali.rice.krad.bo.PersistableBusinessObject)
320 */
321 public PersistableBusinessObject retrieve(PersistableBusinessObject object) {
322 PersistableBusinessObject pbo = null;
323 Object pkObject = MetadataManager.getEntityPrimaryKeyObject(object);
324 if (pkObject != null) {
325 pbo = (PersistableBusinessObject) entityManager.find(object.getClass(), pkObject);
326 if (pbo != null && pbo.getExtension() != null) {
327 pbo.setExtension((PersistableBusinessObjectExtension) entityManager.find(pbo.getExtension().getClass(), MetadataManager.getPersistableBusinessObjectPrimaryKeyObjectWithValuesForExtension(pbo, pbo.getExtension())));
328 }
329 }
330 return pbo;
331 }
332
333 private Criteria buildJpaCriteria(Class clazz, Map<String, ?> fieldValues) {
334 Criteria criteria = new Criteria(clazz.getName());
335 for (Iterator i = fieldValues.entrySet().iterator(); i.hasNext();) {
336 Map.Entry<String, ?> e = (Map.Entry<String, ?>) i.next();
337
338 String key = e.getKey();
339 Object value = e.getValue();
340 String alias = "";
341 String[] keySplit = key.split("\\.");
342 if (keySplit.length > 1) {
343 alias = keySplit[keySplit.length-2];
344 String variableKey = keySplit[keySplit.length-1];
345 for (int j = 0; j < keySplit.length - 1; j++) {
346 if (criteria.getAliasIndex(keySplit[j]) == -1) {
347 criteria.join(keySplit[j], keySplit[j], false, true);
348 }
349 }
350 key = "__JPA_ALIAS[['" + alias + "']]__." + variableKey;
351 }
352 if (value == null) {
353 continue;
354 } else if (value instanceof Collection) {
355 criteria.in(key, (Collection)value);
356 } else {
357 criteria.eq(key, value);
358 }
359 }
360 return criteria;
361 }
362
363 private Criteria buildActiveJpaCriteria(Class clazz) {
364 Criteria criteria = new Criteria(clazz.getName());
365 criteria.eq(KRADPropertyConstants.ACTIVE, true);
366 return criteria;
367 }
368
369 private Criteria buildInactiveJpaCriteria(Class clazz) {
370 Criteria criteria = new Criteria(clazz.getName());
371 criteria.eq(KRADPropertyConstants.ACTIVE, false);
372 return criteria;
373 }
374
375 private Criteria buildNegativeJpaCriteria(Class clazz, Map negativeFieldValues) {
376 Criteria criteria = new Criteria(clazz.getName());
377 for (Iterator i = negativeFieldValues.entrySet().iterator(); i.hasNext();) {
378 Map.Entry e = (Map.Entry) i.next();
379
380 String key = (String) e.getKey();
381 Object value = e.getValue();
382 if (value instanceof Collection) {
383 criteria.notIn(key, (List) value);
384 } else {
385 criteria.ne(key, value);
386 }
387 }
388
389 return criteria;
390 }
391
392 /**
393 * @see org.kuali.rice.krad.dao.BusinessObjectDao#manageReadOnly(org.kuali.rice.krad.bo.PersistableBusinessObject)
394 */
395 public PersistableBusinessObject manageReadOnly(PersistableBusinessObject bo) {
396 Session session = ((HibernateEntityManager)entityManager).getSession();
397 FlushMode currentFlushMode = session.getFlushMode();
398 session.setFlushMode(FlushMode.MANUAL); // make sure the merge doesn't flush what we're trying to make read only
399 PersistableBusinessObject managedBO = entityManager.merge(bo);
400 session.setReadOnly(managedBO, true);
401 session.setFlushMode(currentFlushMode);
402 return managedBO;
403 }
404
405 /**
406 * Gets the persistenceStructureService attribute.
407 *
408 * @return Returns the persistenceStructureService.
409 */
410 protected PersistenceStructureService getPersistenceStructureService() {
411 return persistenceStructureService;
412 }
413
414 /**
415 * Sets the persistenceStructureService attribute value.
416 *
417 * @param persistenceStructureService
418 * The persistenceStructureService to set.
419 */
420 public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
421 this.persistenceStructureService = persistenceStructureService;
422 }
423
424 private PersistableBusinessObject reattachAndSave(PersistableBusinessObject bo) {
425 PersistableBusinessObject attachedBo = findByPrimaryKey(bo.getClass(), MetadataManager.getEntityPrimaryKeyValuePairs(bo));
426 PersistableBusinessObject newBo = attachedBo;
427 if (attachedBo == null) {
428 newBo = entityManager.merge(bo);
429 if (bo.getExtension() != null) {
430 entityManager.merge(bo.getExtension());
431 }
432 } else {
433 /*if (bo.getExtension() != null) {
434 PersistableBusinessObject attachedBoe = findByPrimaryKey(bo.getExtension().getClass(), MetadataManager.getEntityPrimaryKeyValuePairs(bo.getExtension()));
435 OrmUtils.reattach(bo.getExtension(),attachedBoe);
436 attachedBo.setExtension((PersistableBusinessObjectExtension) attachedBoe);
437 entityManager.merge(attachedBoe);
438 }*/
439 OrmUtils.reattach(bo, attachedBo);
440 newBo = entityManager.merge(attachedBo);
441 }
442 return newBo;
443 }
444
445 /**
446 * @return the entityManager
447 */
448 public EntityManager getEntityManager() {
449 return this.entityManager;
450 }
451
452 /**
453 * @param entityManager the entityManager to set
454 */
455 public void setEntityManager(EntityManager entityManager) {
456 this.entityManager = entityManager;
457 }
458
459 }