1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2002,2008 Oracle.  All rights reserved.
5 *
6 * $Id: EntityModel.java,v 1.1 2008/02/07 17:12:28 mark Exp $
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.  Any persistent class may be registered in
83     * advance of using it, to avoid the overhead of updating the catalog
84     * database when an instance of the class is first stored.  This method
85     * <em>must</em> be called to register {@link PersistentProxy} classes.
86     * This method must be called before opening a store based on this model.
87     *
88     * @throws IllegalStateException if this method is called for a model that
89     * is associated with an open store.
90     *
91     * @throws IllegalArgumentException if the given class is not persistent
92     * or has a different class loader than previously registered classes.
93     */
94    public final void registerClass(Class persistentClass) {
95        if (catalog != null) {
96            throw new IllegalStateException("Store is already open");
97        } else {
98            String className = persistentClass.getName();
99            ClassMetadata meta = getClassMetadata(className);
100            if (meta == null) {
101                throw new IllegalArgumentException
102                    ("Class is not persistent: " + className);
103            }
104        }
105    }
106
107    /**
108     * Gives this model access to the catalog, which is used for returning
109     * raw type information.
110     */
111    void setCatalog(PersistCatalog catalog) {
112        this.catalog = catalog;
113    }
114
115    /**
116     * Returns the metadata for a given persistent class name, including proxy
117     * classes and entity classes.
118     *
119     * @return the metadata or null if the class is not persistent or does not
120     * exist.
121     */
122    public abstract ClassMetadata getClassMetadata(String className);
123
124    /**
125     * Returns the metadata for a given entity class name.
126     *
127     * @return the metadata or null if the class is not an entity class or does
128     * not exist.
129     */
130    public abstract EntityMetadata getEntityMetadata(String className);
131
132    /**
133     * Returns the names of all known persistent classes.  A type becomes known
134     * when an instance of the type is stored for the first time or metadata or
135     * type information is queried for a specific class name.
136     *
137     * @return an unmodifiable set of class names.
138     *
139     * @throws IllegalStateException if this method is called for a model that
140     * is not associated with an open store.
141     */
142    public abstract Set<String> getKnownClasses();
143
144    /**
145     * Returns the type information for the current version of a given class,
146     * or null if the class is not currently persistent.
147     *
148     * @param className the name of the current version of the class.
149     *
150     * @throws IllegalStateException if this method is called for a model that
151     * is not associated with an open store.
152     */
153    public final RawType getRawType(String className) {
154        if (catalog != null) {
155            return catalog.getFormat(className);
156        } else {
157            throw new IllegalStateException("Store is not open");
158        }
159    }
160
161    /**
162     * Returns the type information for a given version of a given class,
163     * or null if the given version of the class is unknown.
164     *
165     * @param className the name of the latest version of the class.
166     *
167     * @param version the desired version of the class.
168     *
169     * @throws IllegalStateException if this method is called for a model that
170     * is not associated with an open store.
171     */
172    public final RawType getRawTypeVersion(String className, int version) {
173        if (catalog != null) {
174            Format format = catalog.getLatestVersion(className);
175            while (format != null) {
176                if (version == format.getVersion()) {
177                    return format;
178                }
179            }
180            return null;
181        } else {
182            throw new IllegalStateException("Store is not open");
183        }
184    }
185
186    /**
187     * Returns all known versions of type information for a given class name,
188     * or null if no persistent version of the class is known.
189     *
190     * @param className the name of the latest version of the class.
191     *
192     * @return an unmodifiable list of types for the given class name in order
193     * from most recent to least recent.
194     *
195     * @throws IllegalStateException if this method is called for a model that
196     * is not associated with an open store.
197     */
198    public final List<RawType> getAllRawTypeVersions(String className) {
199        if (catalog != null) {
200            Format format = catalog.getLatestVersion(className);
201            if (format != null) {
202                List<RawType> list = new ArrayList<RawType>();
203                while (format != null) {
204                    list.add(format);
205                    format = format.getPreviousVersion();
206                }
207                return Collections.unmodifiableList(list);
208            } else {
209                return null;
210            }
211        } else {
212            throw new IllegalStateException("Store is not open");
213        }
214    }
215
216    /**
217     * Converts a given raw object to a live object according to the current
218     * class definitions.
219     *
220     * <p>The given raw object must conform to the current class definitions.
221     * However, the raw type ({@link RawObject#getType}) is allowed to be from
222     * a different store, as long as the class names and the value types match.
223     * This allows converting raw objects that are read from one store to live
224     * objects in another store, for example, in a conversion program.</p>
225     */
226    public final Object convertRawObject(RawObject raw) {
227        return catalog.convertRawObject(raw, null);
228    }
229
230    /**
231     * Calls Class.forName with the current thread context class loader.  This
232     * method should be called by entity model implementations instead of
233     * calling Class.forName whenever loading an application class.
234     */
235    public static Class classForName(String className)
236        throws ClassNotFoundException {
237
238        try {
239            return Class.forName(className, true /*initialize*/,
240                             Thread.currentThread().getContextClassLoader());
241        } catch (ClassNotFoundException e) {
242            return Class.forName(className);
243        }
244    }
245}
246