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.core.jpa.metadata;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.jpa.annotations.Sequence;
20  
21  import javax.persistence.*;
22  import java.lang.reflect.Field;
23  import java.lang.reflect.Modifier;
24  import java.lang.reflect.ParameterizedType;
25  import java.util.*;
26  
27  /**
28   * @author Kuali Rice Team (rice.collab@kuali.org)
29   */
30  public class MetadataManager {
31  
32  	private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MetadataManager.class);
33  	
34  	private static Map<Class, EntityDescriptor> entitesByClass = Collections.synchronizedMap( new HashMap<Class, EntityDescriptor>() );
35  	private static Map<String, EntityDescriptor> entitesByName = Collections.synchronizedMap( new HashMap<String, EntityDescriptor>() );
36  	
37  	private MetadataManager() {}
38  
39  	public static EntityDescriptor getEntityDescriptor(Class clazz) {
40  		if (clazz != null && clazz.getName().contains("$$EnhancerByCGLIB")) {
41  			try {
42  				clazz = Class.forName(clazz.getName().substring(0, clazz.getName().indexOf("$$EnhancerByCGLIB")));
43  			} catch (Exception e) {
44  				LOG.error(e.getMessage(), e);
45  			}
46  		}
47  
48  		EntityDescriptor entityDescriptor = addEntity(clazz);
49  		return entityDescriptor;
50  	}
51  	
52  	public static Map<String, Object> getPersistableBusinessObjectPrimaryKeyValuePairs(Object object) {
53  		Map<String, Object> pks = new HashMap<String, Object>();
54  		EntityDescriptor descriptor = getEntityDescriptor(object.getClass());
55  		for (FieldDescriptor fieldDescriptor : descriptor.getPrimaryKeys()) {
56  			try {
57  				Field field = getField(object.getClass(), fieldDescriptor.getName());
58  				field.setAccessible(true);
59  				pks.put(fieldDescriptor.getName(), field.get(object));
60  			} catch (Exception e) {
61  				LOG.error(e.getMessage(), e);
62  			}
63  		}
64  		return pks;
65  	}
66  	
67  	private static Field getField(Class clazz, String name) throws NoSuchFieldException {
68  		if (clazz.equals(Object.class)) {
69  			throw new NoSuchFieldException(name);
70  		}
71  		Field field = null;
72  		try {
73  			field = clazz.getDeclaredField(name);
74  		} catch (Exception e) {}
75  		if (field == null) {
76  			field = getField(clazz.getSuperclass(), name);
77  		}
78  		return field;
79  	}
80  	
81  	private static EntityDescriptor addEntity(Class clazz) {
82  		EntityDescriptor entity = entitesByClass.get(clazz); 
83  		if (entity == null) {
84  			entity = construct(clazz);
85  			if (entity != null) {
86  				entitesByClass.put(entity.getClazz(), entity);
87  				entitesByName.put(entity.getName(), entity);
88  			}
89  		}
90  		return entity;
91  	}
92  
93  	@SuppressWarnings("unchecked")
94  	private static EntityDescriptor construct(Class clazz) {
95  		if (!clazz.isAnnotationPresent(Entity.class)) {
96  			return null;
97  		}
98  		
99  		// Determine the base entity metadata
100 		EntityDescriptor entityDescriptor = new EntityDescriptor();
101 		entityDescriptor.setClazz(clazz);
102 		String defaultName = clazz.getName().substring(clazz.getName().lastIndexOf(".") + 1);
103 		Entity entity = (Entity) clazz.getAnnotation(Entity.class);
104 		if (StringUtils.isBlank(entity.name())) {
105 			entityDescriptor.setName(defaultName);
106 		} else {
107 			entityDescriptor.setName(entity.name());
108 		}
109 		if (clazz.isAnnotationPresent(Table.class)) {
110 			Table table = (Table) clazz.getAnnotation(Table.class);
111 			entityDescriptor.setTable(table.name());
112 		} else {
113 			entityDescriptor.setTable(defaultName);
114 		}
115 		if (clazz.isAnnotationPresent(IdClass.class)) {
116 			entityDescriptor.setIdClass(clazz.getAnnotation(IdClass.class).getClass());
117 		}
118 		if (clazz.isAnnotationPresent(Sequence.class)) {
119 			entityDescriptor.setSequence((Sequence)clazz.getAnnotation(Sequence.class));
120 		}		
121 		
122 		// Check for an "extension"
123 		try {
124 			Class extensionClass = Class.forName(clazz.getName() + "Extension");
125 			OneToOneDescriptor descriptor = new OneToOneDescriptor();
126 			descriptor.setCascade(new CascadeType[] { CascadeType.PERSIST });
127 			descriptor.setAttributeName("extension");
128 			descriptor.setTargetEntity(extensionClass);
129 			descriptor.setMappedBy("extension");
130 			EntityDescriptor extensionDescriptor = MetadataManager.getEntityDescriptor(extensionClass);
131 			for (FieldDescriptor fd : extensionDescriptor.getPrimaryKeys()) {
132 				descriptor.addFkField(fd.getName());
133 			}
134 			entityDescriptor.add(descriptor);
135 			FieldDescriptor extension = new FieldDescriptor();
136 			extension.setName("extension");
137 			extension.setClazz(extensionClass);
138 			entityDescriptor.add(extension);
139 		} catch (Exception e) {}
140 		
141 		
142 		List<Class> classes = new ArrayList<Class>();
143 		classes.add(clazz);
144 		Class c = clazz;
145 		while (!c.getSuperclass().equals(Object.class)) {
146 			c = c.getSuperclass();
147 			classes.add(c);
148 		}
149 		Collections.reverse(classes);
150 		
151 		// Determine the field/relationship metadata for all classes in the clazz hierarchy
152 		for (Class temp : classes) {
153 			extractFieldMetadata(temp, entityDescriptor);
154 			if (temp.isAnnotationPresent(AttributeOverrides.class)) {
155 				for (AttributeOverride override : ((AttributeOverrides)temp.getAnnotation(AttributeOverrides.class)).value()) {
156 					entityDescriptor.getFieldByName(override.name()).setColumn(override.column().name());
157 				}
158 			}
159 			if (temp.isAnnotationPresent(AttributeOverride.class)) {
160 				AttributeOverride override = (AttributeOverride) temp.getAnnotation(AttributeOverride.class);
161 				entityDescriptor.getFieldByName(override.name()).setColumn(override.column().name());					
162 			}
163 		}
164 				
165 		return entityDescriptor;
166 	}
167 
168 	private static void extractFieldMetadata(Class clazz, EntityDescriptor entityDescriptor) {
169     	// Don't want to get parent fields if overridden in children since we are walking the tree from child to parent
170 		Set<String> cachedFields = new HashSet<String>(); 
171 		do {
172 			for (Field field : clazz.getDeclaredFields()) {
173 				if (cachedFields.contains(field.getName())) {
174 					continue;
175 				}
176 				cachedFields.add(field.getName());
177 				
178 				int mods = field.getModifiers();
179 				if (Modifier.isFinal(mods) || Modifier.isStatic(mods) || Modifier.isTransient(mods) || field.isAnnotationPresent(Transient.class)) {
180 					continue;
181 				}
182 
183 				// Basic Fields
184 				FieldDescriptor fieldDescriptor = new FieldDescriptor();
185 				fieldDescriptor.setClazz(field.getType());
186 				fieldDescriptor.setName(field.getName());
187 				if (field.isAnnotationPresent(Id.class)) {
188 					fieldDescriptor.setId(true);
189 				}
190 				if (field.isAnnotationPresent(Column.class)) {
191 					Column column = field.getAnnotation(Column.class);
192 					fieldDescriptor.setColumn(column.name());
193 					fieldDescriptor.setInsertable(column.insertable());
194 					fieldDescriptor.setLength(column.length());
195 					fieldDescriptor.setNullable(column.nullable());
196 					fieldDescriptor.setPrecision(column.precision());
197 					fieldDescriptor.setScale(column.scale());
198 					fieldDescriptor.setUnique(column.unique());
199 					fieldDescriptor.setUpdateable(column.updatable());
200 				} else {
201 					fieldDescriptor.setColumn(field.getName());
202 				}
203 				if (field.isAnnotationPresent(Version.class)) {
204 					fieldDescriptor.setVersion(true);
205 				}
206 				if (field.isAnnotationPresent(Lob.class)) {
207 					fieldDescriptor.setLob(true);
208 				}
209 				if (field.isAnnotationPresent(Temporal.class)) {
210 					fieldDescriptor.setTemporal(true);
211 					fieldDescriptor.setTemporalType(field.getAnnotation(Temporal.class).value());
212 				}				
213 
214 				// Relationships
215 				if (field.isAnnotationPresent(OneToOne.class)) {
216 					OneToOneDescriptor descriptor = new OneToOneDescriptor();
217 					OneToOne relation = field.getAnnotation(OneToOne.class);
218 					descriptor.setAttributeName(field.getName());
219 					if (relation.targetEntity().equals(void.class)) {
220 						descriptor.setTargetEntity(field.getType());
221 					} else {
222 						descriptor.setTargetEntity(relation.targetEntity());
223 					}
224 					descriptor.setCascade(relation.cascade());
225 					descriptor.setFetch(relation.fetch());
226 					descriptor.setMappedBy(relation.mappedBy());
227 					descriptor.setOptional(relation.optional());
228 					if (field.isAnnotationPresent(JoinColumn.class)) {
229 						JoinColumn jc = field.getAnnotation(JoinColumn.class);
230 						descriptor.addJoinColumnDescriptor(constructJoinDescriptor(jc));
231 						descriptor.addFkField(entityDescriptor.getFieldByColumnName(jc.name()).getName());
232 						descriptor.setInsertable(jc.insertable());
233 						descriptor.setUpdateable(jc.updatable());					
234 					}
235 					if (field.isAnnotationPresent(JoinColumns.class)) {
236 						JoinColumns jcs = field.getAnnotation(JoinColumns.class);
237 						for (JoinColumn jc : jcs.value()) {
238 							descriptor.addJoinColumnDescriptor(constructJoinDescriptor(jc));
239 							descriptor.addFkField(entityDescriptor.getFieldByColumnName(jc.name()).getName());
240 							descriptor.setInsertable(jc.insertable());
241 							descriptor.setUpdateable(jc.updatable());
242 						} 
243 					}
244 					entityDescriptor.add(descriptor);
245 				}
246 
247 				if (field.isAnnotationPresent(OneToMany.class)) {
248 					OneToManyDescriptor descriptor = new OneToManyDescriptor();
249 					OneToMany relation = field.getAnnotation(OneToMany.class);
250 					descriptor.setAttributeName(field.getName());
251 					if (relation.targetEntity().equals(void.class)) {
252 						descriptor.setTargetEntity((Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]);
253 					} else {
254 						descriptor.setTargetEntity(relation.targetEntity());
255 					}
256 					descriptor.setCascade(relation.cascade());
257 					descriptor.setFetch(relation.fetch());
258 					descriptor.setMappedBy(relation.mappedBy());
259 					EntityDescriptor mappedBy = MetadataManager.getEntityDescriptor(descriptor.getTargetEntity());
260 					ObjectDescriptor od = mappedBy.getObjectDescriptorByName(descriptor.getMappedBy());
261 					if (od != null) {
262 						for (String fk : od.getForeignKeyFields()) {				
263 							descriptor.addFkField(fk);
264 						}
265 					}
266 					if (field.isAnnotationPresent(JoinTable.class)) {
267 						JoinTable jt = field.getAnnotation(JoinTable.class);
268 						for (JoinColumn jc : jt.joinColumns()) {
269 							descriptor.addFkField(entityDescriptor.getFieldByColumnName(jc.name()).getName());
270 							descriptor.setInsertable(jc.insertable());
271 							descriptor.setUpdateable(jc.updatable());
272 						} 
273 						for (JoinColumn jc : jt.inverseJoinColumns()) {
274 							descriptor.setInsertable(jc.insertable());
275 							descriptor.setUpdateable(jc.updatable());
276 							// TODO: Should we add inverse join columns?
277 						} 
278 					}
279 					entityDescriptor.add(descriptor);
280 				}
281 
282 				if (field.isAnnotationPresent(ManyToOne.class)) {
283 					ManyToOne relation = field.getAnnotation(ManyToOne.class);
284 					ManyToOneDescriptor descriptor = new ManyToOneDescriptor();
285 					descriptor.setAttributeName(field.getName());
286 					if (relation.targetEntity().equals(void.class)) {
287 						descriptor.setTargetEntity(field.getType());
288 					} else {
289 						descriptor.setTargetEntity(relation.targetEntity());
290 					}
291 					descriptor.setCascade(relation.cascade());
292 					descriptor.setFetch(relation.fetch());
293 					descriptor.setOptional(relation.optional());
294 					if (field.isAnnotationPresent(JoinColumn.class)) {
295 						JoinColumn jc = field.getAnnotation(JoinColumn.class);
296 						descriptor.addJoinColumnDescriptor(constructJoinDescriptor(jc));
297 						descriptor.addFkField(entityDescriptor.getFieldByColumnName(jc.name()).getName());
298 						descriptor.setInsertable(jc.insertable());
299 						descriptor.setUpdateable(jc.updatable());
300 					}
301 					if (field.isAnnotationPresent(JoinColumns.class)) {
302 						JoinColumns jcs = field.getAnnotation(JoinColumns.class);
303 						for (JoinColumn jc : jcs.value()) {
304 							descriptor.addJoinColumnDescriptor(constructJoinDescriptor(jc));
305 							descriptor.addFkField(entityDescriptor.getFieldByColumnName(jc.name()).getName());
306 							descriptor.setInsertable(jc.insertable());
307 							descriptor.setUpdateable(jc.updatable());
308 						} 
309 					}
310 					entityDescriptor.add(descriptor);
311 				}
312 
313 				if (field.isAnnotationPresent(ManyToMany.class)) {
314 					ManyToManyDescriptor descriptor = new ManyToManyDescriptor();
315 					ManyToMany relation = field.getAnnotation(ManyToMany.class);
316 					descriptor.setAttributeName(field.getName());
317 					if (relation.targetEntity().equals(void.class)) {
318 						descriptor.setTargetEntity((Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]);
319 					} else {
320 						descriptor.setTargetEntity(relation.targetEntity());
321 					}
322 					descriptor.setCascade(relation.cascade());
323 					descriptor.setFetch(relation.fetch());
324 					descriptor.setMappedBy(relation.mappedBy());
325 					if (field.isAnnotationPresent(JoinTable.class)) {
326 						JoinTable jt = field.getAnnotation(JoinTable.class);
327 						descriptor.setJoinTableName(jt.name());
328 						for (JoinColumn jc : jt.joinColumns()) {
329 							descriptor.addJoinColumnDescriptor(constructJoinDescriptor(jc));
330 							descriptor.addFkField(entityDescriptor.getFieldByColumnName(jc.name()).getName());
331 							descriptor.setInsertable(jc.insertable());
332 							descriptor.setUpdateable(jc.updatable());
333 						} 
334 						for (JoinColumn jc : jt.inverseJoinColumns()) {
335 							descriptor.addInverseJoinColumnDescriptor(constructJoinDescriptor(jc));
336 							descriptor.setInsertable(jc.insertable());
337 							descriptor.setUpdateable(jc.updatable());
338 							// TODO: Should we add inverse join columns?
339 						} 
340 					}
341 					entityDescriptor.add(descriptor);						
342 				}
343 
344 				// Add the field to the entity
345 				entityDescriptor.add(fieldDescriptor);
346 			}
347 			clazz = clazz.getSuperclass();
348 		} while (clazz != null && !(clazz.equals(Object.class)));
349 	}
350 
351 	private static JoinColumnDescriptor constructJoinDescriptor(JoinColumn jc) {
352 		JoinColumnDescriptor join = new JoinColumnDescriptor();
353 		if (StringUtils.isBlank(jc.name())) {
354 			// TODO: Implement default name
355 			// See: http://www.oracle.com/technology/products/ias/toplink/jpa/resources/toplink-jpa-annotations.html#JoinColumn
356 			throw new RuntimeException("Default name for Join Column not yet implemented!");
357 		} else {
358 			join.setName(jc.name());
359 		}
360 		join.setInsertable(jc.insertable());
361 		join.setNullable(jc.nullable());
362 		join.setUnique(jc.unique());
363 		join.setUpdateable(jc.updatable());
364 		return join;
365 	}
366 	
367 }