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 }