View Javadoc

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