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 }