001    /**
002     * Copyright 2005-2011 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     */
016    package org.kuali.rice.krad.dao.impl;
017    
018    import java.lang.reflect.Field;
019    import java.util.Collection;
020    import java.util.HashMap;
021    import java.util.List;
022    import java.util.Map;
023    
024    import javax.persistence.EntityManager;
025    import javax.persistence.EntityNotFoundException;
026    import javax.persistence.PersistenceContext;
027    
028    import org.apache.commons.lang.StringUtils;
029    import org.hibernate.proxy.HibernateProxy;
030    import org.kuali.rice.core.framework.persistence.jpa.metadata.CollectionDescriptor;
031    import org.kuali.rice.core.framework.persistence.jpa.metadata.EntityDescriptor;
032    import org.kuali.rice.core.framework.persistence.jpa.metadata.JoinColumnDescriptor;
033    import org.kuali.rice.core.framework.persistence.jpa.metadata.MetadataManager;
034    import org.kuali.rice.core.framework.persistence.jpa.metadata.ObjectDescriptor;
035    import org.kuali.rice.krad.dao.PersistenceDao;
036    import org.kuali.rice.krad.service.KRADServiceLocator;
037    
038    public 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    }