Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Adding a Component Plugin

VWoeltjen edited this page · 4 revisions

Once your data provider is implemented, MCT will have enough information to connect to your data source. The only remaining step to being able to connect your data points to existing views is to encapsulate them within user objects in MCT. These are described by the AbstractComponent class. Once described in this way, your data points can not only be viewed in MCT, but can also be organized and composed in a consistent manner with all other user objects.

To introduce a new kind of user object to MCT, first begin by defining a subclass of AbstractComponent. The general behavior of user objects is already defined, so you only need to add the behavior specific to your data point.

First, you will want to indicate that your component publishes feeds. In MCT, this is considered a "capability" of the user object, and may be implemented by overriding the handleGetCapability method to indicate that the component supports the FeedProvider capability:

protected <T> T handleGetCapability(Class<T> capability) {
    if (capability.isAssignableFrom(FeedProvider.class)) {
        // return an appropriate FeedProvider - see below
    }

    // . . . check for and return any other capabilities this component supports . . .

    return null; // a null pointer indicates the requested capability is not supported.
} 

A FeedProvider is responsible for communicating with the identifying information that will be connected by the MCT platform to the implemented data providers and event providers. A FeedProvider implements the following interface:

public String getSubscriptionId();
public TimeService getTimeService();
public String getLegendText();
public int getMaximumSampleRate();
public FeedType getFeedType();
public String getCanonicalName();
public RenderingInfo getRenderingInfo(Map<String,String> data);
public long getValidDataExtent();
public boolean isPrediction();
  • The first method, getSubscriptionId, should return a feed ID that will be recognizable to some existing data provider. A common way to generate this subscription ID is to use a source-specific prefix followed by an identifier for the data point, which may be stored in the AbstractComponent's external key (this permits an object to be associated with a specific feed at the database level.) These feed IDs should ultimately be recognized by some corresponding DataProvider.
  • A TimeService, as supplied by getTimeService, is responsible for communicating time information associated with a data feed. It has one method, getCurrentTime, which is responsible for returning a long integer describing the number of milliseconds since the UNIX epoch, from the data source's point of view.
  • The getLegendText method should return a user-displayable name for this feed, as might be displayed on a plot legend. This may be the same as the user object's base displayed name, the external key, or something else entirely, as appropriate to your data.
  • To communicate the expected rate of incoming data, getMaximumSampleRate should return a number indicating the maximum number of samples that could come in through the feed in a one-second interval. The real sample rate may vary over time.
  • The getFeedType method is used to describe the data type that will come in through the stream. This should return one of FeedProvider.FeedType.FLOATING_POINT, FeedProvider.FeedType.INTEGER, or FeedProvider.FeedType.STRING.
  • A feed's canonical name, as supplied by the getCanonicalName method, is similar to getLegendText, but is geared toward tabular views. This should return a human-displayable string that follows naming conventions consistent with other feeds, since common terms may be separated out to minimize redundancy, for instance as row and column headers in a table.
  • A feed provider is also responsible for providing information, by way of getRenderingInfo, on how to display a given data point, by converting a Map provided as a feed value from a data provider to a FeedProvider.RenderingInfo object which can be easily displayed. If a data provider provides normalized rendering information, this is as simple as invoking FeedProvider.RenderingInfo.valueOf(data.get(FeedProvider.NORMALIZED_RENDERING_INFO)), although checks to ensure that the relevant key is actually present are advisable. If working with a data provider that does not obey this convention, the feed provider will be responsible for interpreting the map in a manner appropriate to whatever conventions it does use.
  • The getValidDataExtent simply indicates the maximum time, in milliseconds relative to the UNIX epoch, for which valid data might exist for this particular feed.
  • Finally, the isPrediction flag is used to distinguish live streaming data from predicted data. Some views wish to display these differently; for instance, a plot will prefer to advance its time axis as new live values come in, but this behavior does not make sense if data is predictive.

Once a feed provider is made available as a capability, any feed-based view should be able to work with this component. Other extensions or changes to the default behavior of the AbstractComponent may also be implemented in this subclass; for instance, you may wish to override isLeaf to return true if you do not want this component to be usable as a container for other objects.

When they have been defined, new component types can be recognized by MCT using a component provider. The ComponentProvider interface contains multiple methods which may be used to gather lists of new things added by a specific bundle. Since in this instance only new component types have been added, it make sense for the implementation of ComponentProvider to subclass from AbstractProvider (which provides empty lists for all these things by default, as a convenience for just this situation) and override only one method:

public Collection<ComponentTypeInfo> getComponentTypes()

This should return a collection of objects which describe the component types available through the bundle. There are a few different ways to instantiate a ComponentTypeInfo object, with various parameters determining whether objects are creditable, what their icons are, and what their creation user interface should be. The simplest constructor for ComponentTypeInfo requires only a name (as a String), description (also as a String), and class (as a Class<? extends AbstractComponent).

It is important to note that user objects are not limited to encapsulating data feeds: They can be used to represent any of the important things you might wish to work with in MCT. An example is the GraphicalComponent in the dynamicGraphics package, which allows an image file (perhaps a schematic, or logo) to be referenced and displayed from within MCT. Other examples from the platform are collections for organizing objects, or drop boxes for sharing them. One of the key benefits of MCT is that once you've described your important things as AbstractComponents, they may be organized, composed, and viewed in consistent ways.

Once the component provider has been implemented, it can be made available through OSGi's declarative services. The easiest way to do this is to package your component in a Jar archive along with an XML document that describes what service you are offering (typically OSGI-INF/services.xml). An example of how such an XML file should look:

<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
<scr:component name="org.acme.example.component.ExampleComponentProvider">
    <implementation class="org.acme.example.component.ExampleComponentProvider" />
    <service>
        <provide interface="gov.nasa.arc.mct.services.component.ComponentProvider" />
    </service>
</scr:component>
</root>

Finally, you should add a line to your Jar's manifest to identify this service description:

Service-Component: OSGI-INF/services.xml

Other component types

The section above describes the implementation of a component which is used to observe telemetry data, which is a typical use case for MCT. However, a "Component" in MCT does not have to be telemetry - it can describe any type of user object you might wish.

The general guideline for making something a user object in MCT is your work environment: What are the meaningful "things" in the context of your work? For monitoring, these things may be telemetry points or collections thereof, or rules for dealing with data, or even the users themselves. For other tasks, these "things" will be different; an inventory control system might consider products and vendors to be their important user objects. The MCT platform can be adapted to virtually any work environment by defining appropriate user object types as subclasses of AbstractComponent (and views of those objects as Views).

Component capabilities

As a developer, you often may wish to interact with a component using its specific type, instead of being limited to the methods exposed by AbstractComponent. For instance, you may write a View plugin that is interested in displayed some specific properties of a component class you've defined; however, your View constructor only takes an AbstractComponent as an argument. You could use Java's instanceof operator and cast to your actual type. This works, but MCT offers an even more flexible solution with component capabilities.

The getCapability method of AbstractComponent will return an object of some requested type, if the component offers support for it; otherwise, it will return null. An example of this is present above in the provision of a FeedProvider capability; in deciding whether or not a component has telemetry data, a View might make a decision such as:

FeedProvider fp = abstractComponent.getCapability(FeedProvider.class);
if (fp != null) {
     // the object offers telemetry data! set up somewhere to show it...
} else {
     // no telemetry data is shown by this object, so do something else...
}

This allows cross-cutting behavior to be implemented among any variety of components, circumventing issues with the class hierarchy that might arise using instanceof. Views and other code written using component capabilities will be flexible enough to deal with component types that weren't even imagined at compile time, so long as they expose known capabilities. A user object may even decide at run-time whether or not it supports a given capability, or which implementation of a capability it will publish.

Every component offers a few intrinsic capabilities used by the MCT platform. Subclasses of AbstractComponent may provide more capabilities (including custom capabilities not known to the platform) by overriding the handleGetCapability method.

Persisting custom data

The MCT platform handles the persistence of all of the data elements that are common among user objects, including displayed name, component ID, and owner, as well as hierarchical relationships to other components. This is not, however, enough information to describe any type of user object in existence - going back to the inventory application posited above, one might wish to associate an underlying price with a ProductComponent. Further, one might wish to modify this and persist the changes.

MCT gives components the option to persist additional data by implementing the ModelStatePersistence capability. This is a simple interface wherein the custom internal state of an interface is converted to an from String form.

public interface ModelStatePersistence {
    String getModelState();
    void setModelState(String state);
}

Note that these methods do not contain an AbstractComponent argument: It is presumed that the get- and setModelState methods of the ModelStatePersistence capability returned by a user object refer to that specific user object.

As a convenience, MCT offers an adapter for ModelStatePersistence to utilize JAXB to convert an appropriately-annotated inner object to and from this String form. A good example of this is in, appropriately, the ExampleComponent.

private final AtomicReference<ExampleModelRole> model = 
        new AtomicReference<ExampleModelRole>(new ExampleModelRole());

@Override
protected <T> T handleGetCapability(Class<T> capability) {
    if (ModelStatePersistence.class.isAssignableFrom(capability)) {
        JAXBModelStatePersistence<ExampleModelRole> persistence = 
                    new JAXBModelStatePersistence<ExampleModelRole>() {
            @Override
            protected ExampleModelRole getStateToPersist() {
                return model.get();
            }

            @Override
            protected void setPersistentState(ExampleModelRole modelState) {
                model.set(modelState);
            }

            @Override
            protected Class<ExampleModelRole> getJAXBClass() {
                return ExampleModelRole.class;
            }
        };

        return capability.cast(persistence);
    }

    return null;
}

Here, the ExampleComponent has a separate, JAXB-annotated class ExampleModelRole that holds all of its specific persistent state. An instance of this class is stored locally for each ExampleComponent, and a JAXBModelStatePersistence form the the ModelStatePersistence capability is used to convert this to and from a common form useful to MCT's persistence services.

Something went wrong with that request. Please try again.