001/**
002 * Copyright 2005-2015 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 */
016package org.kuali.rice.krad.dao.impl;
017
018import org.apache.commons.lang.StringUtils;
019import org.apache.ojb.broker.query.Criteria;
020import org.apache.ojb.broker.query.QueryByCriteria;
021import org.apache.ojb.broker.query.QueryFactory;
022import org.kuali.rice.core.framework.persistence.ojb.dao.PlatformAwareDaoBaseOjb;
023import org.kuali.rice.krad.bo.BusinessObject;
024import org.kuali.rice.krad.bo.PersistableBusinessObject;
025import org.kuali.rice.krad.dao.BusinessObjectDao;
026import org.kuali.rice.krad.service.KRADServiceLocatorInternal;
027import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
028import org.kuali.rice.krad.service.PersistenceStructureService;
029import org.kuali.rice.krad.util.KRADPropertyConstants;
030import org.kuali.rice.krad.util.ObjectUtils;
031import org.kuali.rice.krad.util.OjbCollectionAware;
032import org.springframework.dao.DataAccessException;
033
034import java.lang.reflect.InvocationTargetException;
035import java.util.Collection;
036import java.util.HashMap;
037import java.util.Iterator;
038import java.util.List;
039import java.util.Map;
040import java.util.Set;
041
042/**
043 * This class is the OJB implementation of the BusinessObjectDao interface and should be used for generic business object unit
044 * tests.
045 */
046public class BusinessObjectDaoOjb extends PlatformAwareDaoBaseOjb implements BusinessObjectDao, OjbCollectionAware {
047    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BusinessObjectDaoOjb.class);
048
049    private PersistenceStructureService persistenceStructureService;
050
051    /**
052         * This constructs a {@link BusinessObjectDaoOjb}
053         */
054        public BusinessObjectDaoOjb(PersistenceStructureService persistenceStructureService) {
055                this.persistenceStructureService = persistenceStructureService;
056        }
057
058    /**
059         * @see org.kuali.rice.krad.dao.BusinessObjectDao#findBySinglePrimaryKey(java.lang.Class, java.lang.Object)
060         */
061        public <T extends BusinessObject> T findBySinglePrimaryKey(Class<T> clazz, Object primaryKey) {
062                if (primaryKey.getClass().getName().startsWith("java.lang.")
063                || primaryKey.getClass().getName().startsWith("java.sql.")
064                || primaryKey.getClass().getName().startsWith("java.math.")
065                || primaryKey.getClass().getName().startsWith("java.util.")) {
066                        try {
067                                return (T) getPersistenceBrokerTemplate().getObjectById(clazz, primaryKey);
068                        } catch ( DataAccessException ex ) {
069                        // it doesn't exist, just return null
070                                return null;
071                        }
072                } else {
073                        Criteria criteria = buildCriteria(clazz, primaryKey);
074
075                return (T) getPersistenceBrokerTemplate().getObjectByQuery(QueryFactory.newQuery(clazz, criteria));
076                }
077        }
078
079    /**
080     * @see org.kuali.rice.krad.dao.BusinessObjectDao#findByPrimaryKey(java.lang.Class, java.util.Map)
081     */
082    public <T extends BusinessObject> T findByPrimaryKey(Class<T> clazz, Map<String, ?> primaryKeys) {
083        Criteria criteria = buildCriteria(primaryKeys);
084
085        return (T) getPersistenceBrokerTemplate().getObjectByQuery(QueryFactory.newQuery(clazz, criteria));
086    }
087
088    /**
089     * Retrieves all of the records for a given class name.
090     *
091     * @param clazz - the name of the object being used, either KualiCodeBase or a subclass
092     * @return Collection
093     * @see org.kuali.rice.krad.dao.BusinessObjectDao#findAll(java.lang.Class)
094     */
095    public <T extends BusinessObject> Collection<T> findAll(Class<T> clazz) {
096        return (Collection<T>)getPersistenceBrokerTemplate().getCollectionByQuery(QueryFactory.newQuery(clazz, (Criteria) null));
097    }
098
099    /**
100     * @see org.kuali.rice.krad.dao.BusinessObjectDao#findAllOrderBy(java.lang.Class, java.lang.String, boolean)
101     */
102    public <T extends BusinessObject> Collection<T> findAllOrderBy(Class<T> clazz, String sortField, boolean sortAscending) {
103        QueryByCriteria queryByCriteria = new QueryByCriteria(clazz, (Criteria) null);
104
105        if (sortAscending) {
106            queryByCriteria.addOrderByAscending(sortField);
107        }
108        else {
109            queryByCriteria.addOrderByDescending(sortField);
110        }
111
112        return (Collection<T>)getPersistenceBrokerTemplate().getCollectionByQuery(queryByCriteria);
113    }
114
115    /**
116     * This is the default impl that comes with Kuali - uses OJB.
117     *
118     * @see org.kuali.rice.krad.dao.BusinessObjectDao#findMatching(java.lang.Class, java.util.Map)
119     */
120    public <T extends BusinessObject> Collection<T> findMatching(Class<T> clazz, Map<String, ?> fieldValues) {
121        Criteria criteria = buildCriteria(fieldValues);
122
123        return (Collection<T>)getPersistenceBrokerTemplate().getCollectionByQuery(QueryFactory.newQuery(clazz, criteria));
124    }
125
126
127    /**
128         * Throws an UnsupportedOperationException
129         * @see org.kuali.rice.krad.dao.BusinessObjectDao#findMatching(org.kuali.rice.core.framework.persistence.jpa.criteria.Criteria)
130         */
131        //public <T extends BusinessObject> Collection<T> findMatching(org.kuali.rice.core.jpa.criteria.Criteria criteria) {
132        //      throw new UnsupportedOperationException("OJB does not support finding matching business objects using JPA criteria");
133        //}
134
135        /**
136     * @see org.kuali.rice.krad.dao.BusinessObjectDao#findAllActive(java.lang.Class)
137     */
138    public <T extends BusinessObject> Collection<T> findAllActive(Class<T> clazz) {
139        return (Collection<T>)getPersistenceBrokerTemplate().getCollectionByQuery(QueryFactory.newQuery(clazz, buildActiveCriteria()));
140    }
141
142    /**
143     * @see org.kuali.rice.krad.dao.BusinessObjectDao#findAllActive(java.lang.Class)
144     */
145    public <T extends BusinessObject> Collection<T> findAllInactive(Class<T> clazz) {
146        return (Collection<T>)getPersistenceBrokerTemplate().getCollectionByQuery(QueryFactory.newQuery(clazz, buildInactiveCriteria()));
147    }
148
149    /**
150     * @see org.kuali.rice.krad.dao.BusinessObjectDao#findAllActiveOrderBy(java.lang.Class, java.lang.String, boolean)
151     */
152    public <T extends BusinessObject> Collection<T> findAllActiveOrderBy(Class<T> clazz, String sortField, boolean sortAscending) {
153        QueryByCriteria queryByCriteria = new QueryByCriteria(clazz, buildActiveCriteria());
154
155        if (sortAscending) {
156            queryByCriteria.addOrderByAscending(sortField);
157        }
158        else {
159            queryByCriteria.addOrderByDescending(sortField);
160        }
161
162        return (Collection<T>)getPersistenceBrokerTemplate().getCollectionByQuery(queryByCriteria);
163    }
164
165    /**
166     * @see org.kuali.rice.krad.dao.BusinessObjectDao#findMatchingActive(java.lang.Class, java.util.Map)
167     */
168    public <T extends BusinessObject> Collection<T> findMatchingActive(Class<T> clazz, Map<String, ?> fieldValues) {
169        Criteria criteria = buildCriteria(fieldValues);
170        criteria.addAndCriteria(buildActiveCriteria());
171
172        return (Collection<T>)getPersistenceBrokerTemplate().getCollectionByQuery(QueryFactory.newQuery(clazz, criteria));
173    }
174
175    /**
176     * This is the default impl that comes with Kuali - uses OJB.
177     *
178     * @see org.kuali.rice.krad.dao.BusinessObjectDao#countMatching(java.lang.Class, java.util.Map)
179     */
180    public int countMatching(Class clazz, Map<String, ?> fieldValues) {
181        Criteria criteria = buildCriteria(fieldValues);
182
183        return getPersistenceBrokerTemplate().getCount(QueryFactory.newQuery(clazz, criteria));
184    }
185
186    /**
187     * This is the default impl that comes with Kuali - uses OJB.
188     *
189     * @see org.kuali.rice.krad.dao.BusinessObjectDao#countMatching(java.lang.Class, java.util.Map, java.util.Map)
190     */
191    public int countMatching(Class clazz, Map<String, ?> positiveFieldValues, Map<String, ?> negativeFieldValues) {
192        Criteria criteria = buildCriteria(positiveFieldValues);
193        Criteria negativeCriteria = buildNegativeCriteria(negativeFieldValues);
194        criteria.addAndCriteria(negativeCriteria);
195        return getPersistenceBrokerTemplate().getCount(QueryFactory.newQuery(clazz, criteria));
196    }
197
198
199    /**
200     * This is the default impl that comes with Kuali - uses OJB.
201     *
202     * @see org.kuali.rice.krad.dao.BusinessObjectDao#findMatching(java.lang.Class, java.util.Map)
203     */
204    public <T extends BusinessObject> Collection<T> findMatchingOrderBy(Class<T> clazz, Map<String, ?> fieldValues, String sortField, boolean sortAscending) {
205        Criteria criteria = buildCriteria(fieldValues);
206        QueryByCriteria queryByCriteria = new QueryByCriteria(clazz, criteria);
207
208        if (sortAscending) {
209            queryByCriteria.addOrderByAscending(sortField);
210        }
211        else {
212            queryByCriteria.addOrderByDescending(sortField);
213        }
214
215        return (Collection<T>)getPersistenceBrokerTemplate().getCollectionByQuery(queryByCriteria);
216    }
217
218        /**
219         * Saves a business object.
220         *
221         * @see org.kuali.rice.krad.dao.BusinessObjectDao#save(org.kuali.rice.krad.bo.PersistableBusinessObject)
222         */
223        public PersistableBusinessObject save(PersistableBusinessObject bo) throws DataAccessException {
224                // if collections exist on the BO, create a copy and use to process the
225                // collections to ensure
226                // that removed elements are deleted from the database
227                Set<String> boCollections = getPersistenceStructureService().listCollectionObjectTypes(bo.getClass()).keySet();
228                PersistableBusinessObject savedBo = null;
229                if (!boCollections.isEmpty()) {
230                        // refresh bo to get db copy of collections
231                        savedBo = (PersistableBusinessObject) ObjectUtils.deepCopy(bo);
232                        for (String boCollection : boCollections) {
233                                if (getPersistenceStructureService().isCollectionUpdatable(savedBo.getClass(), boCollection)) {
234                                        savedBo.refreshReferenceObject(boCollection);
235                                }
236                        }
237            KRADServiceLocatorInternal.getOjbCollectionHelper().processCollections(this, bo, savedBo);
238        }
239
240                getPersistenceBrokerTemplate().store(bo);
241                return bo;
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        if ( LOG.isDebugEnabled() ) {
251                LOG.debug( "About to persist the following BOs:" );
252                for ( Object bo : businessObjects ) {
253                        LOG.debug( "   --->" + bo );
254                }
255        }
256        for (Iterator i = businessObjects.iterator(); i.hasNext();) {
257            Object bo = i.next();
258            getPersistenceBrokerTemplate().store(bo);
259        }
260        return businessObjects;
261    }
262
263
264    /**
265     * Deletes the business object passed in.
266     *
267     * @param bo
268     * @throws DataAccessException
269     * @see org.kuali.rice.krad.dao.BusinessObjectDao#delete(org.kuali.rice.krad.bo.PersistableBusinessObject)
270     */
271    public void delete(PersistableBusinessObject bo) {
272        getPersistenceBrokerTemplate().delete(bo);
273    }
274
275    /**
276     * @see org.kuali.rice.krad.dao.BusinessObjectDao#delete(java.util.List)
277     */
278    public void delete(List<? extends PersistableBusinessObject> boList) {
279        for (PersistableBusinessObject bo : boList) {
280            getPersistenceBrokerTemplate().delete(bo);
281        }
282    }
283
284
285    /**
286     * @see org.kuali.rice.krad.dao.BusinessObjectDao#deleteMatching(java.lang.Class, java.util.Map)
287     */
288    public void deleteMatching(Class clazz, Map<String, ?> fieldValues) {
289        Criteria criteria = buildCriteria(fieldValues);
290
291        getPersistenceBrokerTemplate().deleteByQuery(QueryFactory.newQuery(clazz, criteria));
292
293        // An ojb delete by query doesn't update the cache so we need to clear the cache for everything to work property.
294        // don't believe me? Read the source code to OJB
295        getPersistenceBrokerTemplate().clearCache();
296    }
297
298    /**
299     * @see org.kuali.rice.krad.dao.BusinessObjectDao#retrieve(org.kuali.rice.krad.bo.PersistableBusinessObject)
300     */
301    public PersistableBusinessObject retrieve(PersistableBusinessObject object) {
302        return (PersistableBusinessObject) getPersistenceBrokerTemplate().getObjectByQuery(QueryFactory.newQueryByIdentity(object));
303    }
304
305    /**
306         * OJB does not support this method
307         * @see org.kuali.rice.krad.dao.BusinessObjectDao#findByPrimaryKey(java.lang.Class, java.lang.Object)
308         */
309        public  <T extends BusinessObject> T findByPrimaryKeyUsingKeyObject(Class<T> clazz, Object pkObject) {
310                throw new UnsupportedOperationException("OJB does not support this option");
311        }
312
313        /**
314         * No need to do anything - avoid saving and OJB will "manage read only"
315         * @see org.kuali.rice.krad.dao.BusinessObjectDao#manageReadOnly(org.kuali.rice.krad.bo.PersistableBusinessObject)
316         */
317        public PersistableBusinessObject manageReadOnly(PersistableBusinessObject bo) {
318                return bo;
319        }
320
321        /**
322     * This method will build out criteria in the key-value paradigm (attribute-value).
323     *
324     * @param fieldValues
325     * @return
326     */
327    private Criteria buildCriteria(Map<String, ?> fieldValues) {
328        Criteria criteria = new Criteria();
329        for (Iterator i = fieldValues.entrySet().iterator(); i.hasNext();) {
330            Map.Entry<String, Object> e = (Map.Entry<String, Object>) i.next();
331
332            String key = e.getKey();
333            Object value = e.getValue();
334            if (value instanceof Collection) {
335                criteria.addIn(key, (Collection) value);
336            } else if(value instanceof String && ((String)value).contains("*")){
337               value = ((String)value).replace("*","%");
338               criteria.addLike(key,value);
339            }
340            else {
341                criteria.addEqualTo(key, value);
342            }
343        }
344
345        return criteria;
346    }
347
348    
349    private <T extends BusinessObject> Criteria buildCriteria(Class<T> clazz, Object primaryKey) {
350        Map<String, Object> fieldValues = new HashMap<String, Object>();
351        List<String> fieldNames = getPersistenceStructureService().getPrimaryKeys(clazz);
352
353        //create map of values
354        for (String fieldName : fieldNames) {
355            Object fieldValue;
356
357            try {
358                fieldValue = primaryKey.getClass().getMethod("get" + StringUtils.capitalize(fieldName)).invoke(primaryKey);
359                fieldValues.put(fieldName, fieldValue);
360            } catch (IllegalArgumentException e) {
361                e.printStackTrace();
362            } catch (IllegalAccessException e) {
363                e.printStackTrace();
364            } catch (SecurityException e) {
365                e.printStackTrace();
366            } catch (InvocationTargetException e) {
367                e.printStackTrace();
368            } catch (NoSuchMethodException e) {
369                e.printStackTrace();
370            }
371        }
372        return this.buildCriteria(fieldValues);
373    }
374    
375    /**
376     * Builds a Criteria object for active field set to true
377     * @return Criteria
378     */
379    private Criteria buildActiveCriteria(){
380        Criteria criteria = new Criteria();
381        criteria.addEqualTo(KRADPropertyConstants.ACTIVE, true);
382
383        return criteria;
384    }
385
386    /**
387     * Builds a Criteria object for active field set to true
388     * @return Criteria
389     */
390    private Criteria buildInactiveCriteria(){
391        Criteria criteria = new Criteria();
392        criteria.addEqualTo(KRADPropertyConstants.ACTIVE, false);
393
394        return criteria;
395    }
396
397    /**
398     * This method will build out criteria in the key-value paradigm (attribute-value).
399     *
400     * @param negativeFieldValues
401     * @return
402     */
403    private Criteria buildNegativeCriteria(Map<String, ?> negativeFieldValues) {
404        Criteria criteria = new Criteria();
405        for (Iterator i = negativeFieldValues.entrySet().iterator(); i.hasNext();) {
406            Map.Entry<String, Object> e = (Map.Entry<String, Object>) i.next();
407
408            String key = e.getKey();
409            Object value = e.getValue();
410            if (value instanceof Collection) {
411                criteria.addNotIn(key, (Collection) value);
412            }
413            else {
414                criteria.addNotEqualTo(key, value);
415            }
416        }
417
418        return criteria;
419    }
420
421    /**
422     * Gets the persistenceStructureService attribute.
423     * @return Returns the persistenceStructureService.
424     */
425    protected PersistenceStructureService getPersistenceStructureService() {
426        return persistenceStructureService;
427    }
428
429    /**
430     * Sets the persistenceStructureService attribute value.
431     * @param persistenceStructureService The persistenceStructureService to set.
432     */
433    public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
434        this.persistenceStructureService = persistenceStructureService;
435    }
436
437}