View Javadoc

1   /*
2    * Copyright 2007-2008 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kns.dao.impl;
17  
18  import java.lang.reflect.Field;
19  import java.util.Collection;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  
24  import javax.persistence.EntityManager;
25  import javax.persistence.EntityNotFoundException;
26  import javax.persistence.PersistenceContext;
27  
28  import org.apache.commons.lang.StringUtils;
29  import org.hibernate.proxy.HibernateProxy;
30  import org.kuali.rice.core.framework.persistence.jpa.metadata.CollectionDescriptor;
31  import org.kuali.rice.core.framework.persistence.jpa.metadata.EntityDescriptor;
32  import org.kuali.rice.core.framework.persistence.jpa.metadata.JoinColumnDescriptor;
33  import org.kuali.rice.core.framework.persistence.jpa.metadata.MetadataManager;
34  import org.kuali.rice.core.framework.persistence.jpa.metadata.ObjectDescriptor;
35  import org.kuali.rice.kns.dao.PersistenceDao;
36  import org.kuali.rice.kns.service.KNSServiceLocator;
37  
38  public class PersistenceDaoJpa implements PersistenceDao {
39  	static org.apache.commons.logging.Log LOG = org.apache.commons.logging.LogFactory.getLog(PersistenceDaoJpa.class);
40  	
41  	@PersistenceContext
42  	private EntityManager entityManager;
43      
44  	/**
45  	 * @see org.kuali.rice.kns.dao.PersistenceDao#clearCache()
46  	 */
47  	public void clearCache() {}
48  
49  	/**
50  	 * @see org.kuali.rice.kns.dao.PersistenceDao#resolveProxy(java.lang.Object)
51  	 */
52  	public Object resolveProxy(Object o) {
53  		if (o instanceof HibernateProxy) {
54          	try {
55  	        	final Object realObject = ((HibernateProxy) o).getHibernateLazyInitializer().getImplementation();
56  	        	return realObject;
57          	} catch (EntityNotFoundException enfe) {
58          		return null;
59          	}
60          }
61  		return o;
62  	}
63  
64  	/**
65  	 * @see org.kuali.rice.kns.dao.PersistenceDao#retrieveAllReferences(java.lang.Object)
66  	 */
67  	public void retrieveAllReferences(Object o) {
68  		EntityDescriptor ed = MetadataManager.getEntityDescriptor(o.getClass());
69  		for (ObjectDescriptor od : ed.getObjectRelationships()) {
70  			retrieveReference(o, od.getAttributeName());
71  		}
72  		for (CollectionDescriptor cd : ed.getCollectionRelationships()) {
73  			retrieveReference(o, cd.getAttributeName());
74  		}
75  	}
76  
77  	/**
78  	 * @see org.kuali.rice.kns.dao.PersistenceDao#retrieveReference(java.lang.Object, java.lang.String)
79  	 */
80  	public void retrieveReference(Object o, String referenceName) {
81  		try {
82  			if (getEntityManager().contains(o)) {
83  				LOG.debug("the entity manager contains the object");
84  			}
85  			
86  			Field field = getField(o.getClass(), referenceName);
87  			field.setAccessible(true);
88  			
89  			String fk = null;
90  			String foreignPK = null;
91  			
92  			if (isReferenceCollection(o, referenceName)) {
93  				Collection reference = retrieveCollectionReference(o, referenceName);
94  				field.set(o, reference);
95  			} else {
96  				Object reference = retrieveObjectReference(o, referenceName);
97  				field.set(o, reference);
98  			}
99  		} 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 KNSServiceLocator.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.kns.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 }