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