001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2002,2008 Oracle. All rights reserved.
005: *
006: * $Id: AnnotationModel.java,v 1.16.2.4 2008/01/07 15:14:20 cwl Exp $
007: */
008:
009: package com.sleepycat.persist.model;
010:
011: import java.lang.reflect.Field;
012: import java.lang.reflect.Modifier;
013: import java.lang.reflect.ParameterizedType;
014: import java.lang.reflect.Type;
015: import java.util.ArrayList;
016: import java.util.Collections;
017: import java.util.HashMap;
018: import java.util.HashSet;
019: import java.util.List;
020: import java.util.Map;
021: import java.util.Set;
022:
023: /**
024: * The default annotation-based entity model. An <code>AnnotationModel</code>
025: * is based on annotations that are specified for entity classes and their key
026: * fields.
027: *
028: * <p>{@code AnnotationModel} objects are thread-safe. Multiple threads may
029: * safely call the methods of a shared {@code AnnotationModel} object.</p>
030: *
031: * <p>The set of persistent classes in the annotation model is the set of all
032: * classes with the {@link Persistent} or {@link Entity} annotation.</p>
033: *
034: * <p>The annotations used to define persistent classes are: {@link Entity},
035: * {@link Persistent}, {@link PrimaryKey}, {@link SecondaryKey} and {@link
036: * KeyField}. A good starting point is {@link Entity}.</p>
037: *
038: * @author Mark Hayes
039: */
040: public class AnnotationModel extends EntityModel {
041:
042: private static class EntityInfo {
043: PrimaryKeyMetadata priKey;
044: Map<String, SecondaryKeyMetadata> secKeys = new HashMap<String, SecondaryKeyMetadata>();
045: }
046:
047: private Map<String, ClassMetadata> classMap;
048: private Map<String, EntityInfo> entityMap;
049:
050: /**
051: * Constructs a model for annotated entity classes.
052: */
053: public AnnotationModel() {
054: super ();
055: classMap = new HashMap<String, ClassMetadata>();
056: entityMap = new HashMap<String, EntityInfo>();
057: }
058:
059: /* EntityModel methods */
060:
061: @Override
062: public synchronized Set<String> getKnownClasses() {
063: return Collections.unmodifiableSet(new HashSet<String>(classMap
064: .keySet()));
065: }
066:
067: @Override
068: public synchronized EntityMetadata getEntityMetadata(
069: String className) {
070: /* Call getClassMetadata to collect metadata. */
071: getClassMetadata(className);
072: /* Return the collected entity metadata. */
073: EntityInfo info = entityMap.get(className);
074: if (info != null) {
075: return new EntityMetadata(className, info.priKey,
076: Collections.unmodifiableMap(info.secKeys));
077: } else {
078: return null;
079: }
080: }
081:
082: @Override
083: public synchronized ClassMetadata getClassMetadata(String className) {
084: ClassMetadata metadata = classMap.get(className);
085: if (metadata == null) {
086: Class<?> type;
087: try {
088: type = EntityModel.classForName(className);
089: } catch (ClassNotFoundException e) {
090: return null;
091: }
092: /* Get class annotation. */
093: Entity entity = type.getAnnotation(Entity.class);
094: Persistent persistent = type
095: .getAnnotation(Persistent.class);
096: if (entity == null && persistent == null) {
097: return null;
098: }
099: if (entity != null && persistent != null) {
100: throw new IllegalArgumentException(
101: "Both @Entity and @Persistent are not allowed: "
102: + type.getName());
103: }
104: boolean isEntity;
105: int version;
106: String 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 proxiedClass = persistent.proxyFor();
115: proxiedClassName = (proxiedClass != void.class) ? proxiedClass
116: .getName()
117: : null;
118: }
119: /* Get instance fields. */
120: List<Field> fields = new ArrayList<Field>();
121: for (Field field : type.getDeclaredFields()) {
122: int mods = field.getModifiers();
123: if (!Modifier.isTransient(mods)
124: && !Modifier.isStatic(mods)) {
125: fields.add(field);
126: }
127: }
128: /* Get the rest of the metadata and save it. */
129: metadata = new ClassMetadata(className, version,
130: proxiedClassName, isEntity, getPrimaryKey(type,
131: fields), getSecondaryKeys(type, fields),
132: getCompositeKeyFields(type, fields));
133: classMap.put(className, metadata);
134: /* Add any new information about entities. */
135: updateEntityInfo(metadata);
136: }
137: return metadata;
138: }
139:
140: private PrimaryKeyMetadata getPrimaryKey(Class<?> type,
141: List<Field> fields) {
142: Field foundField = null;
143: String sequence = null;
144: for (Field field : fields) {
145: PrimaryKey priKey = field.getAnnotation(PrimaryKey.class);
146: if (priKey != null) {
147: if (foundField != null) {
148: throw new IllegalArgumentException(
149: "Only one @PrimaryKey allowed: "
150: + type.getName());
151: } else {
152: foundField = field;
153: sequence = priKey.sequence();
154: if (sequence.length() == 0) {
155: sequence = null;
156: }
157: }
158: }
159: }
160: if (foundField != null) {
161: return new PrimaryKeyMetadata(foundField.getName(),
162: foundField.getType().getName(), type.getName(),
163: sequence);
164: } else {
165: return null;
166: }
167: }
168:
169: private Map<String, SecondaryKeyMetadata> getSecondaryKeys(
170: Class<?> type, List<Field> fields) {
171: Map<String, SecondaryKeyMetadata> map = null;
172: for (Field field : fields) {
173: SecondaryKey secKey = field
174: .getAnnotation(SecondaryKey.class);
175: if (secKey != null) {
176: Relationship rel = secKey.relate();
177: String elemClassName = null;
178: if (rel == Relationship.ONE_TO_MANY
179: || rel == Relationship.MANY_TO_MANY) {
180: elemClassName = getElementClass(field);
181: }
182: String keyName = secKey.name();
183: if (keyName.length() == 0) {
184: keyName = field.getName();
185: }
186: Class<?> relatedClass = secKey.relatedEntity();
187: String relatedEntity = (relatedClass != void.class) ? relatedClass
188: .getName()
189: : null;
190: DeleteAction deleteAction = (relatedEntity != null) ? secKey
191: .onRelatedEntityDelete()
192: : null;
193: SecondaryKeyMetadata metadata = new SecondaryKeyMetadata(
194: field.getName(), field.getType().getName(),
195: type.getName(), elemClassName, keyName, rel,
196: relatedEntity, deleteAction);
197: if (map == null) {
198: map = new HashMap<String, SecondaryKeyMetadata>();
199: }
200: if (map.put(keyName, metadata) != null) {
201: throw new IllegalArgumentException(
202: "Only one @SecondaryKey with the same name allowed: "
203: + type.getName() + '.' + keyName);
204: }
205: }
206: }
207: if (map != null) {
208: map = Collections.unmodifiableMap(map);
209: }
210: return map;
211: }
212:
213: private String getElementClass(Field field) {
214: Class cls = field.getType();
215: if (cls.isArray()) {
216: return cls.getComponentType().getName();
217: }
218: if (java.util.Collection.class.isAssignableFrom(cls)) {
219: Type[] typeArgs = ((ParameterizedType) field
220: .getGenericType()).getActualTypeArguments();
221: if (typeArgs == null || typeArgs.length != 1
222: || !(typeArgs[0] instanceof Class)) {
223: throw new IllegalArgumentException(
224: "Collection typed secondary key field must have a"
225: + " single generic type argument: "
226: + field.getDeclaringClass().getName()
227: + '.' + field.getName());
228: }
229: return ((Class) typeArgs[0]).getName();
230: }
231: throw new IllegalArgumentException(
232: "ONE_TO_MANY or MANY_TO_MANY secondary key field must have"
233: + " an array or Collection type: "
234: + field.getDeclaringClass().getName() + '.'
235: + field.getName());
236: }
237:
238: private List<FieldMetadata> getCompositeKeyFields(Class<?> type,
239: List<Field> fields) {
240: List<FieldMetadata> list = null;
241: for (Field field : fields) {
242: KeyField keyField = field.getAnnotation(KeyField.class);
243: if (keyField != null) {
244: int value = keyField.value();
245: if (value < 1 || value > fields.size()) {
246: throw new IllegalArgumentException(
247: "Unreasonable @KeyField index value "
248: + value + ": " + type.getName());
249: }
250: if (list == null) {
251: list = new ArrayList<FieldMetadata>(fields.size());
252: }
253: if (value <= list.size() && list.get(value - 1) != null) {
254: throw new IllegalArgumentException(
255: "@KeyField index value " + value
256: + " is used more than once: "
257: + type.getName());
258: }
259: while (value > list.size()) {
260: list.add(null);
261: }
262: FieldMetadata metadata = new FieldMetadata(field
263: .getName(), field.getType().getName(), type
264: .getName());
265: list.set(value - 1, metadata);
266: }
267: }
268: if (list != null) {
269: if (list.size() < fields.size()) {
270: throw new IllegalArgumentException(
271: "@KeyField is missing on one or more instance fields: "
272: + type.getName());
273: }
274: for (int i = 0; i < list.size(); i += 1) {
275: if (list.get(i) == null) {
276: throw new IllegalArgumentException(
277: "@KeyField is missing for index value "
278: + (i + 1) + ": " + type.getName());
279: }
280: }
281: }
282: if (list != null) {
283: list = Collections.unmodifiableList(list);
284: }
285: return list;
286: }
287:
288: /**
289: * Add newly discovered metadata to our stash of entity info. This info
290: * is maintained as it is discovered because it would be expensive to
291: * create it on demand -- all class metadata would have to be traversed.
292: */
293: private void updateEntityInfo(ClassMetadata metadata) {
294:
295: /*
296: * Find out whether this class or its superclass is an entity. In the
297: * process, traverse all superclasses to load their metadata -- this
298: * will populate as much entity info as possible.
299: */
300: String entityClass = null;
301: PrimaryKeyMetadata priKey = null;
302: Map<String, SecondaryKeyMetadata> secKeys = new HashMap<String, SecondaryKeyMetadata>();
303: for (ClassMetadata data = metadata; data != null;) {
304: if (data.isEntityClass()) {
305: if (entityClass != null) {
306: throw new IllegalArgumentException(
307: "An entity class may not derived from another"
308: + " entity class: " + entityClass
309: + ' ' + data.getClassName());
310: }
311: entityClass = data.getClassName();
312: }
313: /* Save first primary key encountered. */
314: if (priKey == null) {
315: priKey = data.getPrimaryKey();
316: }
317: /* Save all secondary keys encountered by key name. */
318: Map<String, SecondaryKeyMetadata> classSecKeys = data
319: .getSecondaryKeys();
320: if (classSecKeys != null) {
321: for (SecondaryKeyMetadata secKey : classSecKeys
322: .values()) {
323: secKeys.put(secKey.getKeyName(), secKey);
324: }
325: }
326: /* Load superclass metadata. */
327: Class cls;
328: try {
329: cls = EntityModel.classForName(data.getClassName());
330: } catch (ClassNotFoundException e) {
331: throw new IllegalStateException(e);
332: }
333: cls = cls.getSuperclass();
334: if (cls != Object.class) {
335: data = getClassMetadata(cls.getName());
336: if (data == null) {
337: throw new IllegalArgumentException(
338: "Persistent class has non-persistent superclass: "
339: + cls.getName());
340: }
341: } else {
342: data = null;
343: }
344: }
345:
346: /* Add primary and secondary key entity info. */
347: if (entityClass != null) {
348: EntityInfo info = entityMap.get(entityClass);
349: if (info == null) {
350: info = new EntityInfo();
351: entityMap.put(entityClass, info);
352: }
353: if (priKey == null) {
354: throw new IllegalArgumentException(
355: "Entity class has no primary key: "
356: + entityClass);
357: }
358: info.priKey = priKey;
359: info.secKeys.putAll(secKeys);
360: }
361: }
362: }
|