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    }