001 /** 002 * Copyright 2005-2014 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.service.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 }