1/*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2002,2008 Oracle. All rights reserved. 5 * 6 * $Id: AnnotationModel.java,v 1.1 2008/02/07 17:12:28 mark Exp $ 7 */ 8 9package com.sleepycat.persist.model; 10 11import java.lang.reflect.Field; 12import java.lang.reflect.Modifier; 13import java.lang.reflect.ParameterizedType; 14import java.lang.reflect.Type; 15import java.util.ArrayList; 16import java.util.Collections; 17import java.util.HashMap; 18import java.util.HashSet; 19import java.util.List; 20import java.util.Map; 21import java.util.Set; 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 */ 40public class AnnotationModel extends EntityModel { 41 42 private static class EntityInfo { 43 PrimaryKeyMetadata priKey; 44 Map<String,SecondaryKeyMetadata> secKeys = 45 new HashMap<String,SecondaryKeyMetadata>(); 46 } 47 48 private Map<String,ClassMetadata> classMap; 49 private Map<String,EntityInfo> entityMap; 50 51 /** 52 * Constructs a model for annotated entity classes. 53 */ 54 public AnnotationModel() { 55 super(); 56 classMap = new HashMap<String,ClassMetadata>(); 57 entityMap = new HashMap<String,EntityInfo>(); 58 } 59 60 /* EntityModel methods */ 61 62 @Override 63 public synchronized Set<String> getKnownClasses() { 64 return Collections.unmodifiableSet 65 (new HashSet<String>(classMap.keySet())); 66 } 67 68 @Override 69 public synchronized EntityMetadata getEntityMetadata(String 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 84 public synchronized ClassMetadata getClassMetadata(String className) { 85 ClassMetadata metadata = classMap.get(className); 86 if (metadata == null) { 87 Class<?> type; 88 try { 89 type = EntityModel.classForName(className); 90 } catch (ClassNotFoundException 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 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) ? 116 proxiedClass.getName() : null; 117 } 118 /* Get instance fields. */ 119 List<Field> fields = new ArrayList<Field>(); 120 for (Field 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<?> type, 140 List<Field> fields) { 141 Field foundField = null; 142 String sequence = null; 143 for (Field field : fields) { 144 PrimaryKey priKey = field.getAnnotation(PrimaryKey.class); 145 if (priKey != null) { 146 if (foundField != null) { 147 throw new IllegalArgumentException 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<String,SecondaryKeyMetadata> getSecondaryKeys(Class<?> type, 168 List<Field> fields) { 169 Map<String,SecondaryKeyMetadata> map = null; 170 for (Field field : fields) { 171 SecondaryKey secKey = field.getAnnotation(SecondaryKey.class); 172 if (secKey != null) { 173 Relationship rel = secKey.relate(); 174 String elemClassName = null; 175 if (rel == Relationship.ONE_TO_MANY || 176 rel == Relationship.MANY_TO_MANY) { 177 elemClassName = getElementClass(field); 178 } 179 String keyName = secKey.name(); 180 if (keyName.length() == 0) { 181 keyName = field.getName(); 182 } 183 Class<?> relatedClass = secKey.relatedEntity(); 184 String 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<String,SecondaryKeyMetadata>(); 194 } 195 if (map.put(keyName, metadata) != null) { 196 throw new IllegalArgumentException 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 getElementClass(Field field) { 209 Class cls = field.getType(); 210 if (cls.isArray()) { 211 return cls.getComponentType().getName(); 212 } 213 if (java.util.Collection.class.isAssignableFrom(cls)) { 214 Type[] typeArgs = ((ParameterizedType) field.getGenericType()). 215 getActualTypeArguments(); 216 if (typeArgs == null || 217 typeArgs.length != 1 || 218 !(typeArgs[0] instanceof Class)) { 219 throw new IllegalArgumentException 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) typeArgs[0]).getName(); 226 } 227 throw new IllegalArgumentException 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<FieldMetadata> getCompositeKeyFields(Class<?> type, 234 List<Field> fields) { 235 List<FieldMetadata> list = null; 236 for (Field 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 242 ("Unreasonable @KeyField index value " + value + 243 ": " + type.getName()); 244 } 245 if (list == null) { 246 list = new ArrayList<FieldMetadata>(fields.size()); 247 } 248 if (value <= list.size() && list.get(value - 1) != null) { 249 throw new IllegalArgumentException 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 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 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 entityClass = null; 295 PrimaryKeyMetadata priKey = null; 296 Map<String,SecondaryKeyMetadata> secKeys = 297 new HashMap<String,SecondaryKeyMetadata>(); 298 for (ClassMetadata data = metadata; data != null;) { 299 if (data.isEntityClass()) { 300 if (entityClass != null) { 301 throw new IllegalArgumentException 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<String,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 cls; 322 try { 323 cls = EntityModel.classForName(data.getClassName()); 324 } catch (ClassNotFoundException e) { 325 throw new IllegalStateException(e); 326 } 327 cls = cls.getSuperclass(); 328 if (cls != Object.class) { 329 data = getClassMetadata(cls.getName()); 330 if (data == null) { 331 throw new IllegalArgumentException 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 349 ("Entity class has no primary key: " + entityClass); 350 } 351 info.priKey = priKey; 352 info.secKeys.putAll(secKeys); 353 } 354 } 355} 356