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 java.beans.PropertyDescriptor;
019import java.lang.reflect.Field;
020import java.lang.reflect.InvocationTargetException;
021import java.util.HashSet;
022import java.util.Iterator;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026
027import org.apache.commons.beanutils.PropertyUtils;
028import org.apache.commons.lang.StringUtils;
029import org.apache.log4j.Logger;
030import org.kuali.rice.core.framework.persistence.jpa.metadata.EntityDescriptor;
031import org.kuali.rice.core.framework.persistence.jpa.metadata.MetadataManager;
032import org.kuali.rice.core.framework.persistence.jpa.metadata.ObjectDescriptor;
033import org.kuali.rice.krad.bo.PersistableBusinessObject;
034import org.kuali.rice.krad.dao.PersistenceDao;
035import org.kuali.rice.krad.exception.IntrospectionException;
036import org.kuali.rice.krad.exception.ObjectNotABusinessObjectRuntimeException;
037import org.kuali.rice.krad.exception.ReferenceAttributeDoesntExistException;
038import org.kuali.rice.krad.exception.ReferenceAttributeNotAnOjbReferenceException;
039import org.kuali.rice.krad.service.PersistenceService;
040import org.kuali.rice.krad.util.ObjectUtils;
041import org.springframework.transaction.annotation.Transactional;
042
043
044/**
045 * This class is the service implementation for the Persistence structure.
046 */
047@Transactional
048public class PersistenceServiceJpaImpl extends PersistenceServiceImplBase implements PersistenceService {
049
050        private static Logger LOG = Logger.getLogger(PersistenceServiceJpaImpl.class);
051    private PersistenceDao persistenceDao;
052        
053    public void setPersistenceDao(PersistenceDao persistenceDao) {
054        this.persistenceDao = persistenceDao;
055    }
056
057        /**
058         * @see org.kuali.rice.krad.service.PersistenceService#allForeignKeyValuesPopulatedForReference(org.kuali.rice.krad.bo.PersistableBusinessObject, java.lang.String)
059         */
060        public boolean allForeignKeyValuesPopulatedForReference(PersistableBusinessObject bo, String referenceName) {
061        boolean allFkeysHaveValues = true;
062
063        // yelp if nulls were passed in
064        if (bo == null) {
065            throw new IllegalArgumentException("The Class passed in for the BusinessObject argument was null.");
066        }
067        if (StringUtils.isBlank(referenceName)) {
068            throw new IllegalArgumentException("The String passed in for the referenceName argument was null or empty.");
069        }
070
071        PropertyDescriptor propertyDescriptor = null;
072
073        // make sure the attribute exists at all, throw exception if not
074        try {
075            propertyDescriptor = PropertyUtils.getPropertyDescriptor(bo, referenceName);
076                } catch (Exception e) {
077            throw new RuntimeException(e);
078        }
079        if (propertyDescriptor == null) {
080            throw new ReferenceAttributeDoesntExistException("Requested attribute: '" + referenceName + "' does not exist " + "on class: '" + bo.getClass().getName() + "'.");
081        }
082
083        // get the class of the attribute name
084        Class referenceClass = getBusinessObjectAttributeClass( bo.getClass(), referenceName );
085        if ( referenceClass == null ) {
086                referenceClass = propertyDescriptor.getPropertyType();
087        }
088
089        // make sure the class of the attribute descends from BusinessObject,
090        // otherwise throw an exception
091        if (!PersistableBusinessObject.class.isAssignableFrom(referenceClass)) {
092                        throw new ObjectNotABusinessObjectRuntimeException("Attribute requested (" + referenceName + ") is of class: " + "'" + referenceClass.getName() + "' and is not a " + "descendent of BusinessObject.  Only descendents of BusinessObject " + "can be used.");
093        }
094
095        EntityDescriptor descriptor = MetadataManager.getEntityDescriptor(bo.getClass());
096        ObjectDescriptor objectDescriptor = descriptor.getObjectDescriptorByName(referenceName);
097        if (objectDescriptor == null) {
098            throw new ReferenceAttributeNotAnOjbReferenceException("Attribute requested (" + referenceName + ") is not listed " + "in OJB as a reference-descriptor for class: '" + bo.getClass().getName() + "'");
099        }
100
101                // get the list of the foreign-keys for this reference-descriptor
102        List fkFields = objectDescriptor.getForeignKeyFields();
103        Iterator fkIterator = fkFields.iterator();
104
105        // walk through the list of the foreign keys, get their types
106        while (fkIterator.hasNext()) {
107
108            // get the field name of the fk & pk field
109            String fkFieldName = (String) fkIterator.next();
110
111            // get the value for the fk field
112            Object fkFieldValue = null;
113            try {
114                fkFieldValue = PropertyUtils.getSimpleProperty(bo, fkFieldName);
115            }
116
117            // if we cant retrieve the field value, then
118            // it doesnt have a value
119            catch (IllegalAccessException e) {
120                return false;
121                        } catch (InvocationTargetException e) {
122                return false;
123                        } catch (NoSuchMethodException e) {
124                return false;
125            }
126
127            // test the value
128            if (fkFieldValue == null) {
129                return false;
130                        } else if (String.class.isAssignableFrom(fkFieldValue.getClass())) {
131                if (StringUtils.isBlank((String) fkFieldValue)) {
132                    return false;
133                }
134            }
135        }
136        
137        return allFkeysHaveValues;
138    }
139
140        /**
141         * @see org.kuali.rice.krad.service.PersistenceService#clearCache()
142         */
143        public void clearCache() {
144                persistenceDao.clearCache();
145        }
146
147        /**
148         * @see org.kuali.rice.krad.service.PersistenceService#getFlattenedPrimaryKeyFieldValues(java.lang.Object)
149         */
150        public String getFlattenedPrimaryKeyFieldValues(Object persistableObject) {
151        if (persistableObject == null) {
152            throw new IllegalArgumentException("invalid (null) persistableObject");
153        }
154        
155        Map primaryKeyValues = getPrimaryKeyFieldValues(persistableObject, true);
156
157        StringBuffer flattened = new StringBuffer(persistableObject.getClass().getName());
158        flattened.append("(");
159        for (Iterator i = primaryKeyValues.entrySet().iterator(); i.hasNext();) {
160            Map.Entry e = (Map.Entry) i.next();
161
162            String fieldName = (String) e.getKey();
163            Object fieldValue = e.getValue();
164
165            flattened.append(fieldName + "=" + fieldValue);
166            if (i.hasNext()) {
167                flattened.append(",");
168            }
169        }
170
171        flattened.append(")");
172
173        return flattened.toString();
174        }
175
176        /**
177         * @see org.kuali.rice.krad.service.PersistenceService#linkObjects(java.lang.Object)
178         */
179        public void linkObjects(Object persistableObject) {
180                linkObjectsWithCircularReferenceCheck(persistableObject, new HashSet());
181        }
182
183        /**
184         * @see org.kuali.rice.krad.service.PersistenceService#loadRepositoryDescriptor(java.lang.String)
185         */
186        public void loadRepositoryDescriptor(String ojbRepositoryFilePath) {}
187
188        /**
189         * @see org.kuali.rice.krad.service.PersistenceService#refreshAllNonUpdatingReferences(org.kuali.rice.krad.bo.PersistableBusinessObject)
190         */
191        public void refreshAllNonUpdatingReferences(PersistableBusinessObject bo) {
192                EntityDescriptor descriptor = MetadataManager.getEntityDescriptor(bo.getClass());
193                List<ObjectDescriptor> objectDescriptors = descriptor.getObjectRelationships();
194                
195                for (ObjectDescriptor od : objectDescriptors) {
196            if (od.getCascade().length == 0) {                  
197                retrieveReferenceObject(bo, od.getAttributeName());
198            }                   
199                }
200    }
201
202        /**
203         * @see org.kuali.rice.krad.service.PersistenceService#resolveProxy(java.lang.Object)
204         */
205        public Object resolveProxy(Object o) {
206                return persistenceDao.resolveProxy(o);
207        }
208
209        /**
210         * @see org.kuali.rice.krad.service.PersistenceService#retrieveNonKeyFields(java.lang.Object)
211         */
212        public void retrieveNonKeyFields(Object persistableObject) {
213        if (persistableObject == null) {
214            throw new IllegalArgumentException("invalid (null) persistableObject");
215        }
216        LOG.debug("retrieving non-key fields for " + persistableObject);
217
218        persistenceDao.retrieveAllReferences(persistableObject);
219    }
220
221        /**
222         * @see org.kuali.rice.krad.service.PersistenceService#retrieveReferenceObject(java.lang.Object, java.lang.String)
223         */
224        public void retrieveReferenceObject(Object persistableObject, String referenceObjectName) {
225        if (persistableObject == null) {
226            throw new IllegalArgumentException("invalid (null) persistableObject");
227        }
228        LOG.debug("retrieving reference object " + referenceObjectName + " for " + persistableObject);
229
230        persistenceDao.retrieveReference(persistableObject, referenceObjectName);
231    }
232
233        /**
234         * @see org.kuali.rice.krad.service.PersistenceService#retrieveReferenceObjects(java.lang.Object, java.util.List)
235         */
236        public void retrieveReferenceObjects(Object persistableObject, List referenceObjectNames) {
237        if (persistableObject == null) {
238            throw new IllegalArgumentException("invalid (null) persistableObject");
239        }
240        if (referenceObjectNames == null) {
241            throw new IllegalArgumentException("invalid (null) referenceObjectNames");
242        }
243        if (referenceObjectNames.isEmpty()) {
244            throw new IllegalArgumentException("invalid (empty) referenceObjectNames");
245        }
246
247        int index = 0;
248        for (Iterator i = referenceObjectNames.iterator(); i.hasNext(); index++) {
249            String referenceObjectName = (String) i.next();
250            if (StringUtils.isBlank(referenceObjectName)) {
251                throw new IllegalArgumentException("invalid (blank) name at position " + index);
252            }
253
254            retrieveReferenceObject(persistableObject, referenceObjectName);
255        }
256        }
257
258        /**
259         * @see org.kuali.rice.krad.service.PersistenceService#retrieveReferenceObjects(java.util.List, java.util.List)
260         */
261        public void retrieveReferenceObjects(List persistableObjects, List referenceObjectNames) {
262        if (persistableObjects == null) {
263            throw new IllegalArgumentException("invalid (null) persistableObjects");
264        }
265        if (persistableObjects.isEmpty()) {
266            throw new IllegalArgumentException("invalid (empty) persistableObjects");
267        }
268        if (referenceObjectNames == null) {
269            throw new IllegalArgumentException("invalid (null) referenceObjectNames");
270        }
271        if (referenceObjectNames.isEmpty()) {
272            throw new IllegalArgumentException("invalid (empty) referenceObjectNames");
273        }
274
275        for (Iterator i = persistableObjects.iterator(); i.hasNext();) {
276            Object persistableObject = i.next();
277            retrieveReferenceObjects(persistableObject, referenceObjectNames);
278        }
279        }
280    
281    private void linkObjectsWithCircularReferenceCheck(Object persistableObject, Set referenceSet) {
282        if (ObjectUtils.isNull(persistableObject) || referenceSet.contains(persistableObject)) {
283            return;
284        }
285        
286        referenceSet.add(persistableObject);
287        
288        EntityDescriptor descriptor = MetadataManager.getEntityDescriptor(persistableObject.getClass());
289
290        String className = null;
291        String fieldName = null;
292        try {
293            // iterate through all object references for the persistableObject
294                List<ObjectDescriptor> objectDescriptors = descriptor.getObjectRelationships();
295                for (ObjectDescriptor od : objectDescriptors) {
296                // get the actual reference object
297                className = persistableObject.getClass().getName();
298                fieldName = od.getAttributeName();
299                Object referenceObject = PropertyUtils.getProperty(persistableObject, fieldName);
300                if (ObjectUtils.isNull(referenceObject) || referenceSet.contains(referenceObject)) {
301                    continue;
302                }
303
304                // recursively link object
305                linkObjectsWithCircularReferenceCheck(referenceObject, referenceSet);
306
307                                // iterate through the keys for the reference object and set value
308                List<String> refFkNames = od.getForeignKeyFields();
309                EntityDescriptor refCld = MetadataManager.getEntityDescriptor(od.getTargetEntity());
310                Set<org.kuali.rice.core.framework.persistence.jpa.metadata.FieldDescriptor> refPkNames = refCld.getPrimaryKeys();
311
312                try {
313                        for (String fk : refFkNames) {
314                                                Field f = referenceObject.getClass().getDeclaredField(fk);
315                                                f.setAccessible(true);
316                                if (ObjectUtils.isNull(f.get(referenceObject))) {
317                                                        Field f2 = persistableObject.getClass().getDeclaredField(fk);
318                                                        f2.setAccessible(true);
319                                                        f.set(referenceObject, f2.get(persistableObject));
320                                                }                               
321                                        }
322                } catch (Exception e) {
323                        LOG.error(e.getMessage(), e);
324                }
325            }
326                } catch (NoSuchMethodException e) {
327            throw new IntrospectionException("no setter for property '" + className + "." + fieldName + "'", e);
328                } catch (IllegalAccessException e) {
329            throw new IntrospectionException("problem accessing property '" + className + "." + fieldName + "'", e);
330                } catch (InvocationTargetException e) {
331            throw new IntrospectionException("problem invoking getter for property '" + className + "." + fieldName + "'", e);
332        }
333    }
334    
335    /**
336         * Asks persistenceDao if this represents a proxy
337         * 
338         * @see org.kuali.rice.krad.service.PersistenceService#isProxied(java.lang.Object)
339         */
340        public boolean isProxied(Object object) {
341                return persistenceDao.isProxied(object);
342        }
343}