001/**
002 * Copyright 2005-2016 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.service.impl;
017
018import org.apache.commons.beanutils.PropertyUtils;
019import org.apache.commons.lang.StringUtils;
020import org.apache.log4j.Logger;
021import org.apache.ojb.broker.metadata.ClassDescriptor;
022import org.apache.ojb.broker.metadata.ConnectionRepository;
023import org.apache.ojb.broker.metadata.DescriptorRepository;
024import org.apache.ojb.broker.metadata.FieldDescriptor;
025import org.apache.ojb.broker.metadata.MetadataManager;
026import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
027import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
028import org.kuali.rice.core.api.exception.RiceRuntimeException;
029import org.kuali.rice.core.api.util.ClassLoaderUtils;
030import org.kuali.rice.krad.bo.PersistableBusinessObject;
031import org.kuali.rice.krad.dao.PersistenceDao;
032import org.kuali.rice.krad.exception.IntrospectionException;
033import org.kuali.rice.krad.exception.ObjectNotABusinessObjectRuntimeException;
034import org.kuali.rice.krad.exception.ReferenceAttributeDoesntExistException;
035import org.kuali.rice.krad.exception.ReferenceAttributeNotAnOjbReferenceException;
036import org.kuali.rice.krad.service.PersistenceService;
037import org.kuali.rice.krad.util.LegacyDataFramework;
038import org.kuali.rice.krad.util.ObjectUtils;
039import org.springframework.core.io.DefaultResourceLoader;
040import org.springframework.transaction.annotation.Transactional;
041
042import java.beans.PropertyDescriptor;
043import java.io.IOException;
044import java.io.InputStream;
045import java.lang.reflect.InvocationTargetException;
046import java.util.HashMap;
047import java.util.HashSet;
048import java.util.Iterator;
049import java.util.List;
050import java.util.Map;
051import java.util.Set;
052import java.util.Vector;
053
054/**
055 * This class is the service implementation for the Persistence structure.
056 * OjbRepositoryExplorer provides functions for extracting information from the
057 * OJB repository at runtime. This is the default implementation, that is
058 * delivered with Kuali.
059 */
060@Deprecated
061@Transactional
062@LegacyDataFramework
063public class PersistenceServiceOjbImpl extends PersistenceServiceImplBase implements PersistenceService {
064    private static Logger LOG = Logger.getLogger(PersistenceServiceOjbImpl.class);
065    private static final String CLASSPATH_RESOURCE_PREFIX = "classpath:";
066    private PersistenceDao persistenceDao;
067
068    @Override
069        public void clearCache() {
070        persistenceDao.clearCache();
071    }
072
073    @Override
074        public Object resolveProxy(Object o) {
075        return persistenceDao.resolveProxy(o);
076    }
077
078        @Override
079        public void loadRepositoryDescriptor(String ojbRepositoryFilePath) {
080                if ( LOG.isInfoEnabled() ) {
081                        LOG.info("Begin loading OJB Metadata for: " + ojbRepositoryFilePath);
082                }
083                DefaultResourceLoader resourceLoader = new DefaultResourceLoader(ClassLoaderUtils.getDefaultClassLoader());
084                InputStream is = null;
085                try {
086                        is = resourceLoader.getResource(CLASSPATH_RESOURCE_PREFIX + ojbRepositoryFilePath).getInputStream();
087                        ConnectionRepository cr = MetadataManager.getInstance().readConnectionRepository(is);
088                        MetadataManager.getInstance().mergeConnectionRepository(cr);
089
090                        is = resourceLoader.getResource(CLASSPATH_RESOURCE_PREFIX + ojbRepositoryFilePath).getInputStream();
091                        DescriptorRepository dr = MetadataManager.getInstance().readDescriptorRepository(is);
092                        MetadataManager.getInstance().mergeDescriptorRepository(dr);
093
094                        if (LOG.isDebugEnabled()) {
095                                LOG.debug("--------------------------------------------------------------------------");
096                                LOG.debug("Merging repository descriptor: " + ojbRepositoryFilePath);
097                                LOG.debug("--------------------------------------------------------------------------");
098                        }
099                } catch (IOException ioe) {
100                        if (is != null) {
101                                try {
102                                        is.close();
103                                } catch (IOException e) {
104                                        LOG.warn("Failed to close InputStream on OJB repository path " + ojbRepositoryFilePath, e);
105                                }
106                        }
107                        throw new RiceRuntimeException(ioe);
108                } finally {
109                        if (is != null) {
110                                try {
111                                        is.close();
112                                } catch (IOException e) {
113                                        LOG.warn("Failed to close InputStream on OJB repository path " + ojbRepositoryFilePath, e);
114                                }
115                        }
116                }
117                if ( LOG.isInfoEnabled() ) {
118                        LOG.info("Finished loading OJB Metadata for: " + ojbRepositoryFilePath);
119                }
120        }
121
122    /**
123         * @see org.kuali.rice.krad.service.PersistenceService#retrieveNonKeyFields(java.lang.Object)
124         */
125    @Override
126        public void retrieveNonKeyFields(Object persistableObject) {
127        if (persistableObject == null) {
128            throw new IllegalArgumentException("invalid (null) persistableObject");
129        }
130        if ( LOG.isDebugEnabled() ) {
131                LOG.debug("retrieving non-key fields for " + persistableObject);
132        }
133
134        persistenceDao.retrieveAllReferences(persistableObject);
135    }
136
137    /**
138         * @see org.kuali.rice.krad.service.PersistenceService#retrieveReferenceObject(java.lang.Object,
139         *      String referenceObjectName)
140     */
141    @Override
142        public void retrieveReferenceObject(Object persistableObject, String referenceObjectName) {
143        if (persistableObject == null) {
144            throw new IllegalArgumentException("invalid (null) persistableObject");
145        }
146        if ( LOG.isDebugEnabled() ) {
147                LOG.debug("retrieving reference object " + referenceObjectName + " for " + persistableObject);
148        }
149        persistenceDao.retrieveReference(persistableObject, referenceObjectName);
150    }
151
152    /**
153         * @see org.kuali.rice.krad.service.PersistenceService#retrieveReferenceObject(java.lang.Object,
154         *      String referenceObjectName)
155     */
156    @Override
157        public void retrieveReferenceObjects(Object persistableObject, List referenceObjectNames) {
158        if (persistableObject == null) {
159            throw new IllegalArgumentException("invalid (null) persistableObject");
160        }
161        if (referenceObjectNames == null) {
162            throw new IllegalArgumentException("invalid (null) referenceObjectNames");
163        }
164        if (referenceObjectNames.isEmpty()) {
165            throw new IllegalArgumentException("invalid (empty) referenceObjectNames");
166        }
167
168        int index = 0;
169        for (Iterator i = referenceObjectNames.iterator(); i.hasNext(); index++) {
170            String referenceObjectName = (String) i.next();
171            if (StringUtils.isBlank(referenceObjectName)) {
172                throw new IllegalArgumentException("invalid (blank) name at position " + index);
173            }
174
175            retrieveReferenceObject(persistableObject, referenceObjectName);
176        }
177    }
178
179    /**
180         * @see org.kuali.rice.krad.service.PersistenceService#retrieveReferenceObject(java.lang.Object,
181         *      String referenceObjectName)
182     */
183    @Override
184        public void retrieveReferenceObjects(List persistableObjects, List referenceObjectNames) {
185        if (persistableObjects == null) {
186            throw new IllegalArgumentException("invalid (null) persistableObjects");
187        }
188        if (persistableObjects.isEmpty()) {
189            throw new IllegalArgumentException("invalid (empty) persistableObjects");
190        }
191        if (referenceObjectNames == null) {
192            throw new IllegalArgumentException("invalid (null) referenceObjectNames");
193        }
194        if (referenceObjectNames.isEmpty()) {
195            throw new IllegalArgumentException("invalid (empty) referenceObjectNames");
196        }
197
198        for (Iterator i = persistableObjects.iterator(); i.hasNext();) {
199            Object persistableObject = i.next();
200            retrieveReferenceObjects(persistableObject, referenceObjectNames);
201        }
202    }
203
204
205    /**
206     * @see org.kuali.rice.krad.service.PersistenceService#getFlattenedPrimaryKeyFieldValues(java.lang.Object)
207     */
208    @Override
209        public String getFlattenedPrimaryKeyFieldValues(Object persistableObject) {
210        if (persistableObject == null) {
211            throw new IllegalArgumentException("invalid (null) persistableObject");
212        }
213        Map primaryKeyValues = getPrimaryKeyFieldValues(persistableObject, true);
214
215        StringBuffer flattened = new StringBuffer(persistableObject.getClass().getName());
216        flattened.append("(");
217        for (Iterator i = primaryKeyValues.entrySet().iterator(); i.hasNext();) {
218            Map.Entry e = (Map.Entry) i.next();
219
220            String fieldName = (String) e.getKey();
221            Object fieldValue = e.getValue();
222
223            flattened.append(fieldName + "=" + fieldValue);
224            if (i.hasNext()) {
225                flattened.append(",");
226            }
227        }
228
229        flattened.append(")");
230
231        return flattened.toString();
232
233    }
234
235    private void linkObjectsWithCircularReferenceCheck(Object persistableObject, Set referenceSet) {
236        if (ObjectUtils.isNull(persistableObject) || referenceSet.contains(persistableObject)) {
237            return;
238        }
239        referenceSet.add(persistableObject);
240        ClassDescriptor classDescriptor = getClassDescriptor(persistableObject.getClass());
241
242        String className = null;
243        String fieldName = null;
244        try {
245            // iterate through all object references for the persistableObject
246            Vector objectReferences = classDescriptor.getObjectReferenceDescriptors();
247            for (Iterator iter = objectReferences.iterator(); iter.hasNext();) {
248                ObjectReferenceDescriptor referenceDescriptor = (ObjectReferenceDescriptor) iter.next();
249
250                // get the actual reference object
251                className = persistableObject.getClass().getName();
252                fieldName = referenceDescriptor.getAttributeName();
253                Object referenceObject = PropertyUtils.getProperty(persistableObject, fieldName);
254                if (ObjectUtils.isNull(referenceObject) || referenceSet.contains(referenceObject)) {
255                    continue;
256                }
257
258                // recursively link object
259                linkObjectsWithCircularReferenceCheck(referenceObject, referenceSet);
260
261                                // iterate through the keys for the reference object and set
262                                // value
263                FieldDescriptor[] refFkNames = referenceDescriptor.getForeignKeyFieldDescriptors(classDescriptor);
264                ClassDescriptor refCld = getClassDescriptor(referenceDescriptor.getItemClass());
265                FieldDescriptor[] refPkNames = refCld.getPkFields();
266
267                Map objFkValues = new HashMap();
268                for (int i = 0; i < refPkNames.length; i++) {
269                    objFkValues.put(refFkNames[i].getAttributeName(), ObjectUtils.getPropertyValue(referenceObject, refPkNames[i].getAttributeName()));
270                }
271
272                for (int i = 0; i < refFkNames.length; i++) {
273                    FieldDescriptor fkField = refFkNames[i];
274                    String fkName = fkField.getAttributeName();
275
276                                        // if the fk from object and use if main object does not
277                                        // have value
278                    Object fkValue = null;
279                    if (objFkValues.containsKey(fkName)) {
280                        fkValue = objFkValues.get(fkName);
281                    }
282
283                    // if fk is set in main object, take value from there
284                    Object mainFkValue = ObjectUtils.getPropertyValue(persistableObject, fkName);
285                    if (ObjectUtils.isNotNull(mainFkValue) && StringUtils.isNotBlank(mainFkValue.toString())) {
286                        fkValue = mainFkValue;
287                                        } else if (ObjectUtils.isNull(fkValue) || StringUtils.isBlank(fkValue.toString())) {
288                                                // find the value from one of the other reference
289                                                // objects
290                        for (Iterator iter2 = objectReferences.iterator(); iter2.hasNext();) {
291                            ObjectReferenceDescriptor checkDescriptor = (ObjectReferenceDescriptor) iter2.next();
292
293                            fkValue = getReferenceFKValue(persistableObject, checkDescriptor, fkName);
294                            if (ObjectUtils.isNotNull(fkValue) && StringUtils.isNotBlank(fkValue.toString())) {
295                                break;
296                            }
297                        }
298                    }
299
300                    // set the fk value
301                    if (ObjectUtils.isNotNull(fkValue)) {
302                        fieldName = refPkNames[i].getAttributeName();
303                        ObjectUtils.setObjectProperty(referenceObject, fieldName, fkValue.getClass(), fkValue);
304
305                        // set fk in main object
306                        if (ObjectUtils.isNull(mainFkValue)) {
307                            ObjectUtils.setObjectProperty(persistableObject, fkName, fkValue.getClass(), fkValue);
308                        }
309                    }
310                }
311            }
312                } catch (NoSuchMethodException e) {
313            throw new IntrospectionException("no setter for property '" + className + "." + fieldName + "'", e);
314                } catch (IllegalAccessException e) {
315            throw new IntrospectionException("problem accessing property '" + className + "." + fieldName + "'", e);
316                } catch (InvocationTargetException e) {
317            throw new IntrospectionException("problem invoking getter for property '" + className + "." + fieldName + "'", e);
318        }
319    }
320
321    /**
322         * For each reference object to the parent persistableObject, sets the key
323         * values for that object. First, if the reference object already has a
324         * value for the key, the value is left unchanged. Otherwise, for
325         * non-anonymous keys, the value is taken from the parent object. For
326         * anonymous keys, all other persistableObjects are checked until a value
327         * for the key is found.
328     *
329     * @see org.kuali.rice.krad.service.PersistenceService#getReferencedObject(java.lang.Object,
330     *      org.apache.ojb.broker.metadata.ObjectReferenceDescriptor)
331     */
332    @Override
333        public void linkObjects(Object persistableObject) {
334        linkObjectsWithCircularReferenceCheck(persistableObject, new HashSet());
335    }
336
337    /**
338     *
339     * @see org.kuali.rice.krad.service.PersistenceService#allForeignKeyValuesPopulatedForReference(org.kuali.rice.krad.bo.BusinessObject,
340     *      java.lang.String)
341     */
342    @Override
343        public boolean allForeignKeyValuesPopulatedForReference(PersistableBusinessObject bo, String referenceName) {
344
345        boolean allFkeysHaveValues = true;
346
347        // yelp if nulls were passed in
348        if (bo == null) {
349            throw new IllegalArgumentException("The Class passed in for the BusinessObject argument was null.");
350        }
351        if (StringUtils.isBlank(referenceName)) {
352            throw new IllegalArgumentException("The String passed in for the referenceName argument was null or empty.");
353        }
354
355        PropertyDescriptor propertyDescriptor = null;
356
357        // make sure the attribute exists at all, throw exception if not
358        try {
359            propertyDescriptor = PropertyUtils.getPropertyDescriptor(bo, referenceName);
360                } catch (Exception e) {
361            throw new RuntimeException(e);
362        }
363        if (propertyDescriptor == null) {
364            throw new ReferenceAttributeDoesntExistException("Requested attribute: '" + referenceName + "' does not exist " + "on class: '" + bo.getClass().getName() + "'.");
365        }
366
367        // get the class of the attribute name
368        Class referenceClass = getBusinessObjectAttributeClass( bo.getClass(), referenceName );
369        if ( referenceClass == null ) {
370                referenceClass = propertyDescriptor.getPropertyType();
371        }
372
373        // make sure the class of the attribute descends from BusinessObject,
374        // otherwise throw an exception
375        if (!PersistableBusinessObject.class.isAssignableFrom(referenceClass)) {
376                        throw new ObjectNotABusinessObjectRuntimeException("Attribute requested (" + referenceName + ") is of class: " + "'" + referenceClass.getName() + "' and is not a " + "descendent of BusinessObject.  Only descendents of BusinessObject "
377                                        + "can be used.");
378        }
379
380                // make sure the attribute designated is listed as a
381                // reference-descriptor
382                // on the clazz specified, otherwise throw an exception (OJB);
383        ClassDescriptor classDescriptor = getClassDescriptor(bo.getClass());
384        ObjectReferenceDescriptor referenceDescriptor = classDescriptor.getObjectReferenceDescriptorByName(referenceName);
385        if (referenceDescriptor == null) {
386            throw new ReferenceAttributeNotAnOjbReferenceException("Attribute requested (" + referenceName + ") is not listed " + "in OJB as a reference-descriptor for class: '" + bo.getClass().getName() + "'");
387        }
388
389                // get the list of the foreign-keys for this reference-descriptor
390                // (OJB)
391        Vector fkFields = referenceDescriptor.getForeignKeyFields();
392        Iterator fkIterator = fkFields.iterator();
393
394        // walk through the list of the foreign keys, get their types
395        while (fkIterator.hasNext()) {
396
397            // get the field name of the fk & pk field
398            String fkFieldName = (String) fkIterator.next();
399
400            // get the value for the fk field
401            Object fkFieldValue = null;
402            try {
403                fkFieldValue = PropertyUtils.getSimpleProperty(bo, fkFieldName);
404            }
405
406            // if we cant retrieve the field value, then
407            // it doesnt have a value
408            catch (IllegalAccessException e) {
409                return false;
410                        } catch (InvocationTargetException e) {
411                return false;
412                        } catch (NoSuchMethodException e) {
413                return false;
414            }
415
416            // test the value
417            if (fkFieldValue == null) {
418                return false;
419                        } else if (String.class.isAssignableFrom(fkFieldValue.getClass())) {
420                if (StringUtils.isBlank((String) fkFieldValue)) {
421                    return false;
422                }
423            }
424        }
425
426        return allFkeysHaveValues;
427    }
428
429    /**
430     *
431     * @see org.kuali.rice.krad.service.PersistenceService#refreshAllNonUpdatingReferences(org.kuali.rice.krad.bo.BusinessObject)
432     */
433    @Override
434        public void refreshAllNonUpdatingReferences(PersistableBusinessObject bo) {
435
436        // get the OJB class-descriptor for the bo class
437        ClassDescriptor classDescriptor = getClassDescriptor(bo.getClass());
438
439        // get a list of all reference-descriptors for that class
440        Vector references = classDescriptor.getObjectReferenceDescriptors();
441
442        // walk through all of the reference-descriptors
443        for (Iterator iter = references.iterator(); iter.hasNext();) {
444            ObjectReferenceDescriptor reference = (ObjectReferenceDescriptor) iter.next();
445
446            // if its NOT an updateable reference, then lets refresh it
447            if (reference.getCascadingStore() == ObjectReferenceDescriptor.CASCADE_NONE) {
448                PersistentField persistentField = reference.getPersistentField();
449                String referenceName = persistentField.getName();
450                retrieveReferenceObject(bo, referenceName);
451            }
452        }
453    }
454
455    private Object getReferenceFKValue(Object persistableObject, ObjectReferenceDescriptor chkRefCld, String fkName) {
456        ClassDescriptor classDescriptor = getClassDescriptor(persistableObject.getClass());
457        Object referenceObject = ObjectUtils.getPropertyValue(persistableObject, chkRefCld.getAttributeName());
458
459        if (referenceObject == null) {
460            return null;
461        }
462
463        FieldDescriptor[] refFkNames = chkRefCld.getForeignKeyFieldDescriptors(classDescriptor);
464        ClassDescriptor refCld = getClassDescriptor(chkRefCld.getItemClass());
465        FieldDescriptor[] refPkNames = refCld.getPkFields();
466
467
468        Object fkValue = null;
469        for (int i = 0; i < refFkNames.length; i++) {
470            FieldDescriptor fkField = refFkNames[i];
471
472            if (fkField.getAttributeName().equals(fkName)) {
473                fkValue = ObjectUtils.getPropertyValue(referenceObject, refPkNames[i].getAttributeName());
474                break;
475            }
476        }
477
478        return fkValue;
479    }
480
481    /**
482         * Asks persistenceDao if this represents a proxy
483         *
484         * @see org.kuali.rice.krad.service.PersistenceService#isProxied(java.lang.Object)
485         */
486        @Override
487        public boolean isProxied(Object object) {
488                return persistenceDao.isProxied(object);
489        }
490
491        /**
492     * Sets the persistenceDao attribute value.
493         *
494         * @param persistenceDao
495         *            The persistenceDao to set.
496     */
497    public void setPersistenceDao(PersistenceDao persistenceDao) {
498        this.persistenceDao = persistenceDao;
499    }
500}