Miners are remote tooling extensions to the DataStore. Each miner describes the model that it works with as well as the commands associated with the model that it implements. Each miner is responsible for extending the DataStore schema and providing implementations for commands that it defines.
All miners are derived from the abstract class Miner
. To write a miner, the
following APIs need to be implemented:
public abstract void extendSchema(DataElement schemaRoot); public abstract DataElement handleCommand(DataElement theCommand);
The first method, extendSchema
, is implemented by a miner to contribute the types of objects, relationships and commands that
it deals with. The second method, handleCommand
, is implemented by a miner so that it can execute the commands it defines.
A miner extends the DataStore schema by populating the schemaRoot with descriptors. The DataStore repository tree is structured so that there is a single node where schema descriptors reside. All object descriptors and relation descriptors are created under this schema root. Command descriptors are created under the object descriptors that they apply to. Each miner contributes to this global DataStore schema, so each may also extend or leverage the information from another miner's schema.
A miner implements extendSchema(DataElement)
to add new descriptors to the DataStore schema. If a miner creates representations
of objects that are not already represented by some other miner's schema, then it needs to define descriptors for those new types of objects.
Sometimes new relationship types also need to be defined by a miner for its particular model. In order for a miner to be interacted with,
via handleCommand
, it needs to define command descriptors for object descriptors in the schema. The code below illustrates the
typical structure of an extendSchema
implementation:
public void extendSchema(DataElement schemaRoot) { // create object descriptors DataElement myObjectType1 = createObjectDescriptor(schemaRoot, "my object type 1"); DataElmeent myObjectType2 = createObjectDescriptor(schemaRoot, "my object type 2"); DataElement myObjectContainerType = createObjectDescriptor(schemaRoot, "my object container"); // create relation descriptors DataElement myRelationType = createRelationDescriptor(schemaRoot, "my relation type"); // create command descriptors createCommandDescriptor(myObjectType1, "My Command 1", MY_COMMAND_1); createCommandDescriptor(myObjectType2, "My Command 2", MY_COMMAND_2); createCommandDescriptor(myObjectContainerType, "Query", MY_QUERY); // establish relationships between object types createReference(myObjectType1, myObjectType2, myRelationType); // myObjectType1 instances can be associated with myObjectType2 instances by myRelationType createReference(myObjectContainerType, myObjectType1); // myObjectContainerType instances can contain myObjectType1 instances createReference(myObjectContainerType, myObjectType2); // myObjectContainerType instances can contain myObjectType2 instances ... }
The DataStore does not enforce that instance element trees are structured in the manner that the schema describes so the establishing of relationships between object types, as done in this example, is unnecessary as a miner knows its own model, since it defined it. But by convention, it is a good thing to describe a model with those relationships explicitly stated because other miners or client tools may want to leverage or extend the model for their own purposes.
The handleCommand
method is called by the Server Command Handler
if the descriptor for a command instance is associated with a particular miner. When this is called, it is up to the miner implementation
to interpret and execute the command.
A Command instance is a tree of DataElements representing the command, the subject of the command, additional arguments to the command and the status of the command. The way this is normally interpretted by a miner to mean perform the specified command on the subject using the specified arguments. Change the status to be "done" when the operation is complete..
The base miner class provides APIs to assist a miner implementation in extracting information from
a command tree. The method getCommandName(DataElement)
is used to extract the
name of the command, getCommandStatus(DataElement)
returns the status element of
the command, and getCommandArgument(DataElement, int)
returns an argument at the
specified index. The first argument is always the subject. The code below illustrates
the typical structure of a handleCommand
implementation.
public DataElement handleCommand(DataElement theElement) { String name = getCommandName(theElement); DataElement status = getCommandStatus(theElement); DataElement subject = getCommandArgument(theElement, 0); if (name.equals(MY_COMMAND_1)) { handleMyCommand1(subject, arg1,...,argn, status); } ... status.setAttribute(DE.A_NAME, "done"); _dataStore.refresh(status); }
The results of a command may be returned in a variety of ways, depending on its purpose. One thing that is always returned is the status object.
Think of the status object as the return value of a method. The status object needs to be set to "done" whenever a command is complete but it may also optionally contain results in the form of DataElements. If a command is requesting transient information about something, then the status object could be populated with the results.
Each miner has read and write access to the entire DataStore tree. If desired a miner can modify that tree as a result of a command. For example, a miner used for browsing a file system might populate the subject, a folder object, with other folder and file objects, rather than transiently via the status object. In this way, a miner caches data and expands the data available to the DataStore repository in response to user requests.