KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sleepycat > persist > model > AnnotationModel


1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002,2006 Oracle. All rights reserved.
5  *
6  * $Id: AnnotationModel.java,v 1.16 2006/10/30 21:14:33 bostic Exp $
7  */

8
9 package com.sleepycat.persist.model;
10
11 import java.lang.reflect.Field JavaDoc;
12 import java.lang.reflect.Modifier JavaDoc;
13 import java.lang.reflect.ParameterizedType JavaDoc;
14 import java.lang.reflect.Type JavaDoc;
15 import java.util.ArrayList JavaDoc;
16 import java.util.Collections JavaDoc;
17 import java.util.HashMap JavaDoc;
18 import java.util.HashSet JavaDoc;
19 import java.util.List JavaDoc;
20 import java.util.Map JavaDoc;
21 import java.util.Set JavaDoc;
22
23 /**
24  * The default annotation-based entity model. An <code>AnnotationModel</code>
25  * is based on annotations that are specified for entity classes and their key
26  * fields.
27  *
28  * <p>{@code AnnotationModel} objects are thread-safe. Multiple threads may
29  * safely call the methods of a shared {@code AnnotationModel} object.</p>
30  *
31  * <p>The set of persistent classes in the annotation model is the set of all
32  * classes with the {@link Persistent} or {@link Entity} annotation.</p>
33  *
34  * <p>The annotations used to define persistent classes are: {@link Entity},
35  * {@link Persistent}, {@link PrimaryKey}, {@link SecondaryKey} and {@link
36  * KeyField}. A good starting point is {@link Entity}.</p>
37  *
38  * @author Mark Hayes
39  */

40 public class AnnotationModel extends EntityModel {
41
42     private static class EntityInfo {
43         PrimaryKeyMetadata priKey;
44         Map JavaDoc<String JavaDoc,SecondaryKeyMetadata> secKeys =
45             new HashMap JavaDoc<String JavaDoc,SecondaryKeyMetadata>();
46     }
47
48     private Map JavaDoc<String JavaDoc,ClassMetadata> classMap;
49     private Map JavaDoc<String JavaDoc,EntityInfo> entityMap;
50
51     /**
52      * Constructs a model for annotated entity classes.
53      */

54     public AnnotationModel() {
55         super();
56         classMap = new HashMap JavaDoc<String JavaDoc,ClassMetadata>();
57         entityMap = new HashMap JavaDoc<String JavaDoc,EntityInfo>();
58     }
59
60     /* EntityModel methods */
61
62     @Override JavaDoc
63     public synchronized Set JavaDoc<String JavaDoc> getKnownClasses() {
64         return Collections.unmodifiableSet
65             (new HashSet JavaDoc<String JavaDoc>(classMap.keySet()));
66     }
67
68     @Override JavaDoc
69     public synchronized EntityMetadata getEntityMetadata(String JavaDoc className) {
70         /* Call getClassMetadata to collect metadata. */
71         getClassMetadata(className);
72         /* Return the collected entity metadata. */
73         EntityInfo info = entityMap.get(className);
74         if (info != null) {
75             return new EntityMetadata
76                 (className, info.priKey,
77                  Collections.unmodifiableMap(info.secKeys));
78         } else {
79             return null;
80         }
81     }
82
83     @Override JavaDoc
84     public synchronized ClassMetadata getClassMetadata(String JavaDoc className) {
85         ClassMetadata metadata = classMap.get(className);
86         if (metadata == null) {
87             Class JavaDoc<?> type;
88             try {
89                 type = Class.forName(className);
90             } catch (ClassNotFoundException JavaDoc e) {
91                 return null;
92             }
93             /* Get class annotation. */
94             Entity entity = type.getAnnotation(Entity.class);
95             Persistent persistent = type.getAnnotation(Persistent.class);
96             if (entity == null && persistent == null) {
97                 return null;
98             }
99             if (entity != null && persistent != null) {
100                 throw new IllegalArgumentException JavaDoc
101                     ("Both @Entity and @Persistent are not allowed: " +
102                      type.getName());
103             }
104             boolean isEntity;
105             int version;
106             String JavaDoc proxiedClassName;
107             if (entity != null) {
108                 isEntity = true;
109                 version = entity.version();
110                 proxiedClassName = null;
111             } else {
112                 isEntity = false;
113                 version = persistent.version();
114                 Class JavaDoc proxiedClass = persistent.proxyFor();
115                 proxiedClassName = (proxiedClass != void.class) ?
116                                     proxiedClass.getName() : null;
117             }
118             /* Get instance fields. */
119             List JavaDoc<Field JavaDoc> fields = new ArrayList JavaDoc<Field JavaDoc>();
120             for (Field JavaDoc field : type.getDeclaredFields()) {
121                 int mods = field.getModifiers();
122                 if (!Modifier.isTransient(mods) && !Modifier.isStatic(mods)) {
123                     fields.add(field);
124                 }
125             }
126             /* Get the rest of the metadata and save it. */
127             metadata = new ClassMetadata
128                 (className, version, proxiedClassName, isEntity,
129                  getPrimaryKey(type, fields),
130                  getSecondaryKeys(type, fields),
131                  getCompositeKeyFields(type, fields));
132             classMap.put(className, metadata);
133             /* Add any new information about entities. */
134             updateEntityInfo(metadata);
135         }
136         return metadata;
137     }
138
139     private PrimaryKeyMetadata getPrimaryKey(Class JavaDoc<?> type,
140                                              List JavaDoc<Field JavaDoc> fields) {
141         Field JavaDoc foundField = null;
142         String JavaDoc sequence = null;
143         for (Field JavaDoc field : fields) {
144             PrimaryKey priKey = field.getAnnotation(PrimaryKey.class);
145             if (priKey != null) {
146                 if (foundField != null) {
147                     throw new IllegalArgumentException JavaDoc
148                         ("Only one @PrimaryKey allowed: " + type.getName());
149                 } else {
150                     foundField = field;
151                     sequence = priKey.sequence();
152                     if (sequence.length() == 0) {
153                         sequence = null;
154                     }
155                 }
156             }
157         }
158         if (foundField != null) {
159             return new PrimaryKeyMetadata
160                 (foundField.getName(), foundField.getType().getName(),
161                  type.getName(), sequence);
162         } else {
163             return null;
164         }
165     }
166
167     private Map JavaDoc<String JavaDoc,SecondaryKeyMetadata> getSecondaryKeys(Class JavaDoc<?> type,
168                                                          List JavaDoc<Field JavaDoc> fields) {
169         Map JavaDoc<String JavaDoc,SecondaryKeyMetadata> map = null;
170         for (Field JavaDoc field : fields) {
171             SecondaryKey secKey = field.getAnnotation(SecondaryKey.class);
172             if (secKey != null) {
173                 Relationship rel = secKey.relate();
174                 String JavaDoc elemClassName = null;
175                 if (rel == Relationship.ONE_TO_MANY ||
176                     rel == Relationship.MANY_TO_MANY) {
177                     elemClassName = getElementClass(field);
178                 }
179                 String JavaDoc keyName = secKey.name();
180                 if (keyName.length() == 0) {
181                     keyName = field.getName();
182                 }
183                 Class JavaDoc<?> relatedClass = secKey.relatedEntity();
184                 String JavaDoc relatedEntity = (relatedClass != void.class) ?
185                                         relatedClass.getName() : null;
186                 DeleteAction deleteAction = (relatedEntity != null) ?
187                                         secKey.onRelatedEntityDelete() : null;
188                 SecondaryKeyMetadata metadata = new SecondaryKeyMetadata
189                     (field.getName(), field.getType().getName(),
190                      type.getName(), elemClassName, keyName, rel,
191                      relatedEntity, deleteAction);
192                 if (map == null) {
193                     map = new HashMap JavaDoc<String JavaDoc,SecondaryKeyMetadata>();
194                 }
195                 if (map.put(keyName, metadata) != null) {
196                     throw new IllegalArgumentException JavaDoc
197                         ("Only one @SecondaryKey with the same name allowed: "
198                          + type.getName() + '.' + keyName);
199                 }
200             }
201         }
202         if (map != null) {
203             map = Collections.unmodifiableMap(map);
204         }
205         return map;
206     }
207
208     private String JavaDoc getElementClass(Field JavaDoc field) {
209         Class JavaDoc cls = field.getType();
210         if (cls.isArray()) {
211             return cls.getComponentType().getName();
212         }
213         if (java.util.Collection JavaDoc.class.isAssignableFrom(cls)) {
214             Type JavaDoc[] typeArgs = ((ParameterizedType JavaDoc) field.getGenericType()).
215                 getActualTypeArguments();
216             if (typeArgs == null ||
217                 typeArgs.length != 1 ||
218                 !(typeArgs[0] instanceof Class JavaDoc)) {
219                 throw new IllegalArgumentException JavaDoc
220                     ("Collection typed secondary key field must have a" +
221                      " single generic type argument: " +
222                      field.getDeclaringClass().getName() + '.' +
223                      field.getName());
224             }
225             return ((Class JavaDoc) typeArgs[0]).getName();
226         }
227         throw new IllegalArgumentException JavaDoc
228             ("ONE_TO_MANY or MANY_TO_MANY secondary key field must have" +
229              " an array or Collection type: " +
230              field.getDeclaringClass().getName() + '.' + field.getName());
231     }
232
233     private List JavaDoc<FieldMetadata> getCompositeKeyFields(Class JavaDoc<?> type,
234                                                       List JavaDoc<Field JavaDoc> fields) {
235         List JavaDoc<FieldMetadata> list = null;
236         for (Field JavaDoc field : fields) {
237             KeyField keyField = field.getAnnotation(KeyField.class);
238             if (keyField != null) {
239                 int value = keyField.value();
240                 if (value < 1 || value > fields.size()) {
241                     throw new IllegalArgumentException JavaDoc
242                         ("Unreasonable @KeyField index value " + value +
243                          ": " + type.getName());
244                 }
245                 if (list == null) {
246                     list = new ArrayList JavaDoc<FieldMetadata>(fields.size());
247                 }
248                 if (value <= list.size() && list.get(value - 1) != null) {
249                     throw new IllegalArgumentException JavaDoc
250                         ("@KeyField index value " + value +
251                          " is used more than once: " + type.getName());
252                 }
253                 while (value > list.size()) {
254                     list.add(null);
255                 }
256                 FieldMetadata metadata = new FieldMetadata
257                     (field.getName(), field.getType().getName(),
258                      type.getName());
259                 list.set(value - 1, metadata);
260             }
261         }
262         if (list != null) {
263             if (list.size() < fields.size()) {
264                 throw new IllegalArgumentException JavaDoc
265                     ("@KeyField is missing on one or more instance fields: " +
266                      type.getName());
267             }
268             for (int i = 0; i < list.size(); i += 1) {
269                 if (list.get(i) == null) {
270                     throw new IllegalArgumentException JavaDoc
271                         ("@KeyField is missing for index value " + (i + 1) +
272                          ": " + type.getName());
273                 }
274             }
275         }
276         if (list != null) {
277             list = Collections.unmodifiableList(list);
278         }
279         return list;
280     }
281     
282     /**
283      * Add newly discovered metadata to our stash of entity info. This info
284      * is maintained as it is discovered because it would be expensive to
285      * create it on demand -- all class metadata would have to be traversed.
286      */

287     private void updateEntityInfo(ClassMetadata metadata) {
288
289         /*
290          * Find out whether this class or its superclass is an entity. In the
291          * process, traverse all superclasses to load their metadata -- this
292          * will populate as much entity info as possible.
293          */

294         String JavaDoc entityClass = null;
295         PrimaryKeyMetadata priKey = null;
296         Map JavaDoc<String JavaDoc,SecondaryKeyMetadata> secKeys =
297             new HashMap JavaDoc<String JavaDoc,SecondaryKeyMetadata>();
298         for (ClassMetadata data = metadata; data != null;) {
299             if (data.isEntityClass()) {
300                 if (entityClass != null) {
301                     throw new IllegalArgumentException JavaDoc
302                         ("An entity class may not derived from another" +
303                          " entity class: " + entityClass +
304                          ' ' + data.getClassName());
305                 }
306                 entityClass = data.getClassName();
307             }
308             /* Save first primary key encountered. */
309             if (priKey == null) {
310                 priKey = data.getPrimaryKey();
311             }
312             /* Save all secondary keys encountered by key name. */
313             Map JavaDoc<String JavaDoc,SecondaryKeyMetadata> classSecKeys =
314                 data.getSecondaryKeys();
315             if (classSecKeys != null) {
316                 for (SecondaryKeyMetadata secKey : classSecKeys.values()) {
317                     secKeys.put(secKey.getKeyName(), secKey);
318                 }
319             }
320             /* Load superclass metadata. */
321             Class JavaDoc cls;
322             try {
323                 cls = Class.forName(data.getClassName());
324             } catch (ClassNotFoundException JavaDoc e) {
325                 throw new IllegalStateException JavaDoc(e);
326             }
327             cls = cls.getSuperclass();
328             if (cls != Object JavaDoc.class) {
329                 data = getClassMetadata(cls.getName());
330                 if (data == null) {
331                     throw new IllegalArgumentException JavaDoc
332                         ("Persistent class has non-persistent superclass: " +
333                          cls.getName());
334                 }
335             } else {
336                 data = null;
337             }
338         }
339
340         /* Add primary and secondary key entity info. */
341         if (entityClass != null) {
342             EntityInfo info = entityMap.get(entityClass);
343             if (info == null) {
344                 info = new EntityInfo();
345                 entityMap.put(entityClass, info);
346             }
347             if (priKey == null) {
348                 throw new IllegalArgumentException JavaDoc
349                     ("Entity class has no primary key: " + entityClass);
350             }
351             info.priKey = priKey;
352             info.secKeys.putAll(secKeys);
353         }
354     }
355 }
356
Popular Tags