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.dao.impl;
017
018import java.lang.reflect.Field;
019import java.util.Collection;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import javax.persistence.EntityManager;
025import javax.persistence.EntityNotFoundException;
026import javax.persistence.PersistenceContext;
027
028import org.apache.commons.lang.StringUtils;
029import org.hibernate.proxy.HibernateProxy;
030import org.kuali.rice.core.framework.persistence.jpa.metadata.CollectionDescriptor;
031import org.kuali.rice.core.framework.persistence.jpa.metadata.EntityDescriptor;
032import org.kuali.rice.core.framework.persistence.jpa.metadata.JoinColumnDescriptor;
033import org.kuali.rice.core.framework.persistence.jpa.metadata.MetadataManager;
034import org.kuali.rice.core.framework.persistence.jpa.metadata.ObjectDescriptor;
035import org.kuali.rice.krad.dao.PersistenceDao;
036import org.kuali.rice.krad.service.KRADServiceLocator;
037
038public class PersistenceDaoJpa implements PersistenceDao {
039        static org.apache.commons.logging.Log LOG = org.apache.commons.logging.LogFactory.getLog(PersistenceDaoJpa.class);
040        
041        @PersistenceContext
042        private EntityManager entityManager;
043    
044        /**
045         * @see org.kuali.rice.krad.dao.PersistenceDao#clearCache()
046         */
047        public void clearCache() {}
048
049        /**
050         * @see org.kuali.rice.krad.dao.PersistenceDao#resolveProxy(java.lang.Object)
051         */
052        public Object resolveProxy(Object o) {
053                if (o instanceof HibernateProxy) {
054                try {
055                        final Object realObject = ((HibernateProxy) o).getHibernateLazyInitializer().getImplementation();
056                        return realObject;
057                } catch (EntityNotFoundException enfe) {
058                        return null;
059                }
060        }
061                return o;
062        }
063
064        /**
065         * @see org.kuali.rice.krad.dao.PersistenceDao#retrieveAllReferences(java.lang.Object)
066         */
067        public void retrieveAllReferences(Object o) {
068                EntityDescriptor ed = MetadataManager.getEntityDescriptor(o.getClass());
069                for (ObjectDescriptor od : ed.getObjectRelationships()) {
070                        retrieveReference(o, od.getAttributeName());
071                }
072                for (CollectionDescriptor cd : ed.getCollectionRelationships()) {
073                        retrieveReference(o, cd.getAttributeName());
074                }
075        }
076
077        /**
078         * @see org.kuali.rice.krad.dao.PersistenceDao#retrieveReference(java.lang.Object, java.lang.String)
079         */
080        public void retrieveReference(Object o, String referenceName) {
081                try {
082                        if (getEntityManager().contains(o)) {
083                                LOG.debug("the entity manager contains the object");
084                        }
085                        
086                        Field field = getField(o.getClass(), referenceName);
087                        field.setAccessible(true);
088                        
089                        String fk = null;
090                        String foreignPK = null;
091                        
092                        if (isReferenceCollection(o, referenceName)) {
093                                Collection reference = retrieveCollectionReference(o, referenceName);
094                                field.set(o, reference);
095                        } else {
096                                Object reference = retrieveObjectReference(o, referenceName);
097                                field.set(o, reference);
098                        }
099                } catch (Exception e) {
100                        e.printStackTrace();
101                }               
102        }
103        
104        private Field getField(Class clazz, String name) throws NoSuchFieldException {
105                if (clazz.equals(Object.class)) {
106                        throw new NoSuchFieldException(name);
107                }
108                Field field = null;
109                try {
110                        field = clazz.getDeclaredField(name);
111                } catch (Exception e) {}
112                if (field == null) {
113                        field = getField(clazz.getSuperclass(), name);
114                }
115                return field;
116        }
117        
118        /**
119         * Determines if the reference on the given object represents a collection or not
120         * 
121         * @param o the object which is to be refreshed
122         * @param referenceName the name of the reference to refresh
123         * @return true if the reference is a collection, false otherwise
124         */
125        protected boolean isReferenceCollection(Object o, String referenceName) {
126                EntityDescriptor ed = MetadataManager.getEntityDescriptor(o.getClass());
127                return ed.getCollectionDescriptorByName(referenceName) != null;
128        }
129        
130        /**
131         * This method fetches a collection to refresh a reference
132         * 
133         * @param o the object to refresh
134         * @param referenceName the name of the reference to refresh
135         * @return the retrieved object to refresh the 
136         * @throws NoSuchFieldException 
137         * @throws IllegalAccessException 
138         * @throws IllegalArgumentException 
139         */
140        protected Collection retrieveCollectionReference(Object o, String referenceName) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
141                final EntityDescriptor ed = MetadataManager.getEntityDescriptor(o.getClass());
142                final CollectionDescriptor cd = ed.getCollectionDescriptorByName(referenceName);
143                final EntityDescriptor foreignEntityDescriptor = MetadataManager.getEntityDescriptor(cd.getTargetEntity());
144                
145                Map<String, Object> searchKey = new HashMap<String, Object>();
146                for (String foreignKey : cd.getForeignKeyFields()) {
147                        Field localField = getField(o.getClass(), foreignKey);
148                        localField.setAccessible(true);
149                        final Object localValue = localField.get(o);
150                        
151                        final String foreignKeyProperty = getForeignKeyPropertyForKeyWithPossibleInverse(foreignKey, ed, foreignEntityDescriptor, cd);
152
153                        searchKey.put(foreignKeyProperty, localValue);
154                }
155                return KRADServiceLocator.getBusinessObjectService().findMatching(cd.getTargetEntity(), searchKey);
156        }
157        
158        /**
159         * Finds the correct foreign key property which corresponds to the given key
160         * 
161         * @param foreignKey the name of the key we want to find the proper foreign name for
162         * @param localEntityDescriptor the descriptor of the entity that key is on
163         * @param foreignEntityDescriptor the descriptor of the entity we're trying to retrieve
164         * @param joinColumnDescriptors a Map of the JoinColumnDescriptors that describe the relationship from the local entity's point of view, keyed by the column name of the JoinColumnDescriptor 
165         * @return the name of the property on the related object
166         */
167        protected String getForeignKeyPropertyForKeyWithPossibleInverse(String foreignKey, EntityDescriptor localEntityDescriptor, EntityDescriptor foreignEntityDescriptor, CollectionDescriptor collectionDescriptor) {
168                final String foreignKeyColumn = localEntityDescriptor.getFieldByName(foreignKey).getColumn();
169                
170                int count = 0;
171                JoinColumnDescriptor joinColumnDescriptor = null;
172                JoinColumnDescriptor inverseColumnDescriptor = null;
173                while (count < collectionDescriptor.getJoinColumnDescriptors().size() && joinColumnDescriptor == null) {
174                        if (collectionDescriptor.getJoinColumnDescriptors().get(count).getName().equalsIgnoreCase(foreignKeyColumn)) {
175                                joinColumnDescriptor = collectionDescriptor.getJoinColumnDescriptors().get(count);
176                                if (count < collectionDescriptor.getInverseJoinColumnDescriptors().size()) {
177                                        inverseColumnDescriptor = collectionDescriptor.getInverseJoinColumnDescriptors().get(count);
178                                }
179                        }
180                        count += 1;
181                }
182                
183                if (inverseColumnDescriptor != null) {
184                        return foreignEntityDescriptor.getFieldByColumnName(inverseColumnDescriptor.getName()).getName();
185                }
186                
187                if (!StringUtils.isBlank(joinColumnDescriptor.getReferencedColumName())) {
188                        return foreignEntityDescriptor.getFieldByColumnName(joinColumnDescriptor.getReferencedColumName()).getName();
189                }
190
191                return foreignEntityDescriptor.getFieldByColumnName(joinColumnDescriptor.getName()).getName();
192
193        }
194        
195        /**
196         * Fetches an object reference
197         * 
198         * @param o the object to refresh
199         * @param referenceName the name of the reference to fetch
200         * @return the fetched referred to object
201         * @throws IllegalAccessException 
202         * @throws IllegalArgumentException 
203         * @throws NoSuchFieldException 
204         * @throws ClassNotFoundException 
205         * @throws InstantiationException 
206         */
207        protected Object retrieveObjectReference(Object o, String referenceName) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InstantiationException, ClassNotFoundException {
208                final EntityDescriptor ed = MetadataManager.getEntityDescriptor(o.getClass());
209                final ObjectDescriptor od = ed.getObjectDescriptorByName(referenceName);
210                
211                final Object foreignKeyObject = buildForeignKeyObject(o, ed, od);
212                return getEntityManager().find(od.getTargetEntity(), foreignKeyObject);
213        }
214        
215        /**
216         * Builds a foreign key object for the relationship given by the foreignKeyClass and the objectDescriptor
217         * 
218         * @param o the object to refresh
219         * @param localEntityDescriptor the entity descriptor for that object
220         * @param objectDescriptor the object descriptor for the relationship to refresh
221         * @return the foreign key to fetch that object
222         * @throws IllegalArgumentException
223         * @throws NoSuchFieldException
224         * @throws IllegalAccessException
225         * @throws InstantiationException 
226         */
227        protected Object buildForeignKeyObject(Object o, EntityDescriptor localEntityDescriptor, ObjectDescriptor objectDescriptor) throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException, InstantiationException {
228                return (objectDescriptor.getForeignKeyFields().size() == 1) ? 
229                                        buildSingleKeyForeignKeyObject(o, objectDescriptor.getForeignKeyFields().get(0)) : 
230                                        buildCompositeForeignKeyObject(o, localEntityDescriptor, objectDescriptor);
231        }
232
233        /**
234         * Builds a composite key to fetch a reference
235         * 
236         * @param o the object to refresh
237         * @param localEntityDescriptor the entity descriptor for that object
238         * @param objectDescriptor the object descriptor for the relationship to refresh
239         * @return the foreign key to fetch that object
240         * @throws InstantiationException
241         * @throws IllegalAccessException
242         * @throws NoSuchFieldException
243         */
244        private Object buildCompositeForeignKeyObject(Object o, EntityDescriptor localEntityDescriptor, ObjectDescriptor objectDescriptor) throws InstantiationException, IllegalAccessException, NoSuchFieldException {
245                final Map<String, JoinColumnDescriptor> joinColumnDescriptors = buildJoinColumnDescriptorMap(objectDescriptor.getJoinColumnDescriptors());
246                
247                final EntityDescriptor foreignEntityDescriptor = MetadataManager.getEntityDescriptor(objectDescriptor.getTargetEntity());
248                final Class foreignEntityIdClass = foreignEntityDescriptor.getIdClass();
249                
250                Object foreignEntityId = foreignEntityIdClass.newInstance();
251                for (String foreignKey : objectDescriptor.getForeignKeyFields()) {
252                        // get the value from the current object
253                        Field localField = getField(o.getClass(), foreignKey);
254                        localField.setAccessible(true);
255                        final Object localValue = localField.get(o); 
256                        
257                        final String foreignKeyProperty = getForeignKeyPropertyForKey(foreignKey, localEntityDescriptor, foreignEntityDescriptor, joinColumnDescriptors);
258                                                
259                        Field foreignField = getField(foreignEntityId.getClass(), foreignKeyProperty);
260                        foreignField.setAccessible(true);
261                        foreignField.set(foreignEntityId, localValue);
262                }
263                return foreignEntityId;
264        }
265        
266        /**
267         * Finds the correct foreign key property which corresponds to the given key
268         * 
269         * @param foreignKey the name of the key we want to find the proper foreign name for
270         * @param localEntityDescriptor the descriptor of the entity that key is on
271         * @param foreignEntityDescriptor the descriptor of the entity we're trying to retrieve
272         * @param joinColumnDescriptors a Map of the JoinColumnDescriptors that describe the relationship from the local entity's point of view, keyed by the column name of the JoinColumnDescriptor 
273         * @return the name of the property on the related object
274         */
275        protected String getForeignKeyPropertyForKey(String foreignKey, EntityDescriptor localEntityDescriptor, EntityDescriptor foreignEntityDescriptor, Map<String, JoinColumnDescriptor> joinColumnDescriptors) {
276                final String foreignKeyColumn = localEntityDescriptor.getFieldByName(foreignKey).getColumn();
277                final JoinColumnDescriptor joinColumnDescriptor = joinColumnDescriptors.get(foreignKeyColumn);
278                
279                return (!StringUtils.isBlank(joinColumnDescriptor.getReferencedColumName())) ? 
280                                foreignEntityDescriptor.getFieldByColumnName(joinColumnDescriptor.getReferencedColumName()).getName() : 
281                                foreignEntityDescriptor.getFieldByColumnName(joinColumnDescriptor.getName()).getName();
282
283        }
284        
285        /**
286         * Turns a List of JoinColumnDescriptors and maps them by their name
287         * 
288         * @param joinColumnDescriptors a List of JoinColumnDescriptors
289         * @return a Map the List as a Map, keyed by the name of the JoinColumn
290         */
291        protected Map<String, JoinColumnDescriptor> buildJoinColumnDescriptorMap(List<JoinColumnDescriptor> joinColumnDescriptors) {
292                Map<String, JoinColumnDescriptor> descriptorMap = new HashMap<String, JoinColumnDescriptor>();
293                for (JoinColumnDescriptor joinColumnDescriptor : joinColumnDescriptors) {
294                        descriptorMap.put(joinColumnDescriptor.getName(), joinColumnDescriptor);
295                }
296                return descriptorMap;
297        }
298        
299        /**
300         * Builds a foreign key, where that foreign key has a single field
301         * 
302         * @param o the object to get the foreign key value from
303         * @param singleForeignKeyFieldName the name of the foreign key field
304         * @return a value for the foreign key
305         * @throws NoSuchFieldException
306         * @throws IllegalArgumentException
307         * @throws IllegalAccessException
308         */
309        protected Object buildSingleKeyForeignKeyObject(Object o, String singleForeignKeyFieldName) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
310                Field singleFKField = getField(o.getClass(), singleForeignKeyFieldName);
311                singleFKField.setAccessible(true);
312                return singleFKField.get(o);
313        }
314
315        /**
316         * True if object is an instance of HibernateProxy, false otherwise
317         * 
318         * @see org.kuali.rice.krad.dao.PersistenceDao#isProxied(java.lang.Object)
319         */
320        public boolean isProxied(Object object) {
321                return (object instanceof HibernateProxy);
322        }
323
324        /**
325         * @return the entityManager
326         */
327        public EntityManager getEntityManager() {
328                return this.entityManager;
329        }
330
331        /**
332         * @param entityManager the entityManager to set
333         */
334        public void setEntityManager(EntityManager entityManager) {
335                this.entityManager = entityManager;
336        }
337}