View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.dao.impl;
17  
18  import java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.Map;
23  
24  import javax.persistence.EntityManager;
25  import javax.persistence.EntityNotFoundException;
26  import javax.persistence.PersistenceContext;
27  import javax.persistence.PersistenceException;
28  
29  import org.hibernate.FlushMode;
30  import org.hibernate.Session;
31  import org.hibernate.ejb.HibernateEntityManager;
32  import org.hibernate.proxy.HibernateProxy;
33  import org.kuali.rice.core.framework.persistence.jpa.OrmUtils;
34  import org.kuali.rice.core.framework.persistence.jpa.criteria.Criteria;
35  import org.kuali.rice.core.framework.persistence.jpa.criteria.QueryByCriteria;
36  import org.kuali.rice.core.framework.persistence.jpa.criteria.QueryByCriteria.QueryByCriteriaType;
37  import org.kuali.rice.core.framework.persistence.jpa.metadata.MetadataManager;
38  import org.kuali.rice.krad.bo.BusinessObject;
39  import org.kuali.rice.krad.bo.PersistableBusinessObject;
40  import org.kuali.rice.krad.bo.PersistableBusinessObjectExtension;
41  import org.kuali.rice.krad.dao.BusinessObjectDao;
42  import org.kuali.rice.krad.service.PersistenceStructureService;
43  import org.kuali.rice.krad.util.KRADPropertyConstants;
44  import org.kuali.rice.krad.util.OjbCollectionHelper;
45  import org.springframework.dao.DataAccessException;
46  
47  /**
48   * This class is the JPA implementation of the BusinessObjectDao interface.
49   */
50  @SuppressWarnings("unchecked")
51  public class BusinessObjectDaoJpa implements BusinessObjectDao {
52  
53  	@PersistenceContext
54  	private EntityManager entityManager;
55  
56  	private PersistenceStructureService persistenceStructureService;
57  
58  	private OjbCollectionHelper ojbCollectionHelper;
59  
60  	public BusinessObjectDaoJpa(EntityManager entityManager, PersistenceStructureService persistenceStructureService) {
61  		this.entityManager = entityManager;
62  		this.persistenceStructureService = persistenceStructureService;
63  	}
64  
65  	/**
66  	 * @see org.kuali.rice.krad.dao.BusinessObjectDao#findBySinglePrimaryKey(java.lang.Class, java.lang.Object)
67  	 */
68  	public <T extends BusinessObject> T findBySinglePrimaryKey(Class<T> clazz, Object primaryKey) {
69  		return (T) entityManager.find(clazz, primaryKey);
70  	}
71  
72  	/**
73  	 * @see org.kuali.rice.krad.dao.BusinessObjectDao#findByPrimaryKey(java.lang.Class,
74  	 *      java.util.Map)
75  	 */
76  	public <T extends BusinessObject> T findByPrimaryKey(Class<T> clazz, Map<String, ?> primaryKeys) { 
77  		if (primaryKeys == null || primaryKeys.isEmpty()) {
78  			return null;
79  		}
80  		T bo = null;
81  		try {
82  			bo = (T) new QueryByCriteria(entityManager, buildJpaCriteria(clazz, primaryKeys)).toQuery().getSingleResult();
83  		} catch (PersistenceException e) {}
84  		return bo;
85  	}
86  	
87  	/**
88  	 * Retrieves an object, based on its PK object
89  	 * 
90  	 * @param clazz the class of the object to retrieve
91  	 * @param pkObject the value of the primary key
92  	 * @return the retrieved PersistableBusinessObject
93  	 */
94  	public <T extends BusinessObject> T findByPrimaryKeyUsingKeyObject(Class<T> clazz, Object pkObject) { 
95  		if (pkObject == null) {
96  			return null;
97  		}
98  		T bo = null;
99  		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 }