1/*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2002-2009 Oracle. All rights reserved. 5 * 6 * $Id$ 7 */ 8 9package com.sleepycat.persist.model; 10 11import java.util.ArrayList; 12import java.util.Collections; 13import java.util.List; 14import java.util.Set; 15 16import com.sleepycat.persist.EntityStore; 17import com.sleepycat.persist.PrimaryIndex; 18import com.sleepycat.persist.SecondaryIndex; 19import com.sleepycat.persist.impl.Format; 20import com.sleepycat.persist.impl.PersistCatalog; 21import com.sleepycat.persist.raw.RawObject; 22import com.sleepycat.persist.raw.RawType; 23 24/** 25 * The base class for classes that provide entity model metadata. An {@link 26 * EntityModel} defines entity classes, primary keys, secondary keys, and 27 * relationships between entities. For each entity class that is part of the 28 * model, a single {@link PrimaryIndex} object and zero or more {@link 29 * SecondaryIndex} objects may be accessed via an {@link EntityStore}. 30 * 31 * <p>The built-in entity model, the {@link AnnotationModel}, is based on 32 * annotations that are added to entity classes and their key fields. 33 * Annotations are used in the examples in this package, and it is expected 34 * that annotations will normally be used; most readers should therefore skip 35 * to the {@link AnnotationModel} class. However, a custom entity model class 36 * may define its own metadata. This can be used to define entity classes and 37 * keys using mechanisms other than annotations.</p> 38 * 39 * <p>A concrete entity model class should extend this class and implement the 40 * {@link #getClassMetadata}, {@link #getEntityMetadata} and {@link 41 * #getKnownClasses} methods.</p> 42 * 43 * <p>This is an abstract class rather than an interface to allow adding 44 * capabilities to the model at a future date without causing 45 * incompatibilities. For example, a method may be added in the future for 46 * returning new information about the model and subclasses may override this 47 * method to return the new information. Any new methods will have default 48 * implementations that return default values, and the use of the new 49 * information will be optional.</p> 50 * 51 * @author Mark Hayes 52 */ 53public abstract class EntityModel { 54 55 private PersistCatalog catalog; 56 57 /** 58 * The default constructor for use by subclasses. 59 */ 60 protected EntityModel() { 61 } 62 63 /** 64 * Returns whether the model is associated with an open store. 65 * 66 * <p>The {@link #registerClass} method may only be called when the model 67 * is not yet open. Certain other methods may only be called when the 68 * model is open:</p> 69 * <ul> 70 * <li>{@link #convertRawObject}</li> 71 * <li>{@link #getAllRawTypeVersions}</li> 72 * <li>{@link #getRawType}</li> 73 * <li>{@link #getRawTypeVersion}</li> 74 * </ul> 75 */ 76 public final boolean isOpen() { 77 return catalog != null; 78 } 79 80 /** 81 * Registers a persistent class, most importantly, a {@link 82 * PersistentProxy} class or entity subclass. 83 * 84 * <p>Any persistent class may be registered in advance of using it, to 85 * avoid the overhead of updating the catalog database when an instance of 86 * the class is first stored. This method <em>must</em> be called in two 87 * cases:</p> 88 * <ol> 89 * <li>to register all {@link PersistentProxy} classes, and</li> 90 * <li>to register an entity subclass defining a secondary key, if {@link 91 * EntityStore#getSubclassIndex getSubclassIndex} is not called for the 92 * subclass.</li> 93 * </ol> 94 * 95 * <p>For example:</p> 96 * 97 * <pre class="code"> 98 * EntityModel model = new AnnotationModel(); 99 * model.registerClass(MyProxy.class); 100 * model.registerClass(MyEntitySubclass.class); 101 * 102 * StoreConfig config = new StoreConfig(); 103 * ... 104 * config.setModel(model); 105 * 106 * EntityStore store = new EntityStore(..., config);</pre> 107 * 108 * <p>This method must be called before opening a store based on this 109 * model.</p> 110 * 111 * @throws IllegalStateException if this method is called for a model that 112 * is associated with an open store. 113 * 114 * @throws IllegalArgumentException if the given class is not persistent 115 * or has a different class loader than previously registered classes. 116 */ 117 public final void registerClass(Class persistentClass) { 118 if (catalog != null) { 119 throw new IllegalStateException("Store is already open"); 120 } else { 121 String className = persistentClass.getName(); 122 ClassMetadata meta = getClassMetadata(className); 123 if (meta == null) { 124 throw new IllegalArgumentException 125 ("Class is not persistent: " + className); 126 } 127 } 128 } 129 130 /** 131 * Gives this model access to the catalog, which is used for returning 132 * raw type information. 133 */ 134 void setCatalog(PersistCatalog catalog) { 135 this.catalog = catalog; 136 } 137 138 /** 139 * Returns the metadata for a given persistent class name, including proxy 140 * classes and entity classes. 141 * 142 * @return the metadata or null if the class is not persistent or does not 143 * exist. 144 */ 145 public abstract ClassMetadata getClassMetadata(String className); 146 147 /** 148 * Returns the metadata for a given entity class name. 149 * 150 * @return the metadata or null if the class is not an entity class or does 151 * not exist. 152 */ 153 public abstract EntityMetadata getEntityMetadata(String className); 154 155 /** 156 * Returns the names of all known persistent classes. A type becomes known 157 * when an instance of the type is stored for the first time or metadata or 158 * type information is queried for a specific class name. 159 * 160 * @return an unmodifiable set of class names. 161 * 162 * @throws IllegalStateException if this method is called for a model that 163 * is not associated with an open store. 164 */ 165 public abstract Set<String> getKnownClasses(); 166 167 /** 168 * Returns the type information for the current version of a given class, 169 * or null if the class is not currently persistent. 170 * 171 * @param className the name of the current version of the class. 172 * 173 * @throws IllegalStateException if this method is called for a model that 174 * is not associated with an open store. 175 */ 176 public final RawType getRawType(String className) { 177 if (catalog != null) { 178 return catalog.getFormat(className); 179 } else { 180 throw new IllegalStateException("Store is not open"); 181 } 182 } 183 184 /** 185 * Returns the type information for a given version of a given class, 186 * or null if the given version of the class is unknown. 187 * 188 * @param className the name of the latest version of the class. 189 * 190 * @param version the desired version of the class. 191 * 192 * @throws IllegalStateException if this method is called for a model that 193 * is not associated with an open store. 194 */ 195 public final RawType getRawTypeVersion(String className, int version) { 196 if (catalog != null) { 197 Format format = catalog.getLatestVersion(className); 198 while (format != null) { 199 if (version == format.getVersion()) { 200 return format; 201 } 202 } 203 return null; 204 } else { 205 throw new IllegalStateException("Store is not open"); 206 } 207 } 208 209 /** 210 * Returns all known versions of type information for a given class name, 211 * or null if no persistent version of the class is known. 212 * 213 * @param className the name of the latest version of the class. 214 * 215 * @return an unmodifiable list of types for the given class name in order 216 * from most recent to least recent. 217 * 218 * @throws IllegalStateException if this method is called for a model that 219 * is not associated with an open store. 220 */ 221 public final List<RawType> getAllRawTypeVersions(String className) { 222 if (catalog != null) { 223 Format format = catalog.getLatestVersion(className); 224 if (format != null) { 225 List<RawType> list = new ArrayList<RawType>(); 226 while (format != null) { 227 list.add(format); 228 format = format.getPreviousVersion(); 229 } 230 return Collections.unmodifiableList(list); 231 } else { 232 return null; 233 } 234 } else { 235 throw new IllegalStateException("Store is not open"); 236 } 237 } 238 239 /** 240 * Returns all versions of all known types. 241 * 242 * @return an unmodifiable list of types. 243 * 244 * @throws IllegalStateException if this method is called for a model that 245 * is not associated with an open store. 246 */ 247 public final List<RawType> getAllRawTypes() { 248 if (catalog != null) { 249 return catalog.getAllRawTypes(); 250 } else { 251 throw new IllegalStateException("Store is not open"); 252 } 253 } 254 255 /** 256 * Converts a given raw object to a live object according to the current 257 * class definitions. 258 * 259 * <p>The given raw object must conform to the current class definitions. 260 * However, the raw type ({@link RawObject#getType}) is allowed to be from 261 * a different store, as long as the class names and the value types match. 262 * This allows converting raw objects that are read from one store to live 263 * objects in another store, for example, in a conversion program.</p> 264 */ 265 public final Object convertRawObject(RawObject raw) { 266 return catalog.convertRawObject(raw, null); 267 } 268 269 /** 270 * Calls Class.forName with the current thread context class loader. This 271 * method should be called by entity model implementations instead of 272 * calling Class.forName whenever loading an application class. 273 */ 274 public static Class classForName(String className) 275 throws ClassNotFoundException { 276 277 try { 278 return Class.forName(className, true /*initialize*/, 279 Thread.currentThread().getContextClassLoader()); 280 } catch (ClassNotFoundException e) { 281 return Class.forName(className); 282 } 283 } 284} 285