public class Schema<I extends Iced,S extends Schema<I,S>> extends Iced
The purpose of Schemas is to provide a stable, versioned interface to the functionality in H2O, which allows the back end implementation to change rapidly without breaking REST API clients such as the Web UI and R and Python bindings. Schemas also allow for functionality which exposes the schema metadata to clients, allowing them to do service discovery and to adapt dynamically to new H2O functionality, e.g. to be able to call any ModelBuilder, even new ones written since the client was built, without knowing any details about the specific algo.
In most cases, Java developers who wish to expose new functionality through the REST API will need only to define their schemas with the fields that they wish to expose, adding @API annotations to denote the field metadata. Their fields will be copied back and forth through the reflection magic in this class. If there are fields they have to adapt between the REST API representation and the back end this can be done piecemeal by overriding the fill* methods, calling the versions in super, and making only those changes that are absolutely necessary. A lot of work has gone into avoiding boilerplate code.
Schemas are versioned for stability. When you look up the schema for a given impl object or class you supply a version number. If a schema for that version doesn't exist then the versions are searched in reverse order. For example, if you ask for version 5 and the highest schema version for that impl class is 3 then V3 will be returned. This allows us to move to higher versions without having to write gratuitous new schema classes for the things that haven't changed in the new version.
The current version can be found by calling Schema.getHighestSupportedVersion(). For schemas that are still in flux because development is ongoing we also support an EXPERIMENTAL_VERSION, which indicates that there are no interface stability guarantees between H2O versions. Eventually these schemas will move to a normal, stable version number. Call Schema.getExperimentalVersion() to find the experimental version number (99 as of this writing).
Schema names must be unique within an application in a single namespace. The class getSimpleName() is used as the schema name. During Schema discovery and registration there are checks to ensure that the names are unique.
Most schemas have a 1-to-1 mapping to an Iced implementation object, aka the "impl" or implementation class. This class is specified as a type parameter to the Schema class. This type parameter is used by reflection magic to avoid a large amount of boilerplate code.
Both the Schema and the Iced object may have children, or (more often) not. Occasionally, e.g. in the case of schemas used only to handle HTTP request parameters, there will not be a backing impl object and the Schema will be parameterized by Iced.
Other than Schemas backed by Iced this 1-1 mapping is enforced: a check at Schema
registration time ensures that at most one Schema is registered for each Iced class.
This 1-1 mapping allows us to have generic code here in the Schema class which does
all the work for common cases. For example, one can write code which accepts any
Schema instance and creates and fills an instance of its associated impl class:
I impl = schema.createAndFillImpl();
Schemas have a State section (broken into Input, Output and InOut fields) and an Adapter section. The adapter methods fill the State to and from the Iced impl objects and from HTTP request parameters. In the simple case, where the backing object corresponds 1:1 with the Schema and no adapting need be done, the methods here in the Schema class will do all the work based on reflection. In that case your Schema need only contain the fields you wish to expose, and no adapter code.
Methods here allow us to convert from Schema to Iced (impl) and back in a flexible way. The default behaviour is to map like-named fields back and forth, often with some default type conversions (e.g., a Keyed object like a Model will be automagically converted back and forth to a Key). Subclasses can override methods such as fillImpl or fillFromImpl to provide special handling when adapting from schema to impl object and back. Usually they will want to call super to get the default behavior, and then modify the results a bit (e.g., to map differently-named fields, or to compute field values).
Schema Fields must have a single @API annotation describing their direction of operation and any other properties such as "required". Fields are API.Direction.INPUT by default. Transient and static fields are ignored.
Most Java developers need not be concerned with the details that follow, because the framework will make these calls as necessary.
Some use cases:
To find and create an instance of the appropriate schema for an Iced object, with the given version or the highest previous version:
Schema s = Schema.schema(6, impl);
To create a schema object and fill it from an existing impl object (the common case):
S schema = MySchemaClass(version).fillFromImpl(impl);or more generally:
S schema = Schema(version, impl).fillFromImpl(impl);To create an impl object and fill it from an existing schema object (the common case):
I impl = schema.createImpl(); // create an empty impl object and any empty children schema.fillImpl(impl); // fill the empty impl object and any children from the Schema and its childrenor
I impl = schema.createAndFillImpl(); // helper which does schema.fillImpl(schema.createImpl())
Schemas that are used for HTTP requests are filled with the default values of their impl class, and then any present HTTP parameters override those default values.
To create a schema object filled from the default values of its impl class and then overridden by HTTP request params:
S schema = MySchemaClass(version); I defaults = schema.createImpl(); schema.fillFromImpl(defaults); schema.fillFromParms(parms);or more tersely:
S schema = MySchemaClass(version).fillFromImpl(schema.createImpl()).fillFromParms(parms);
Modifier and Type | Class and Description |
---|---|
static class |
Schema.Meta
Metadata for a Schema, including the version, name and type.
|
Constructor and Description |
---|
Schema()
Default constructor; triggers lazy schema registration.
|
Modifier and Type | Method and Description |
---|---|
I |
createAndFillImpl()
Convenience helper which creates and fills an impl object from this schema.
|
I |
createImpl()
Create an appropriate implementation object and any child objects but does not fill them.
|
S |
fillFromImpl(I impl)
Fill this Schema from the given implementation object.
|
S |
fillFromParms(java.util.Properties parms)
Fill this Schema object from a set of parameters.
|
S |
fillFromParms(java.util.Properties parms,
boolean checkRequiredFields)
Fill this Schema from a set of (generally HTTP) parameters.
|
I |
fillImpl(I impl)
Fill an impl object and any children from this schema and its children.
|
protected Schema.Meta |
get__meta()
Get the metadata for this schema instance which makes it self-describing when serialized to JSON.
|
static int |
getExperimentalVersion()
Get the experimental schema version, which indicates that a schema is not guaranteed stable between H2O releases.
|
static int |
getHighestSupportedVersion()
Get the highest schema version that we support.
|
java.lang.Class<I> |
getImplClass()
Return the class of the implementation type parameter I for this Schema.
|
static java.lang.Class<? extends Iced> |
getImplClass(java.lang.Class<? extends Schema> clz)
Return the class of the implementation type parameter I for the given Schema class.
|
static int |
getLatestVersion()
Get the highest schema version number that we've encountered during schema registration.
|
int |
getSchemaVersion()
Get the version number of this schema, for example 3 or 99.
|
java.lang.StringBuffer |
markdown(SchemaMetadata meta,
java.lang.StringBuffer appendToMe)
Append Markdown documentation for another Schema, given we already have the metadata constructed.
|
java.lang.StringBuffer |
markdown(SchemaMetadata meta,
java.lang.StringBuffer appendToMe,
boolean include_input_fields,
boolean include_output_fields)
Generate Markdown documentation for this Schema, given we already have the metadata constructed.
|
java.lang.StringBuffer |
markdown(java.lang.StringBuffer appendToMe,
boolean include_input_fields,
boolean include_output_fields)
Generate Markdown documentation for this Schema possibly including only the input or output fields.
|
static <T extends Schema> |
newInstance(java.lang.Class<T> clz) |
protected static Schema |
newInstance(java.lang.String schema_name)
For a given schema_name (e.g., "FrameV2") return an appropriate new schema object (e.g., a water.api.Framev2).
|
static void |
registerAllSchemasIfNecessary()
Find all schemas using reflection and register them.
|
static Schema |
schema(int version,
java.lang.Class<? extends Iced> impl_class)
For a given version and Iced class return an appropriate Schema instance, if any.
|
static Schema |
schema(int version,
Iced impl)
For a given version and Iced object return an appropriate Schema instance, if any.
|
protected static java.lang.Class<? extends Schema> |
schemaClass(int version,
java.lang.Class<? extends Iced> impl_class)
For a given version and Iced class return the appropriate Schema class, if any.f
|
protected static java.util.Map<java.lang.String,java.lang.Class<? extends Schema>> |
schemas()
Return an immutable Map of all the schemas: schema_name -> schema Class.
|
static <T extends Schema> |
setField(T o,
java.lang.reflect.Field f,
java.lang.String key,
java.lang.String value,
boolean required,
java.lang.Class thisSchemaClass)
Safe method to set the field on given schema object
|
clone, frozenType, read_impl, read, readExternal, readJSON_impl, readJSON, toJsonString, write_impl, write, writeExternal, writeJSON_impl, writeJSON
public Schema()
H2OFailException
- if there is a name collision or there is more than one schema which maps to the same Iced classprotected Schema.Meta get__meta()
public int getSchemaVersion()
public static final int getLatestVersion()
public static final int getHighestSupportedVersion()
public static final int getExperimentalVersion()
public I createImpl()
For objects without children this method does all the required work. For objects with children the subclass will need to override, e.g. by calling super.createImpl() and then calling createImpl() on its children.
Note that impl objects for schemas which override this method don't need to have a default constructor (e.g., a Keyed object constructor can still create and set the Key), but they must not fill any fields which can be filled later from the schema.
TODO: We could handle the common case of children with the same field names here by finding all of our fields that are themselves Schemas.
H2OIllegalArgumentException
- if Class.newInstance failspublic I fillImpl(I impl)
public final I createAndFillImpl()
public S fillFromImpl(I impl)
public static java.lang.Class<? extends Iced> getImplClass(java.lang.Class<? extends Schema> clz)
public java.lang.Class<I> getImplClass()
public S fillFromParms(java.util.Properties parms)
parms
- parameters - set of tuples (parameter name, parameter value)fillFromParms(Properties, boolean)
public S fillFromParms(java.util.Properties parms, boolean checkRequiredFields)
Using reflection this process determines the type of the target field and conforms the types if possible. For example, if the field is a Keyed type the name (ID) will be looked up in the DKV and mapped appropriately.
The process ignores parameters which are not fields in the schema, and it verifies that all fields marked as required are present in the parameters list.
It also does various sanity checks for broken Schemas, for example fields must not be private, and since input fields get filled here they must not be final.
parms
- Properties map of parameter valuescheckRequiredFields
- perform check for missing required fieldsH2OIllegalArgumentException
- for bad/missing parameterspublic static <T extends Schema> void setField(T o, java.lang.reflect.Field f, java.lang.String key, java.lang.String value, boolean required, java.lang.Class thisSchemaClass) throws java.lang.IllegalAccessException
o
- schema object to modifyf
- field to modifykey
- name of field to modifyvalue
- string-based representation of value to setrequired
- is field required by APIthisSchemaClass
- class of schema handling this (can be null)java.lang.IllegalAccessException
public static void registerAllSchemasIfNecessary()
protected static java.util.Map<java.lang.String,java.lang.Class<? extends Schema>> schemas()
protected static java.lang.Class<? extends Schema> schemaClass(int version, java.lang.Class<? extends Iced> impl_class)
schemaClass(int, java.lang.String)
public static Schema schema(int version, Iced impl)
schema(int, java.lang.String)
public static Schema schema(int version, java.lang.Class<? extends Iced> impl_class)
H2OIllegalArgumentException
- if Class.newInstance() throwsschema(int, java.lang.String)
public static <T extends Schema> T newInstance(java.lang.Class<T> clz)
protected static Schema newInstance(java.lang.String schema_name)
H2ONotFoundArgumentException
- if an appropriate schema is not foundpublic java.lang.StringBuffer markdown(java.lang.StringBuffer appendToMe, boolean include_input_fields, boolean include_output_fields)
H2ONotFoundArgumentException
- if reflection on a field failspublic java.lang.StringBuffer markdown(SchemaMetadata meta, java.lang.StringBuffer appendToMe)
H2ONotFoundArgumentException
- if reflection on a field failspublic java.lang.StringBuffer markdown(SchemaMetadata meta, java.lang.StringBuffer appendToMe, boolean include_input_fields, boolean include_output_fields)
H2ONotFoundArgumentException
- if reflection on a field fails