Developing Java clients

Overview

This section gives an overview on how to access Cumulocity from Java clients, starting from connecting to Cumulocity over accessing data to remote control of devices. It also discusses how to extend the Cumulocity domain model from Java for new devices and other business objects. Finally, this section describes how to configure the logging service in order to control the level of diagnostic messages generated by the client.

The section is tightly linked to the design of the REST interfaces, which are described in the "REST implementation" section of the reference guide. JavaDoc is available on the resources site.

Connecting to Cumulocity

The root interface for connecting to Cumulocity from Java is called "Platform" (see "Root interface" in the REST implementation section of the reference guide). It provides access to all other interfaces of the platform, such as the inventory. In its simplest form, it is instantiated as follows:

Platform platform = new PlatformImpl("<<URL>>", new CumulocityCredentials("<<user>>", "<<password>>"));

As an example:

Platform platform = new PlatformImpl("https://demos.cumulocity.com", new CumulocityCredentials("myuser", "mypassword"));

If you use the Java client for developing an application, you need to register an application key (through "Own applications" in the Cumulocity administration application, or through the Application API).

new CumulocityCredentials("<<tenant>>", "<<user>>", "<<password>>", "<<application key>>")

For testing purposes, every tenant is subscribed to the demo application key "uL27no8nhvLlYmW1JIK1CA==". The constructor for PlatformImpl also allows you to specify the default number of objects returned from the server in one reply with the parameter "pageSize".

Accessing the inventory

The following code snippet shows how to obtain a handle to the inventory from Java:

InventoryApi inventory = platform.getInventoryApi();

Using this handle, you can create, retrieve and update managed objects. For example, if you would like to retrieve all objects that have a geographical position, use

InventoryFilter inventoryFilter = new InventoryFilter();
inventoryFilter.byFragmentType(Position.class);
ManagedObjectCollection moc = inventory.getManagedObjectsByFilter(inventoryFilter);

This returns a query to get the objects -- it does not actually get them. In practice, such a list of objects could be very large. Hence, it is return in "pages" from the server. To get all pages and iterate over them, use:

for (ManagedObjectRepresentation mo : moc.get().allPages()) {
        System.out.println(mo.getName());
}

To create a new managed object, simply construct a local representation of the object and send it to the platform. The following code snippet shows how to create a new electricity meter with a relay in it:

ManagedObjectRepresentation mo = new ManagedObjectRepresentation();
mo.setName("MyMeter-1");
Relay relay = new Relay();
mo.set(relay);
SinglePhaseElectricitySensor meter = new SinglePhaseElectricitySensor();
mo.set(meter);
// Set additional properties, e.g., tariff tables, ...
mo = inventory.create(mo);
System.out.println(mo.getId());

The result of invoking "create" is a version of the new managed object with a populated unique identifier.

Now assume that you would like to store additional, own properties along with the device. This can be simply done by creating a new "fragment" in the form of a Java bean. For example, assume that you would like to store tariff information along with your meter. There is a day and a night tariff, and we need to store the hours during which the night time tariff is active:

public class Tariff {
  public int getNightTariffStart() {
    return nightTariffStart;
  }
  public void setNightTariffStart(int nightTariffStart) {
    this.nightTariffStart = nightTariffStart;
  }
  public int getNightTariffEnd() {
    return nightTariffEnd;
  }
  public void setNightTariffEnd(int nightTariffEnd) {
    this.nightTariffEnd = nightTariffEnd;
  }
  private int nightTariffStart = 22;
  private int nightTariffEnd = 6;
}

Now, you can simply add tariff information to your meter:

Tariff tariff = new Tariff();
mo.set(tariff);

This will store the tariff information along with the meter. For converting Java objects from and towards JSON/REST, Cumulocity uses Svenson. The Svenson documentation provides more information on how to influence the JSON format that is produced respectively accepted.

When creating own fragments in OSGi, you need to make the fragments visible to the Cumulocity client libraries. To do this, add the following line to the manifest file of the bundle containing the fragments:

Eclipse-RegisterBuddy: com.nsn.cumulocity.model.core-model

It is a good practice to maintain your domain model in a separate project in the SDK. That way, you can share the domain model between your agent and your application.

Accessing the identity service

A device typically has a technical identifier that an agent needs to know to be able to contact the device. Examples are meter numbers, IP addresses and REST URLs. To associate such identifiers with the unique identifier of Cumulocity, agents can use the identity service. Again, to create the association, create an object of type "ExternalIDRepresentation" and send it to the platform. The code snippet below shows how to register a REST URL for a device. It assumes that "mo" is the managed object from the above example and "deviceUrl" is a string with the REST URL of the device.

final String ASSET_TYPE = "com_cumulocity_idtype_AssetTag";
final String deviceUrl = "SAMPLE-A-239239232";

ExternalIDRepresentation externalIDGid = new ExternalIDRepresentation();
externalIDGid.setType(ASSET_TYPE);
externalIDGid.setExternalId(deviceUrl);
externalIDGid.setManagedObject(mo);
IdentityApi identityApi= platform.getIdentityApi();
identityApi.create(externalIDGid);

Now, if you need the association back, you can just query the identity service as follows:

ID id = new ID();
id.setType(ASSET_TYPE);
id.setValue(deviceUrl);
externalIDGid = identityApi.getExternalId(id);

The returned object will contain the unique identifier and a link to the managed object.

Accessing events and measurements

Events and measurements can be accessed in a very similar manner as described above for the inventory. The following examples queries the signal strength of the mobile connection of devices in the past two weeks and prints the device ID, the time of the measurement, the received signal strength and the bit error rate.

MeasurementApi measurementApi = platform.getMeasurementApi();
MeasurementFilter measurementFilter = new MeasurementFilter();

Calendar cal = Calendar.getInstance();
Date toDate = cal.getTime();
cal.add(Calendar.DATE, -14);
Date fromDate = cal.getTime();
measurementFilter.byDate(fromDate, toDate);
measurementFilter.byFragmentType(SignalStrength.class);
MeasurementCollection mc = measurementApi.getMeasurementsByFilter(measurementFilter);

MeasurementCollectionRepresentation measurements = mc.get();
for (; measurements != null; measurements = mc.getNextPage(measurements)) {
    for (MeasurementRepresentation measurement : measurements.getMeasurements()) {
        SignalStrength signal = measurement.get(SignalStrength.class);
        System.out.println(measurement.getSource().getId() + " " + measurement.getTime() + " " + signal.getRssiValue() + " " + signal.getBerValue());
    }
}

Controlling devices

Finally, the "DeviceControlResource" enables you to manipulate devices remotely. It has two sides: You can create operations in applications to be sent to devices, and you can query operations from agents.

In order to control a device it must be in the "childDevices" hierarchy of an agent managed object. The agent managed object represents your agent in the inventory. It is identified by a fragment com_cumulocity_model_Agent. This is how Cumulocity identifies where to send operations to control a particular device. This code demonstrates the setup:

ManagedObjectRepresentation agent = new ManagedObjectRepresentation();
agent.set(new com.cumulocity.model.Agent()); // agents must include this fragment
// ... create agent in inventory
ManagedObjectRepresentation device = ...;
// ... create device in inventory

ManagedObjectReferenceRepresentation child2Ref = new ManagedObjectReferenceRepresentation();
child2Ref.setManagedObject(device);
inventory.getManagedObject(agent.getId()). addChildDevice(child2Ref);

For example, assume that you would like to switch off a relay in a meter from an application. Similar to the previous examples, you create the operation to be executed locally, and then send it to the platform:

DeviceControlApi control = platform.getDeviceControlApi();
OperationRepresentation operation = new OperationRepresentation();
operation.setDeviceId(mo.getId());
relay.setRelayState(RelayState.OPEN);
operation.set(relay);
control.create(operation);

Now, if you would like to query the pending operations from an agent, the following code would need to be executed:

OperationFilter operationFilter = new OperationFilter();
operationFilter.byAgent(mo.getId().getValue());
operationFilter.byStatus(OperationStatus.PENDING);
OperationCollection oc = control.getOperationsByFilter(operationFilter);

Again, the returned result may come in several pages due to its potential size.

OperationCollectionRepresentation opCollectionRepresentation;
for (opCollectionRepresentation = oc.get(); opCollectionRepresentation != null; opCollectionRepresentation = oc.getNextPage(opCollectionRepresentation)) {
    for (OperationRepresentation op : opCollectionRepresentation.getOperations()) {
        System.out.println(op.getStatus());
    }
}

Realtime features

The Java client libraries fully support the real-time APIs of Cumulocity. For example, to get immediately notified when someone sends an operation to your agent, use the following code:

Subscriber<GId, OperationRepresentation> subscriber = deviceControl.getNotificationsSubscriber();
subscriber.subscribe(agentId, new SubscriptionListener<GId, OperationRepresentation> {
    public void onError(Subscription<GId> sub, Throwable e) {
        logger.error("OperationDispatcher error!", e);
    }

    public void onNotification(Subscription<GId> sub, OperationRepresentation operation) {
        // Execute the operation
    }
});

"agentId" is the ID of your agent in the inventory.

Reliability features

In particular on mobile devices, Internet connectivity might be unreliable. To support such environments, the Java client libraries support local buffering. This means that you can pass data to the client libraries regardless if an Internet connection is available or not. If a connection is available, the data will be send immediately. If not, the data will be buffered until the connection is back again. For this, "async" variants of the API calls are offered. For example, to send an alarm, use

AlarmApi alarmApi = platform.getAlarmApi();
Future future = alarmApi.createAsync(anAlarm);

The "createAsync" method returns immediately. The "Future" object can be used to determine the result of the request whenever it was actually carried out.

Logging Configuration

Logging in the Java client SDK is handled through slf4j with a logback backend. For a detailed description on how to use and configure logging, see the logback documentation.

Since version 0.11, the default logging level of the SDK is set to "Error" for all components, which means that logging messages are suppressed unless their level is "Error". If everything runs smoothly, there should be no log messages generated by the SDK. By default, log messages are sent to the console only.

The default logging configuration can be changed by providing a new configuration file. Two methods for providing the configuration file are discussed here: via an absolute filename passed using a system property; and via an OSGi fragment. Note that both of these methods override the default behaviour, rather than extending it.

Configuration via System Property

The absolute path to a logging configuration file can be passed using the following system property:

-Dlogback.configurationFile=/path/to/config.xml

In Eclipse, this property should be added on the Arguments->VM Arguments of the Run Configuration window.

Configuration via OSGi Fragment Bundle

A more flexible approach is to provide the logger configuration via an OSGi bundle that can be deployed along with the client. From within Eclipse, the fragment can be generated as follows:

  1. Create a new project of type "Fragment Project" called "LogConfig"

    • Target to run with: an OSGi framework
    • Host plugin ID: ch.qos.logback.classic
  2. Add a file called logback.xml to the root of the new project. This file will contain the logging configuratiom.

  3. Edit the MANIFEST.MF file and add the following:

    • On the Build page add the logback.xml file to the Binary Build
  4. On the Bundles page of the Run Configuration dialog of the Java client project:

    • Select the LogConfig bundle
    • Deselect the com.nsn.cumulocity.platform-services.sdk.logging-config bundle

Simple Logging Configuration

The following example shows how to enable debug-level logging for a single component, called "com.cumulocity.javaclient", whilst keeping error-level logging for all other components. The following code snippet shows how to create the logger, and to log a message:

Logger logger = LoggerFactory.getLogger("com.cumulocity.javaclient");logger.debug("A debug message");

The configuration file looks like this:

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder><pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern></encoder>
    </appender>
    <logger name="com.cumulocity.javaclient" level="debug"/>
    <root level="error"><appender-ref ref="STDOUT" /></root>
</configuration>

When the code is run, the console should contain a message similar to the following:

21:52:02.790 [Start Level Event Dispatcher] DEBUG com.cumulocity.javaclient - A debug message