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 org.apache.commons.lang.StringUtils;
19  import org.apache.ojb.broker.query.Criteria;
20  import org.apache.ojb.broker.query.QueryByCriteria;
21  import org.apache.ojb.broker.query.QueryFactory;
22  import org.kuali.rice.core.framework.persistence.ojb.dao.PlatformAwareDaoBaseOjb;
23  import org.kuali.rice.krad.bo.BusinessObject;
24  import org.kuali.rice.krad.bo.PersistableBusinessObject;
25  import org.kuali.rice.krad.dao.BusinessObjectDao;
26  import org.kuali.rice.krad.service.KRADServiceLocatorInternal;
27  import org.kuali.rice.krad.service.PersistenceStructureService;
28  import org.kuali.rice.krad.service.util.OjbCollectionHelper;
29  import org.kuali.rice.krad.util.KRADPropertyConstants;
30  import org.kuali.rice.krad.util.ObjectUtils;
31  import org.kuali.rice.krad.service.util.OjbCollectionAware;
32  import org.springframework.dao.DataAccessException;
33  
34  import java.lang.reflect.InvocationTargetException;
35  import java.util.Collection;
36  import java.util.HashMap;
37  import java.util.Iterator;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.Set;
41  
42  /**
43   * OJB implementation of the BusinessObjectDao interface and should be used for generic business object unit
44   * tests
45   *
46   * @author Kuali Rice Team (rice.collab@kuali.org)
47   */
48  public class BusinessObjectDaoOjb extends PlatformAwareDaoBaseOjb implements BusinessObjectDao, OjbCollectionAware {
49      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BusinessObjectDaoOjb.class);
50  
51      private PersistenceStructureService persistenceStructureService;
52      private OjbCollectionHelper ojbCollectionHelper;
53  
54      /**
55  	 * This constructs a {@link BusinessObjectDaoOjb}
56  	 */
57  	public BusinessObjectDaoOjb(PersistenceStructureService persistenceStructureService) {
58  		this.persistenceStructureService = persistenceStructureService;
59  	}
60  
61      /**
62  	 * @see org.kuali.rice.krad.dao.BusinessObjectDao#findBySinglePrimaryKey(java.lang.Class, java.lang.Object)
63  	 */
64  	public <T extends BusinessObject> T findBySinglePrimaryKey(Class<T> clazz, Object primaryKey) {
65  		if (primaryKey.getClass().getName().startsWith("java.lang.")
66                  || primaryKey.getClass().getName().startsWith("java.sql.")
67                  || primaryKey.getClass().getName().startsWith("java.math.")
68                  || primaryKey.getClass().getName().startsWith("java.util.")) {
69  			try {
70  				return (T) getPersistenceBrokerTemplate().getObjectById(clazz, primaryKey);
71  			} catch ( DataAccessException ex ) {
72  	    		// it doesn't exist, just return null
73  				return null;
74  			}
75  		} else {
76  			Criteria criteria = buildCriteria(clazz, primaryKey);
77  
78  	        return (T) getPersistenceBrokerTemplate().getObjectByQuery(QueryFactory.newQuery(clazz, criteria));
79  		}
80  	}
81  
82      /**
83       * @see org.kuali.rice.krad.dao.BusinessObjectDao#findByPrimaryKey(java.lang.Class, java.util.Map)
84       */
85      public <T extends BusinessObject> T findByPrimaryKey(Class<T> clazz, Map<String, ?> primaryKeys) {
86          Criteria criteria = buildCriteria(primaryKeys);
87  
88          return (T) getPersistenceBrokerTemplate().getObjectByQuery(QueryFactory.newQuery(clazz, criteria));
89      }
90  
91      /**
92       * Retrieves all of the records for a given class name.
93       *
94       * @param clazz - the name of the object being used, either KualiCodeBase or a subclass
95       * @return Collection
96       * @see org.kuali.rice.krad.dao.BusinessObjectDao#findAll(java.lang.Class)
97       */
98      public <T extends BusinessObject> Collection<T> findAll(Class<T> clazz) {
99          return (Collection<T>)getPersistenceBrokerTemplate().getCollectionByQuery(QueryFactory.newQuery(clazz, (Criteria) null));
100     }
101 
102     /**
103      * @see org.kuali.rice.krad.dao.BusinessObjectDao#findAllOrderBy(java.lang.Class, java.lang.String, boolean)
104      */
105     public <T extends BusinessObject> Collection<T> findAllOrderBy(Class<T> clazz, String sortField, boolean sortAscending) {
106         QueryByCriteria queryByCriteria = new QueryByCriteria(clazz, (Criteria) null);
107 
108         if (sortAscending) {
109             queryByCriteria.addOrderByAscending(sortField);
110         }
111         else {
112             queryByCriteria.addOrderByDescending(sortField);
113         }
114 
115         return (Collection<T>)getPersistenceBrokerTemplate().getCollectionByQuery(queryByCriteria);
116     }
117 
118     /**
119      * This is the default impl that comes with Kuali - uses OJB.
120      *
121      * @see org.kuali.rice.krad.dao.BusinessObjectDao#findMatching(java.lang.Class, java.util.Map)
122      */
123     public <T extends BusinessObject> Collection<T> findMatching(Class<T> clazz, Map<String, ?> fieldValues) {
124         Criteria criteria = buildCriteria(fieldValues);
125 
126         return (Collection<T>)getPersistenceBrokerTemplate().getCollectionByQuery(QueryFactory.newQuery(clazz, criteria));
127     }
128 
129 
130     /**
131 	 * Throws an UnsupportedOperationException
132 	 * @see org.kuali.rice.krad.dao.BusinessObjectDao#findMatching(org.kuali.rice.core.framework.persistence.jpa.criteria.Criteria)
133 	 */
134 	//public <T extends BusinessObject> Collection<T> findMatching(org.kuali.rice.core.jpa.criteria.Criteria criteria) {
135 	//	throw new UnsupportedOperationException("OJB does not support finding matching business objects using JPA criteria");
136 	//}
137 
138 	/**
139      * @see org.kuali.rice.krad.dao.BusinessObjectDao#findAllActive(java.lang.Class)
140      */
141     public <T extends BusinessObject> Collection<T> findAllActive(Class<T> clazz) {
142         return (Collection<T>)getPersistenceBrokerTemplate().getCollectionByQuery(QueryFactory.newQuery(clazz, buildActiveCriteria()));
143     }
144 
145     /**
146      * @see org.kuali.rice.krad.dao.BusinessObjectDao#findAllActive(java.lang.Class)
147      */
148     public <T extends BusinessObject> Collection<T> findAllInactive(Class<T> clazz) {
149         return (Collection<T>)getPersistenceBrokerTemplate().getCollectionByQuery(QueryFactory.newQuery(clazz, buildInactiveCriteria()));
150     }
151 
152     /**
153      * @see org.kuali.rice.krad.dao.BusinessObjectDao#findAllActiveOrderBy(java.lang.Class, java.lang.String, boolean)
154      */
155     public <T extends BusinessObject> Collection<T> findAllActiveOrderBy(Class<T> clazz, String sortField, boolean sortAscending) {
156         QueryByCriteria queryByCriteria = new QueryByCriteria(clazz, buildActiveCriteria());
157 
158         if (sortAscending) {
159             queryByCriteria.addOrderByAscending(sortField);
160         }
161         else {
162             queryByCriteria.addOrderByDescending(sortField);
163         }
164 
165         return (Collection<T>)getPersistenceBrokerTemplate().getCollectionByQuery(queryByCriteria);
166     }
167 
168     /**
169      * @see org.kuali.rice.krad.dao.BusinessObjectDao#findMatchingActive(java.lang.Class, java.util.Map)
170      */
171     public <T extends BusinessObject> Collection<T> findMatchingActive(Class<T> clazz, Map<String, ?> fieldValues) {
172         Criteria criteria = buildCriteria(fieldValues);
173         criteria.addAndCriteria(buildActiveCriteria());
174 
175         return (Collection<T>)getPersistenceBrokerTemplate().getCollectionByQuery(QueryFactory.newQuery(clazz, criteria));
176     }
177 
178     /**
179      * This is the default impl that comes with Kuali - uses OJB.
180      *
181      * @see org.kuali.rice.krad.dao.BusinessObjectDao#countMatching(java.lang.Class, java.util.Map)
182      */
183     public int countMatching(Class clazz, Map<String, ?> fieldValues) {
184         Criteria criteria = buildCriteria(fieldValues);
185 
186         return getPersistenceBrokerTemplate().getCount(QueryFactory.newQuery(clazz, criteria));
187     }
188 
189     /**
190      * This is the default impl that comes with Kuali - uses OJB.
191      *
192      * @see org.kuali.rice.krad.dao.BusinessObjectDao#countMatching(java.lang.Class, java.util.Map, java.util.Map)
193      */
194     public int countMatching(Class clazz, Map<String, ?> positiveFieldValues, Map<String, ?> negativeFieldValues) {
195         Criteria criteria = buildCriteria(positiveFieldValues);
196         Criteria negativeCriteria = buildNegativeCriteria(negativeFieldValues);
197         criteria.addAndCriteria(negativeCriteria);
198         return getPersistenceBrokerTemplate().getCount(QueryFactory.newQuery(clazz, criteria));
199     }
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, java.util.Map)
206      */
207     public <T extends BusinessObject> Collection<T> findMatchingOrderBy(Class<T> clazz, Map<String, ?> fieldValues, String sortField, boolean sortAscending) {
208         Criteria criteria = buildCriteria(fieldValues);
209         QueryByCriteria queryByCriteria = new QueryByCriteria(clazz, criteria);
210 
211         if (sortAscending) {
212             queryByCriteria.addOrderByAscending(sortField);
213         }
214         else {
215             queryByCriteria.addOrderByDescending(sortField);
216         }
217 
218         return (Collection<T>)getPersistenceBrokerTemplate().getCollectionByQuery(queryByCriteria);
219     }
220 
221 	/**
222 	 * Saves a business object.
223 	 *
224 	 * @see org.kuali.rice.krad.dao.BusinessObjectDao#save(org.kuali.rice.krad.bo.PersistableBusinessObject)
225 	 */
226 	public PersistableBusinessObject save(PersistableBusinessObject bo) throws DataAccessException {
227 		// if collections exist on the BO, create a copy and use to process the
228 		// collections to ensure
229 		// that removed elements are deleted from the database
230 		Set<String> boCollections = getPersistenceStructureService().listCollectionObjectTypes(bo.getClass()).keySet();
231 		PersistableBusinessObject savedBo = null;
232 		if (!boCollections.isEmpty()) {
233 			// refresh bo to get db copy of collections
234 			savedBo = (PersistableBusinessObject) ObjectUtils.deepCopy(bo);
235 			for (String boCollection : boCollections) {
236 				if (getPersistenceStructureService().isCollectionUpdatable(savedBo.getClass(), boCollection)) {
237 					savedBo.refreshReferenceObject(boCollection);
238 				}
239 			}
240             getOjbCollectionHelper().processCollections(this, bo, savedBo);
241         }
242 
243 		getPersistenceBrokerTemplate().store(bo);
244 		return bo;
245 	}
246 
247     /**
248      * Saves a business object.
249      *
250      * @see org.kuali.rice.krad.dao.BusinessObjectDao#save(org.kuali.rice.krad.bo.PersistableBusinessObject)
251      */
252     public List<? extends PersistableBusinessObject> save(List businessObjects) throws DataAccessException {
253     	if ( LOG.isDebugEnabled() ) {
254     		LOG.debug( "About to persist the following BOs:" );
255     		for ( Object bo : businessObjects ) {
256     			LOG.debug( "   --->" + bo );
257     		}
258     	}
259         for (Iterator i = businessObjects.iterator(); i.hasNext();) {
260             Object bo = i.next();
261             getPersistenceBrokerTemplate().store(bo);
262         }
263         return businessObjects;
264     }
265 
266 
267     /**
268      * Deletes the business object passed in.
269      *
270      * @param bo
271      * @throws DataAccessException
272      * @see org.kuali.rice.krad.dao.BusinessObjectDao#delete(org.kuali.rice.krad.bo.PersistableBusinessObject)
273      */
274     public void delete(PersistableBusinessObject bo) {
275         getPersistenceBrokerTemplate().delete(bo);
276     }
277 
278     /**
279      * @see org.kuali.rice.krad.dao.BusinessObjectDao#delete(java.util.List)
280      */
281     public void delete(List<? extends PersistableBusinessObject> boList) {
282         for (PersistableBusinessObject bo : boList) {
283             getPersistenceBrokerTemplate().delete(bo);
284         }
285     }
286 
287 
288     /**
289      * @see org.kuali.rice.krad.dao.BusinessObjectDao#deleteMatching(java.lang.Class, java.util.Map)
290      */
291     public void deleteMatching(Class clazz, Map<String, ?> fieldValues) {
292         Criteria criteria = buildCriteria(fieldValues);
293 
294         getPersistenceBrokerTemplate().deleteByQuery(QueryFactory.newQuery(clazz, criteria));
295 
296         // An ojb delete by query doesn't update the cache so we need to clear the cache for everything to work property.
297         // don't believe me? Read the source code to OJB
298         getPersistenceBrokerTemplate().clearCache();
299     }
300 
301     /**
302      * @see org.kuali.rice.krad.dao.BusinessObjectDao#retrieve(org.kuali.rice.krad.bo.PersistableBusinessObject)
303      */
304     public PersistableBusinessObject retrieve(PersistableBusinessObject object) {
305         return (PersistableBusinessObject) getPersistenceBrokerTemplate().getObjectByQuery(QueryFactory.newQueryByIdentity(object));
306     }
307 
308     /**
309 	 * OJB does not support this method
310 	 * @see org.kuali.rice.krad.dao.BusinessObjectDao#findByPrimaryKey(java.lang.Class, java.lang.Object)
311 	 */
312 	public  <T extends BusinessObject> T findByPrimaryKeyUsingKeyObject(Class<T> clazz, Object pkObject) {
313 		throw new UnsupportedOperationException("OJB does not support this option");
314 	}
315 
316 	/**
317 	 * No need to do anything - avoid saving and OJB will "manage read only"
318 	 * @see org.kuali.rice.krad.dao.BusinessObjectDao#manageReadOnly(org.kuali.rice.krad.bo.PersistableBusinessObject)
319 	 */
320 	public PersistableBusinessObject manageReadOnly(PersistableBusinessObject bo) {
321 		return bo;
322 	}
323 
324 	/**
325      * This method will build out criteria in the key-value paradigm (attribute-value).
326      *
327      * @param fieldValues
328      * @return
329      */
330     private Criteria buildCriteria(Map<String, ?> fieldValues) {
331         Criteria criteria = new Criteria();
332         for (Iterator i = fieldValues.entrySet().iterator(); i.hasNext();) {
333             Map.Entry<String, Object> e = (Map.Entry<String, Object>) i.next();
334 
335             String key = e.getKey();
336             Object value = e.getValue();
337             if (value instanceof Collection) {
338                 criteria.addIn(key, (Collection) value);
339             } else if(value instanceof String && ((String)value).contains("*")){
340                value = ((String)value).replace("*","%");
341                criteria.addLike(key,value);
342             }
343             else {
344                 criteria.addEqualTo(key, value);
345             }
346         }
347 
348         return criteria;
349     }
350 
351     
352     private <T extends BusinessObject> Criteria buildCriteria(Class<T> clazz, Object primaryKey) {
353         Map<String, Object> fieldValues = new HashMap<String, Object>();
354         List<String> fieldNames = getPersistenceStructureService().getPrimaryKeys(clazz);
355 
356         //create map of values
357         for (String fieldName : fieldNames) {
358             Object fieldValue;
359 
360             try {
361                 fieldValue = primaryKey.getClass().getMethod("get" + StringUtils.capitalize(fieldName)).invoke(primaryKey);
362                 fieldValues.put(fieldName, fieldValue);
363             } catch (IllegalArgumentException e) {
364                 e.printStackTrace();
365             } catch (IllegalAccessException e) {
366                 e.printStackTrace();
367             } catch (SecurityException e) {
368                 e.printStackTrace();
369             } catch (InvocationTargetException e) {
370                 e.printStackTrace();
371             } catch (NoSuchMethodException e) {
372                 e.printStackTrace();
373             }
374         }
375         return this.buildCriteria(fieldValues);
376     }
377     
378     /**
379      * Builds a Criteria object for active field set to true
380      * @return Criteria
381      */
382     private Criteria buildActiveCriteria(){
383         Criteria criteria = new Criteria();
384         criteria.addEqualTo(KRADPropertyConstants.ACTIVE, true);
385 
386         return criteria;
387     }
388 
389     /**
390      * Builds a Criteria object for active field set to true
391      * @return Criteria
392      */
393     private Criteria buildInactiveCriteria(){
394         Criteria criteria = new Criteria();
395         criteria.addEqualTo(KRADPropertyConstants.ACTIVE, false);
396 
397         return criteria;
398     }
399 
400     /**
401      * This method will build out criteria in the key-value paradigm (attribute-value).
402      *
403      * @param negativeFieldValues
404      * @return
405      */
406     private Criteria buildNegativeCriteria(Map<String, ?> negativeFieldValues) {
407         Criteria criteria = new Criteria();
408         for (Iterator i = negativeFieldValues.entrySet().iterator(); i.hasNext();) {
409             Map.Entry<String, Object> e = (Map.Entry<String, Object>) i.next();
410 
411             String key = e.getKey();
412             Object value = e.getValue();
413             if (value instanceof Collection) {
414                 criteria.addNotIn(key, (Collection) value);
415             }
416             else {
417                 criteria.addNotEqualTo(key, value);
418             }
419         }
420 
421         return criteria;
422     }
423 
424     /**
425      * Gets the persistenceStructureService attribute.
426      * @return Returns the persistenceStructureService.
427      */
428     protected PersistenceStructureService getPersistenceStructureService() {
429         return persistenceStructureService;
430     }
431 
432     /**
433      * Sets the persistenceStructureService attribute value.
434      * @param persistenceStructureService The persistenceStructureService to set.
435      */
436     public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
437         this.persistenceStructureService = persistenceStructureService;
438     }
439 
440     /**
441      * OJB collection helper instance which processes collections before persisting
442      *
443      * @return OjbCollectionHelper
444      */
445     protected OjbCollectionHelper getOjbCollectionHelper() {
446         if (ojbCollectionHelper == null) {
447             ojbCollectionHelper = KRADServiceLocatorInternal.getOjbCollectionHelper();
448         }
449 
450         return ojbCollectionHelper;
451     }
452 
453     /**
454      * Setter for the OJB collection helper
455      *
456      * @param ojbCollectionHelper
457      */
458     public void setOjbCollectionHelper(OjbCollectionHelper ojbCollectionHelper) {
459         this.ojbCollectionHelper = ojbCollectionHelper;
460     }
461 }