blob: 1403732f472be10ff9baa59bc91ee029d2889d7b [file] [log] [blame]
/*-
* Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle Berkeley
* DB Java Edition made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle Berkeley DB Java Edition for a copy of the
* license and additional information.
*/
package com.sleepycat.persist.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import com.sleepycat.compat.DbCompat;
import com.sleepycat.persist.EntityStore;
import com.sleepycat.persist.PrimaryIndex;
import com.sleepycat.persist.SecondaryIndex;
import com.sleepycat.persist.impl.Format;
import com.sleepycat.persist.impl.PersistCatalog;
import com.sleepycat.persist.impl.RefreshException;
import com.sleepycat.persist.raw.RawObject;
import com.sleepycat.persist.raw.RawType;
import com.sleepycat.util.ClassResolver;
/**
* The base class for classes that provide entity model metadata. An {@link
* EntityModel} defines entity classes, primary keys, secondary keys, and
* relationships between entities. For each entity class that is part of the
* model, a single {@link PrimaryIndex} object and zero or more {@link
* SecondaryIndex} objects may be accessed via an {@link EntityStore}.
*
* <p>The built-in entity model, the {@link AnnotationModel}, is based on
* annotations that are added to entity classes and their key fields.
* Annotations are used in the examples in this package, and it is expected
* that annotations will normally be used; most readers should therefore skip
* to the {@link AnnotationModel} class. However, a custom entity model class
* may define its own metadata. This can be used to define entity classes and
* keys using mechanisms other than annotations.</p>
*
* <p>A concrete entity model class should extend this class and implement the
* {@link #getClassMetadata}, {@link #getEntityMetadata} and {@link
* #getKnownClasses} methods.</p>
*
* <p>This is an abstract class rather than an interface to allow adding
* capabilities to the model at a future date without causing
* incompatibilities. For example, a method may be added in the future for
* returning new information about the model and subclasses may override this
* method to return the new information. Any new methods will have default
* implementations that return default values, and the use of the new
* information will be optional.</p>
*
* @author Mark Hayes
*/
public abstract class EntityModel {
private volatile PersistCatalog catalog;
private ClassLoader classLoader;
/**
* The default constructor for use by subclasses.
*/
protected EntityModel() {
}
/**
* Returns whether the model is associated with an open store.
*
* <p>The {@link #registerClass} method may only be called when the model
* is not yet open. Certain other methods may only be called when the
* model is open:</p>
* <ul>
* <li>{@link #convertRawObject}</li>
* <li>{@link #getAllRawTypeVersions}</li>
* <li>{@link #getRawType}</li>
* <li>{@link #getRawTypeVersion}</li>
* </ul>
*
* @return whether the model is associated with an open store.
*/
public final boolean isOpen() {
return catalog != null;
}
/**
* Registers a persistent class, most importantly, a {@link
* PersistentProxy} class or entity subclass. Also registers an enum or
* array class.
*
* <p>Any persistent class , enum class or array may be registered in
* advance of using it, to avoid the overhead of updating the catalog
* database when an instance of the class is first stored. This method
* <em>must</em> be called in three cases:</p>
* <ol>
* <li>to register all {@link PersistentProxy} classes, and</li>
* <li>to register an entity subclass defining a secondary key, if {@link
* EntityStore#getSubclassIndex getSubclassIndex} is not called for the
* subclass, and</li>
* <li>to register all new enum or array classes, if the these enum or
* array classes are unknown for DPL but will be used in a Converter
* mutation.
* </li>
* </ol>
*
* <p>For example:</p>
*
* <pre class="code">
* EntityModel model = new AnnotationModel();
* model.registerClass(MyProxy.class);
* model.registerClass(MyEntitySubclass.class);
* model.registerClass(MyEnum.class);
* model.registerClass(MyArray[].class);
*
* StoreConfig config = new StoreConfig();
* ...
* config.setModel(model);
*
* EntityStore store = new EntityStore(..., config);</pre>
*
* <p>This method must be called before opening a store based on this
* model.</p>
*
* @param persistentClass the class to register.
*
* @throws IllegalStateException if this method is called for a model that
* is associated with an open store.
*
* @throws IllegalArgumentException if the given class is not persistent
* or has a different class loader than previously registered classes.
*/
public final void registerClass(Class persistentClass) {
if (catalog != null) {
throw new IllegalStateException("Store is already open");
} else {
String className = persistentClass.getName();
ClassMetadata meta = getClassMetadata(className);
if (meta == null &&
!persistentClass.isEnum() &&
!persistentClass.isArray()) {
throw new IllegalArgumentException
("Class is not persistent, or is not an enum or array: " +
className);
}
}
}
/**
* <!-- begin JE only -->
* @hidden
* <!-- end JE only -->
* Internal access method that should not be used by applications.
*
* This method is used to initialize the model when catalog creation is
* complete, and reinitialize it when a Replica refresh occurs. See
* Store.refresh.
*
* @param newCatalog the catalog.
*/
protected void setCatalog(final PersistCatalog newCatalog) {
this.catalog = newCatalog;
}
/**
* <!-- begin JE only -->
* @hidden
* <!-- end JE only -->
* Internal access method that should not be used by applications.
*
* This method is called during EntityStore construction, before using the
* model.
*/
void setClassLoader(final ClassLoader loader) {
this.classLoader = loader;
}
/**
* <!-- begin JE only -->
* @hidden
* <!-- end JE only -->
* Internal access method that should not be used by applications.
*/
ClassLoader getClassLoader() {
return classLoader;
}
/**
* Returns the metadata for a given persistent class name, including proxy
* classes and entity classes.
*
* @param className the class name.
*
* @return the metadata or null if the class is not persistent or does not
* exist.
*/
public abstract ClassMetadata getClassMetadata(String className);
/**
* Returns the metadata for a given entity class name.
*
* @param className the class name.
*
* @return the metadata or null if the class is not an entity class or does
* not exist.
*/
public abstract EntityMetadata getEntityMetadata(String className);
/**
* Returns the names of all known persistent classes. A type becomes known
* when an instance of the type is stored for the first time or metadata or
* type information is queried for a specific class name.
*
* @return an unmodifiable set of class names.
*
* @throws IllegalStateException if this method is called for a model that
* is not associated with an open store.
*/
public abstract Set<String> getKnownClasses();
/**
* Returns the names of all known persistent enum and array classes that
* may be used to store persistent data. This differs from
* {@link #getKnownClasses}, which does not return enum and array classes
* because they have no metadata.
*
* @return an unmodifiable set of enum and array class names.
*
* @throws IllegalStateException if this method is called for a model that
* is not associated with an open store.
*/
public Set<String> getKnownSpecialClasses() {
return Collections.emptySet();
}
/**
* Returns the type information for the current version of a given class,
* or null if the class is not currently persistent.
*
* @param className the name of the current version of the class.
*
* @return the RawType.
*
* @throws IllegalStateException if this method is called for a model that
* is not associated with an open store.
*/
public final RawType getRawType(String className) {
if (catalog != null) {
return catalog.getFormat(className);
} else {
throw new IllegalStateException("Store is not open");
}
}
/**
* Returns the type information for a given version of a given class,
* or null if the given version of the class is unknown.
*
* @param className the name of the latest version of the class.
*
* @param version the desired version of the class.
*
* @return the RawType.
*
* @throws IllegalStateException if this method is called for a model that
* is not associated with an open store.
*/
public final RawType getRawTypeVersion(String className, int version) {
if (catalog != null) {
Format format = catalog.getLatestVersion(className);
while (format != null) {
if (version == format.getVersion()) {
return format;
}
}
return null;
} else {
throw new IllegalStateException("Store is not open");
}
}
/**
* Returns all known versions of type information for a given class name,
* or null if no persistent version of the class is known.
*
* @param className the name of the latest version of the class.
*
* @return an unmodifiable list of types for the given class name in order
* from most recent to least recent.
*
* @throws IllegalStateException if this method is called for a model that
* is not associated with an open store.
*/
public final List<RawType> getAllRawTypeVersions(String className) {
if (catalog != null) {
Format format = catalog.getLatestVersion(className);
if (format != null) {
List<RawType> list = new ArrayList<RawType>();
while (format != null) {
list.add(format);
format = format.getPreviousVersion();
}
return Collections.unmodifiableList(list);
} else {
return null;
}
} else {
throw new IllegalStateException("Store is not open");
}
}
/**
* Returns all versions of all known types.
*
* @return an unmodifiable list of types.
*
* @throws IllegalStateException if this method is called for a model that
* is not associated with an open store.
*/
public final List<RawType> getAllRawTypes() {
if (catalog != null) {
return catalog.getAllRawTypes();
} else {
throw new IllegalStateException("Store is not open");
}
}
/**
* Converts a given raw object to a live object according to the current
* class definitions.
*
* <p>The given raw object must conform to the current class definitions.
* However, the raw type ({@link RawObject#getType}) is allowed to be from
* a different store, as long as the class names and the value types match.
* This allows converting raw objects that are read from one store to live
* objects in another store, for example, in a conversion program.</p>
*
* @param raw the RawObject.
*
* @return the live object.
*/
public final Object convertRawObject(RawObject raw) {
try {
return catalog.convertRawObject(raw, null);
} catch (RefreshException e) {
e.refresh();
try {
return catalog.convertRawObject(raw, null);
} catch (RefreshException e2) {
throw DbCompat.unexpectedException(e2);
}
}
}
/**
* Should be called by entity model implementations instead of calling
* Class.forName whenever loading an application class. This method honors
* the BDB JE environment's ClassLoader property and uses {@link
* ClassResolver} to implement the class loading policy.
*
* @param className the class name.
*
* @return the Class.
*
* @throws ClassNotFoundException if the class is not found.
*/
public Class resolveClass(String className)
throws ClassNotFoundException {
return ClassResolver.resolveClass(className, classLoader);
}
/**
* @param className the class name.
*
* @return the Class.
*
* @throws ClassNotFoundException if the class is not found.
*
* @deprecated use {@link #resolveClass} instead. This method does not
* use the environment's ClassLoader property.
*/
public static Class classForName(String className)
throws ClassNotFoundException {
try {
return Class.forName
(className, true /*initialize*/,
Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
return Class.forName(className);
}
}
}