View Javadoc

1   /**
2    * Copyright 2005-2013 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.krad.metadata;
17  
18  import org.apache.commons.lang.StringUtils;
19  
20  import javax.persistence.AttributeOverride;
21  import javax.persistence.AttributeOverrides;
22  import javax.persistence.CascadeType;
23  import javax.persistence.Column;
24  import javax.persistence.Entity;
25  import javax.persistence.Id;
26  import javax.persistence.IdClass;
27  import javax.persistence.JoinColumn;
28  import javax.persistence.JoinColumns;
29  import javax.persistence.JoinTable;
30  import javax.persistence.Lob;
31  import javax.persistence.ManyToMany;
32  import javax.persistence.ManyToOne;
33  import javax.persistence.OneToMany;
34  import javax.persistence.OneToOne;
35  import javax.persistence.Table;
36  import javax.persistence.Temporal;
37  import javax.persistence.Transient;
38  import javax.persistence.Version;
39  import java.lang.reflect.Field;
40  import java.lang.reflect.Modifier;
41  import java.lang.reflect.ParameterizedType;
42  import java.util.ArrayList;
43  import java.util.Collections;
44  import java.util.HashMap;
45  import java.util.HashSet;
46  import java.util.List;
47  import java.util.Map;
48  import java.util.Set;
49  
50  /**
51   * @author Kuali Rice Team (rice.collab@kuali.org)
52   */
53  @Deprecated
54  public class MetadataManager {
55  
56  	private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MetadataManager.class);
57  	
58  	private static Map<Class, EntityDescriptor> entitesByClass = Collections.synchronizedMap( new HashMap<Class, EntityDescriptor>() );
59  	private static Map<String, EntityDescriptor> entitesByName = Collections.synchronizedMap( new HashMap<String, EntityDescriptor>() );
60  	
61  	private MetadataManager() {}
62  
63  	public static EntityDescriptor getEntityDescriptor(Class clazz) {
64  		if (clazz != null && clazz.getName().contains("$$EnhancerByCGLIB")) {
65  			try {
66  				clazz = Class.forName(clazz.getName().substring(0, clazz.getName().indexOf("$$EnhancerByCGLIB")));
67  			} catch (Exception e) {
68  				LOG.error(e.getMessage(), e);
69  			}
70  		}
71  
72  		return addEntity(clazz);
73  	}
74  	
75  	public static Map<String, Object> getEntityPrimaryKeyValuePairs(Object object) {
76  		Map<String, Object> pks = new HashMap<String, Object>();
77  		EntityDescriptor descriptor = getEntityDescriptor(object.getClass());
78  		for (FieldDescriptor fieldDescriptor : descriptor.getPrimaryKeys()) {
79  			try {
80  				Field field = getField(object.getClass(), fieldDescriptor.getName());
81  				field.setAccessible(true);
82  				if (field.get(object) != null) {
83  					pks.put(fieldDescriptor.getName(), field.get(object));
84  				}
85  			} catch (Exception e) {
86  				LOG.error(e.getMessage(), e);
87  			}
88  		}
89  		return pks;
90  	}
91  	
92  	/**
93  	 * Retrieves the primary key as an object for the given Object (which is assumed to be a JPA entity).  If the entity has a single field
94  	 * primary key, the value of that field is returned.  If a composite key is needed, it will be constructed and populated with the correct
95  	 * values.  If a problem occurs, a null will be returned
96  	 * 
97  	 * @param object the object to get a primary key value from
98  	 * @return a primary key value
99  	 */
100 	public static Object getEntityPrimaryKeyObject(Object object) {
101 		final EntityDescriptor descriptor = getEntityDescriptor(object.getClass());
102 		final Class idClass = descriptor.getIdClass();
103 		if (idClass != null) {
104 			try {
105 				Object pkObject = idClass.newInstance();
106 				
107 				for (FieldDescriptor fieldDescriptor : descriptor.getPrimaryKeys()) {
108 					Field field = getField(object.getClass(), fieldDescriptor.getName());
109 					field.setAccessible(true);
110 					final Object value = field.get(object);
111 					if (value != null) {
112 						final Field fieldToSet = getField(pkObject.getClass(), fieldDescriptor.getName());
113 						fieldToSet.setAccessible(true);
114 						fieldToSet.set(pkObject, value);
115 					}
116 				}
117 				
118 				return pkObject;
119 			} catch (SecurityException se) {
120 				LOG.error(se.getMessage(), se);
121 			} catch (InstantiationException ie) {
122 				LOG.error(ie.getMessage(), ie);
123 			} catch (IllegalAccessException iae) {
124 				LOG.error(iae.getMessage(), iae);
125 			} catch (NoSuchFieldException nsfe) {
126 				LOG.error(nsfe.getMessage(), nsfe);
127 			}
128 		} else {
129 			for (FieldDescriptor fieldDescriptor : descriptor.getPrimaryKeys()) {
130 				try {
131 					Field field = getField(object.getClass(), fieldDescriptor.getName());
132 					field.setAccessible(true);
133 					return field.get(object);  // there's only one value, let's kick out
134 				} catch (Exception e) {
135 					LOG.error(e.getMessage(), e);
136 				}
137 			}
138 		}
139 		
140 		return null;
141 	}
142 	
143 	/**
144 	 * Retrieves the primary key as an object for the given Object (which is assumed to be a JPA entity), filling it with values from the extension.
145 	 * If the entity has a single field primary key, the value of that field from the extension is returned.  If a composite key is needed, it will be 
146 	 * constructed (based on the id class for the extension object) and populated with the correct values from the extension.  If a problem occurs, 
147 	 * a null will be returned.
148 	 * 
149 	 * @param owner the object to get values from
150 	 * @param extension the object to build a key for
151 	 * @return a primary key value
152 	 */
153 	public static Object getPersistableBusinessObjectPrimaryKeyObjectWithValuesForExtension(Object owner, Object extension) {
154 		final EntityDescriptor descriptor = getEntityDescriptor(extension.getClass());
155 		final Class idClass = descriptor.getIdClass();
156 		if (idClass != null) {
157 			try {
158 				Object pkObject = idClass.newInstance();
159 				
160 				for (FieldDescriptor fieldDescriptor : descriptor.getPrimaryKeys()) {
161 					Field field = getField(owner.getClass(), fieldDescriptor.getName());
162 					field.setAccessible(true);
163 					final Object value = field.get(owner);
164 					if (value != null) {
165 						final Field fieldToSet = getField(pkObject.getClass(), fieldDescriptor.getName());
166 						fieldToSet.setAccessible(true);
167 						fieldToSet.set(pkObject, value);
168 					}
169 				}
170 				
171 				return pkObject;
172 			} catch (SecurityException se) {
173 				LOG.error(se.getMessage(), se);
174 			} catch (InstantiationException ie) {
175 				LOG.error(ie.getMessage(), ie);
176 			} catch (IllegalAccessException iae) {
177 				LOG.error(iae.getMessage(), iae);
178 			} catch (NoSuchFieldException nsfe) {
179 				LOG.error(nsfe.getMessage(), nsfe);
180 			}
181 		} else {
182 			for (FieldDescriptor fieldDescriptor : descriptor.getPrimaryKeys()) {
183 				try {
184 					Field field = getField(owner.getClass(), fieldDescriptor.getName());
185 					field.setAccessible(true);
186 					final Object value = field.get(owner);
187 					return value;  // there's only one value, let's kick out
188 				} catch (Exception e) {
189 					LOG.error(e.getMessage(), e);
190 				}
191 			}
192 		}
193 		
194 		return null;
195 	}
196 	
197 	/**
198 	 * This converts a map of primary keys into an object: in the case of a single key, just the value object iself; in the case of a composite key,
199 	 * the correct composite key object populated with the values from the map
200 	 * @param entityClazz the class of the entity the pkMap was for
201 	 * @param pkMap the map of primary key fields and values
202 	 * @return the correct primary key object
203 	 */
204 	public static Object convertPrimaryKeyMapToObject(Class entityClazz, Map<String, Object> pkMap) {
205 		if (pkMap.isEmpty()) {
206 			return null;
207 		}
208 		
209 		final EntityDescriptor descriptor = getEntityDescriptor(entityClazz);
210 		final Class idClass = descriptor.getIdClass();
211 		
212 		if (idClass == null) {
213 			if (pkMap.size() != 1) {
214 				throw new IllegalArgumentException("pkMap has a size of "+pkMap.size()+"; but since entityClazz does not have a composite primary key, the size should be 1");
215 			}
216 			for (String key : pkMap.keySet()) {
217 				return pkMap.get(key);
218 			}
219 		} else {
220 			try {
221 				Object pkObject = idClass.newInstance();
222 				for (String key : pkMap.keySet()) {
223 					Field field = getField(idClass, key);
224 					field.setAccessible(true);
225 					field.set(pkObject, pkMap.get(key));
226 				}
227 				return pkObject;
228 			} catch (InstantiationException ie) {
229 				throw new RuntimeException("Could not convert primary key map to composite key object", ie);
230 			} catch (IllegalAccessException iae) {
231 				throw new RuntimeException("Could not convert primary key map to composite key object", iae);
232 			} catch (NoSuchFieldException nsfe) {
233 				throw new RuntimeException("Could not convert primary key map to composite key object", nsfe);
234 			}
235 		}
236 		return null;// I don't believe this code is reachable, but...you never know
237 	}
238 	
239 	private static Field getField(Class clazz, String name) throws NoSuchFieldException {
240 		if (clazz.equals(Object.class)) {
241 			throw new NoSuchFieldException(name);
242 		}
243 		Field field = null;
244 		try {
245 			field = clazz.getDeclaredField(name);
246 		} catch (Exception e) {}
247 		if (field == null) {
248 			field = getField(clazz.getSuperclass(), name);
249 		}
250 		return field;
251 	}
252 	
253 	private static EntityDescriptor addEntity(Class clazz) {
254 		EntityDescriptor entity = entitesByClass.get(clazz); 
255 		if (entity == null) {
256 			entity = construct(clazz);
257 			if (entity != null) {
258 				entitesByClass.put(entity.getClazz(), entity);
259 				entitesByName.put(entity.getName(), entity);
260 			}
261 		}
262 		return entity;
263 	}
264 
265 	@SuppressWarnings("unchecked")
266 	private static EntityDescriptor construct(Class clazz) {
267 		if (!clazz.isAnnotationPresent(Entity.class)) {
268 			return null;
269 		}
270 		
271 		// Determine the base entity metadata
272 		EntityDescriptor entityDescriptor = new EntityDescriptor();
273 		entityDescriptor.setClazz(clazz);
274 		String defaultName = clazz.getName().substring(clazz.getName().lastIndexOf(".") + 1);
275 		Entity entity = (Entity) clazz.getAnnotation(Entity.class);
276 		if (StringUtils.isBlank(entity.name())) {
277 			entityDescriptor.setName(defaultName);
278 		} else {
279 			entityDescriptor.setName(entity.name());
280 		}
281 		if (clazz.isAnnotationPresent(Table.class)) {
282 			Table table = (Table) clazz.getAnnotation(Table.class);
283 			entityDescriptor.setTable(table.name());
284 		} else {
285 			entityDescriptor.setTable(defaultName);
286 		}
287 		if (clazz.isAnnotationPresent(IdClass.class)) {
288 			entityDescriptor.setIdClass(((IdClass)clazz.getAnnotation(IdClass.class)).value());
289 		}
290 
291 		// Check for an "extension"
292 		try {
293 			Class extensionClass = Class.forName(clazz.getName() + "Extension");
294 			OneToOneDescriptor descriptor = new OneToOneDescriptor();
295 			descriptor.setCascade(new CascadeType[] { CascadeType.PERSIST });
296 			descriptor.setAttributeName("extension");
297 			descriptor.setTargetEntity(extensionClass);
298 			descriptor.setMappedBy("extension");
299 			EntityDescriptor extensionDescriptor = MetadataManager.getEntityDescriptor(extensionClass);
300 			for (FieldDescriptor fd : extensionDescriptor.getPrimaryKeys()) {
301 				descriptor.addFkField(fd.getName());
302 			}
303 			entityDescriptor.add(descriptor);
304 			FieldDescriptor extension = new FieldDescriptor();
305 			extension.setName("extension");
306 			extension.setClazz(extensionClass);
307 			entityDescriptor.add(extension);
308 		} catch (Exception e) {}
309 		
310 		
311 		List<Class> classes = new ArrayList<Class>();
312 		classes.add(clazz);
313 		Class c = clazz;
314 		while (!c.getSuperclass().equals(Object.class)) {
315 			c = c.getSuperclass();
316 			classes.add(c);
317 		}
318 		Collections.reverse(classes);
319 		
320 		// Determine the field/relationship metadata for all classes in the clazz hierarchy
321 		for (Class temp : classes) {
322 			extractFieldMetadata(temp, entityDescriptor);
323 			if (temp.isAnnotationPresent(AttributeOverrides.class)) {
324 				for (AttributeOverride override : ((AttributeOverrides)temp.getAnnotation(AttributeOverrides.class)).value()) {
325 					entityDescriptor.getFieldByName(override.name()).setColumn(override.column().name());
326 				}
327 			}
328 			if (temp.isAnnotationPresent(AttributeOverride.class)) {
329 				AttributeOverride override = (AttributeOverride) temp.getAnnotation(AttributeOverride.class);
330 				entityDescriptor.getFieldByName(override.name()).setColumn(override.column().name());					
331 			}
332 			//if (temp.isAnnotationPresent(AssociationOverrides.class)) {
333 			//	for (AssociationOverride override : ((AssociationOverrides)temp.getAnnotation(AssociationOverride.class)).value()) {
334 			//		entityDescriptor.getFieldByName(override.name()).;
335 			//	}
336 			//}
337 			//if (temp.isAnnotationPresent(AttributeOverride.class)) {
338 			//	AttributeOverride override = (AttributeOverride) temp.getAnnotation(AttributeOverride.class);
339 			//	entityDescriptor.getFieldByName(override.name()).setColumn(override.column().name());					
340 			//}
341 			
342 		}
343 				
344 		return entityDescriptor;
345 	}
346 
347 	
348 	private static void extractFieldMetadata(Class clazz, EntityDescriptor entityDescriptor) {
349     	// Don't want to get parent fields if overridden in children since we are walking the tree from child to parent
350 		Set<String> cachedFields = new HashSet<String>(); 
351 		do {
352 			for (Field field : clazz.getDeclaredFields()) {
353 				if (cachedFields.contains(field.getName())) {
354 					continue;
355 				}
356 				cachedFields.add(field.getName());
357 				
358 				int mods = field.getModifiers();
359 				if (Modifier.isFinal(mods) || Modifier.isStatic(mods) || Modifier.isTransient(mods) || field.isAnnotationPresent(Transient.class)) {
360 					continue;
361 				}
362 
363 				// Basic Fields
364 				FieldDescriptor fieldDescriptor = new FieldDescriptor();
365 				fieldDescriptor.setClazz(field.getType());
366 				fieldDescriptor.setTargetClazz(field.getType());
367 				fieldDescriptor.setName(field.getName());
368 				
369 				
370 				if (field.isAnnotationPresent(Id.class)) {
371 					fieldDescriptor.setId(true);
372 					
373 					if (entityDescriptor.getIdClass() != null) {
374 						// pull the column from IdClass
375 						try {
376 							Field idClassField = entityDescriptor.getIdClass().getDeclaredField(field.getName());
377 							idClassField.setAccessible(true);
378 							addColumnInformationToFieldDescriptor(fieldDescriptor, idClassField);
379 						} catch (Exception e) {
380 							e.printStackTrace();
381 						}
382 					}
383 				}
384 				if (field.isAnnotationPresent(Column.class)) {
385 					
386 					if (!field.isAnnotationPresent(Id.class) || entityDescriptor.getIdClass() == null) {
387 						// only populate if we haven't populated already
388 						addColumnInformationToFieldDescriptor(fieldDescriptor, field);
389 					}
390 				} else if (!field.isAnnotationPresent(Id.class) || entityDescriptor.getIdClass() == null) {
391 					fieldDescriptor.setColumn(field.getName());
392 				}
393 				if (field.isAnnotationPresent(Version.class)) {
394 					fieldDescriptor.setVersion(true);
395 				}
396 				if (field.isAnnotationPresent(Lob.class)) {
397 					fieldDescriptor.setLob(true);
398 				}
399 				if (field.isAnnotationPresent(Temporal.class)) {
400 					fieldDescriptor.setTemporal(true);
401 					fieldDescriptor.setTemporalType(field.getAnnotation(Temporal.class).value());
402 				}				
403 
404 				
405 				// Relationships
406 				if (field.isAnnotationPresent(OneToOne.class)) {
407 					OneToOneDescriptor descriptor = new OneToOneDescriptor();
408 					OneToOne relation = field.getAnnotation(OneToOne.class);
409 					descriptor.setAttributeName(field.getName());
410 					if (relation.targetEntity().equals(void.class)) {
411 						descriptor.setTargetEntity(field.getType());
412 					} else {
413 						descriptor.setTargetEntity(relation.targetEntity());
414 						fieldDescriptor.setTargetClazz(relation.targetEntity());
415 					}
416 					
417 					descriptor.setCascade(relation.cascade());
418 					descriptor.setFetch(relation.fetch());
419 					descriptor.setMappedBy(relation.mappedBy());
420 					descriptor.setOptional(relation.optional());
421 					if (field.isAnnotationPresent(JoinColumn.class)) {
422 						JoinColumn jc = field.getAnnotation(JoinColumn.class);
423 						descriptor.addJoinColumnDescriptor(constructJoinDescriptor(jc));
424 						FieldDescriptor jcFkField = entityDescriptor.getFieldByColumnName(jc.name());
425 						if (jcFkField != null) {
426 							descriptor.addFkField(jcFkField.getName());
427 						} else {
428 							//check to see if foreign key is in an AttributeOverride annotation
429 							if (clazz.isAnnotationPresent(AttributeOverrides.class)) {
430 								for (AttributeOverride override : ((AttributeOverrides)clazz.getAnnotation(AttributeOverrides.class)).value()) {
431 									if (jc.name().equals(override.column().name())) {
432 										entityDescriptor.getFieldByName(override.name()).setColumn(override.column().name());
433 										jcFkField = entityDescriptor.getFieldByName(override.name());
434 										if (jcFkField != null) {
435 											descriptor.addFkField(jcFkField.getName());
436 										}
437 									}
438 								}
439 							}
440 							if (clazz.isAnnotationPresent(AttributeOverride.class)) {
441 								AttributeOverride override = (AttributeOverride) clazz.getAnnotation(AttributeOverride.class);
442 								if (jc.name().equals(override.column().name())) {
443 									entityDescriptor.getFieldByName(override.name()).setColumn(override.column().name());
444 									jcFkField = entityDescriptor.getFieldByName(override.name());
445 									if (jcFkField != null) {
446 										descriptor.addFkField(jcFkField.getName());
447 									}
448 								}					
449 							}
450 						}
451 						//descriptor.addFkField(entityDescriptor.getFieldByColumnName(jc.name()).getName());
452 						descriptor.setInsertable(jc.insertable());
453 						descriptor.setUpdateable(jc.updatable());					
454 					}
455 					if (field.isAnnotationPresent(JoinColumns.class)) {
456 						JoinColumns jcs = field.getAnnotation(JoinColumns.class);
457 						for (JoinColumn jc : jcs.value()) {
458 							descriptor.addJoinColumnDescriptor(constructJoinDescriptor(jc));
459 							descriptor.addFkField(entityDescriptor.getFieldByColumnName(jc.name()).getName());
460 							descriptor.setInsertable(jc.insertable());
461 							descriptor.setUpdateable(jc.updatable());
462 						} 
463 					}
464 					entityDescriptor.add(descriptor);
465 				}
466 
467 				if (field.isAnnotationPresent(OneToMany.class)) {
468 					OneToManyDescriptor descriptor = new OneToManyDescriptor();
469 					OneToMany relation = field.getAnnotation(OneToMany.class);
470 					descriptor.setAttributeName(field.getName());
471 					if (relation.targetEntity().equals(void.class)) {
472 						descriptor.setTargetEntity((Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]);
473 					} else {
474 						descriptor.setTargetEntity(relation.targetEntity());
475 						fieldDescriptor.setTargetClazz(relation.targetEntity());
476 					}
477 					descriptor.setCascade(relation.cascade());
478 					descriptor.setFetch(relation.fetch());
479 					descriptor.setMappedBy(relation.mappedBy());
480 					EntityDescriptor mappedBy = (entityDescriptor.getClazz().equals(descriptor.getTargetEntity())) ?
481 							entityDescriptor : MetadataManager.getEntityDescriptor(descriptor.getTargetEntity());
482 					ObjectDescriptor od = mappedBy.getObjectDescriptorByName(descriptor.getMappedBy());
483 					if (od != null) {
484 						for (String fk : od.getForeignKeyFields()) {				
485 							descriptor.addFkField(fk);
486 						}
487 					}
488 					if (field.isAnnotationPresent(JoinTable.class)) {
489 						JoinTable jt = field.getAnnotation(JoinTable.class);
490 						for (JoinColumn jc : jt.joinColumns()) {
491 							descriptor.addFkField(entityDescriptor.getFieldByColumnName(jc.name()).getName());
492 							descriptor.setInsertable(jc.insertable());
493 							descriptor.setUpdateable(jc.updatable());
494 							descriptor.addJoinColumnDescriptor(constructJoinDescriptor(jc));
495 						} 
496 						for (JoinColumn jc : jt.inverseJoinColumns()) {
497 							descriptor.setInsertable(jc.insertable());
498 							descriptor.setUpdateable(jc.updatable());
499 							descriptor.addInverseJoinColumnDescriptor(constructJoinDescriptor(jc));
500 						} 
501 					} else {
502 						if (field.isAnnotationPresent(JoinColumn.class)) {
503 							JoinColumn jc = field.getAnnotation(JoinColumn.class);
504 							FieldDescriptor jcFkField = entityDescriptor.getFieldByColumnName(jc.name());
505 							if (jcFkField != null) {
506 								descriptor.addFkField(jcFkField.getName());
507 							}
508 							descriptor.setInsertable(jc.insertable());
509 							descriptor.setUpdateable(jc.updatable());
510 							descriptor.addJoinColumnDescriptor(constructJoinDescriptor(jc));
511 						}
512 						if (field.isAnnotationPresent(JoinColumns.class)) {
513 							JoinColumns jcs = field.getAnnotation(JoinColumns.class);
514 							for (JoinColumn jc : jcs.value()) {
515 								descriptor.addFkField(entityDescriptor.getFieldByColumnName(jc.name()).getName());
516 								descriptor.setInsertable(jc.insertable());
517 								descriptor.setUpdateable(jc.updatable());
518 								descriptor.addJoinColumnDescriptor(constructJoinDescriptor(jc));
519 							} 
520 						}
521 					}
522 					entityDescriptor.add(descriptor);
523 				}
524 
525 				if (field.isAnnotationPresent(ManyToOne.class)) {
526 					ManyToOne relation = field.getAnnotation(ManyToOne.class);
527 					ManyToOneDescriptor descriptor = new ManyToOneDescriptor();
528 					descriptor.setAttributeName(field.getName());
529 					if (relation.targetEntity().equals(void.class)) {
530 						descriptor.setTargetEntity(field.getType());
531 					} else {
532 						descriptor.setTargetEntity(relation.targetEntity());
533 						fieldDescriptor.setTargetClazz(relation.targetEntity());
534 					}
535 					descriptor.setCascade(relation.cascade());
536 					descriptor.setFetch(relation.fetch());
537 					descriptor.setOptional(relation.optional());
538 					if (field.isAnnotationPresent(JoinColumn.class)) {
539 						JoinColumn jc = field.getAnnotation(JoinColumn.class);
540 						descriptor.addJoinColumnDescriptor(constructJoinDescriptor(jc));
541 						FieldDescriptor jcFkField = entityDescriptor.getFieldByColumnName(jc.name());
542 						if (jcFkField != null) {
543 							descriptor.addFkField(jcFkField.getName());
544 						}
545 						//descriptor.addFkField(entityDescriptor.getFieldByColumnName(jc.name()).getName());
546 						//descriptor.addFkField(entitesByClass.get(field.getType()).getFieldByColumnName(jc.name()).getName());
547 						descriptor.setInsertable(jc.insertable());
548 						descriptor.setUpdateable(jc.updatable());
549 					}
550 					if (field.isAnnotationPresent(JoinColumns.class)) {
551 						JoinColumns jcs = field.getAnnotation(JoinColumns.class);
552 						for (JoinColumn jc : jcs.value()) {
553 							descriptor.addJoinColumnDescriptor(constructJoinDescriptor(jc));
554 							descriptor.addFkField(entityDescriptor.getFieldByColumnName(jc.name()).getName());
555 							descriptor.setInsertable(jc.insertable());
556 							descriptor.setUpdateable(jc.updatable());
557 						} 
558 					}
559 					entityDescriptor.add(descriptor);
560 				}
561 
562 				if (field.isAnnotationPresent(ManyToMany.class)) {
563 					ManyToManyDescriptor descriptor = new ManyToManyDescriptor();
564 					ManyToMany relation = field.getAnnotation(ManyToMany.class);
565 					descriptor.setAttributeName(field.getName());
566 					if (relation.targetEntity().equals(void.class)) {
567 						descriptor.setTargetEntity((Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]);
568 					} else {
569 						descriptor.setTargetEntity(relation.targetEntity());
570 						fieldDescriptor.setTargetClazz(relation.targetEntity());
571 					}
572 					descriptor.setCascade(relation.cascade());
573 					descriptor.setFetch(relation.fetch());
574 					descriptor.setMappedBy(relation.mappedBy());
575 					if (field.isAnnotationPresent(JoinTable.class)) {
576 						JoinTable jt = field.getAnnotation(JoinTable.class);
577 						descriptor.setJoinTableName(jt.name());
578 						for (JoinColumn jc : jt.joinColumns()) {
579 							descriptor.addJoinColumnDescriptor(constructJoinDescriptor(jc));
580 							descriptor.addFkField(entityDescriptor.getFieldByColumnName(jc.name()).getName());
581 							descriptor.setInsertable(jc.insertable());
582 							descriptor.setUpdateable(jc.updatable());
583 						} 
584 						for (JoinColumn jc : jt.inverseJoinColumns()) {
585 							descriptor.addInverseJoinColumnDescriptor(constructJoinDescriptor(jc));
586 							descriptor.setInsertable(jc.insertable());
587 							descriptor.setUpdateable(jc.updatable());
588 							// TODO: Should we add inverse join columns?
589 						} 
590 					} else {
591 						if (field.isAnnotationPresent(JoinColumn.class)) {
592 							JoinColumn jc = field.getAnnotation(JoinColumn.class);
593 							FieldDescriptor jcFkField = entityDescriptor.getFieldByColumnName(jc.name());
594 							if (jcFkField != null) {
595 								descriptor.addFkField(jcFkField.getName());
596 							}
597 							descriptor.addJoinColumnDescriptor(constructJoinDescriptor(jc));
598 							descriptor.setInsertable(jc.insertable());
599 							descriptor.setUpdateable(jc.updatable());
600 						}
601 						if (field.isAnnotationPresent(JoinColumns.class)) {
602 							JoinColumns jcs = field.getAnnotation(JoinColumns.class);
603 							for (JoinColumn jc : jcs.value()) {
604 								descriptor.addJoinColumnDescriptor(constructJoinDescriptor(jc));
605 								descriptor.addFkField(entityDescriptor.getFieldByColumnName(jc.name()).getName());
606 								descriptor.setInsertable(jc.insertable());
607 								descriptor.setUpdateable(jc.updatable());
608 							} 
609 						}
610 					}
611 					entityDescriptor.add(descriptor);						
612 				}
613 
614 				// Add the field to the entity
615 				entityDescriptor.add(fieldDescriptor);
616 			}
617 			clazz = clazz.getSuperclass();
618 		} while (clazz != null && !(clazz.equals(Object.class)));
619 	}
620 	
621 	/**
622 	 * Populate a FieldDescriptor with Column annotation information
623 	 * 
624 	 * @param fieldDescriptor the FieldDescriptor to populate
625 	 * @param field the field which has the annotation
626 	 */
627 	private static void addColumnInformationToFieldDescriptor(FieldDescriptor fieldDescriptor, Field field) {
628 		Column column = field.getAnnotation(Column.class);
629 		fieldDescriptor.setColumn(column.name());
630 		fieldDescriptor.setInsertable(column.insertable());
631 		fieldDescriptor.setLength(column.length());
632 		fieldDescriptor.setNullable(column.nullable());
633 		fieldDescriptor.setPrecision(column.precision());
634 		fieldDescriptor.setScale(column.scale());
635 		fieldDescriptor.setUnique(column.unique());
636 		fieldDescriptor.setUpdateable(column.updatable());
637 	}
638 
639 	private static JoinColumnDescriptor constructJoinDescriptor(JoinColumn jc) {
640 		JoinColumnDescriptor join = new JoinColumnDescriptor();
641 		if (StringUtils.isBlank(jc.name())) {
642 			// TODO: Implement default name
643 			// See: http://www.oracle.com/technology/products/ias/toplink/jpa/resources/toplink-jpa-annotations.html#JoinColumn
644 			throw new RuntimeException("Default name for Join Column not yet implemented!");
645 		} else {
646 			join.setName(jc.name());
647 		}
648 		join.setInsertable(jc.insertable());
649 		join.setNullable(jc.nullable());
650 		join.setUnique(jc.unique());
651 		join.setUpdateable(jc.updatable());
652 		join.setReferencedColumName(jc.referencedColumnName());
653 		return join;
654 	}
655 	
656 }