Introduction
The Cumulocity IoT API provides a wide range of API query capabilities, but only a subset is easily useable directly by the Microservice SDK for Java.
Mostly the inventory API provides the most capable (and complex) query language, which is heavily used in many projects. Also other APIs like alarm, event, measurement, audit and identity also can be used with the same approach!
Due to the wide variety of features, query language and URL parameters, the SDK does not support this in all detail.
Implementing a microservice usually requires to interact for example with the inventory API to search for devices by specific criteria. Once the required API call is identified, e.g. via Postman, you think of easily taking that into a microservice to automate the task.
This article is about to show some best practices including code examples on how to simplify calling the Cumulocity IoT API with the full set of its features.
Pre-requisite
Any microservice based on the Cumulocity IoT Microservice SDK for Java is applicable.
How does it work?
Problem statement
You want to query devices by their creation time. Hence via API call you know to use a GET API call like below and you want to be able to do the same call within a microservice, using the microservice SDK.
GET /inventory/managedObjects?query=$filter=creationTime.date gt '2023-06-12T12:00:00.000Z' and creationTime.date le '2023-06-12T12:59:59.999Z'
Limitations of standard SDK classes
Normally you can use InventoryFilter
to query the inventory API, but it is limited and does not (easily) support the powerful query language (e.g. filter by creation time from the problem statement above) or URL parameters like withParents
. Hence I want to share a best practice to use the SDK in a code-friendly, reusable, and readable way.
Using the QueryParam class directly from the SDK - not optimal
The QueryParam
class allows us to define custom URL parameters when doing API calls - especially those not directly supported by the InventoryFilter
class which is used by the InventoryApi
class.
The getManagedObjects()
method of the inventoryApi
returns an ManagedObjectCollection
object, for which the interesting and simple method get
is available.
This get
method takes the pageSize as the first parameter and one or more QueryParam
s as following parameters.
Note: This applies not only to the inventory API but also to other APIs mentioned in the introduction.
In the below simple example the withParents URL parameter is used with the value true.
However, this code gets quite huge when using more parameters. The next section shows a convenience class that reduces the code and ensures reusability.
import com.cumulocity.sdk.client.Filter;
import com.cumulocity.sdk.client.QueryParam;
QueryParam queryParam = new QueryParam(new Param() {
@Override
public String getName() {
return "withParents";
}
}, "true");
Iterable<ManagedObjectRepresentation> managedObjects = inventoryApi
.getManagedObjects()
.get(50, queryParam)
.allPages();
Create a convenience class for code simplification
The below class CustomQueryParam
implements the Param interface and allows easy, repetitive use QueryParam
objects to define any URL parameter of the inventory API.
Any URL parameter can be simply provided as an enum with its actual parameter name - feel free to use and extend this enum.
See below for how to use this enum and how it simplifies the code.
public enum CustomQueryParam implements Param {
WITH_TOTAL_PAGES("withTotalPages"),
PAGE_SIZE("pageSize"),
QUERY("query"),
DEVICE_QUERY("q"),
DATE_FROM("dateFrom"),
STATUS("status"),
FRAGMENT_TYPE("fragmentType"),
DEVICE_ID("deviceId"),
REVERT("revert"),
;
private String name;
private String value;
public String getValue() {
return value;
}
private CustomQueryParam(final String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
public CustomQueryParam setValue(final String value) {
this.value = value;
return this;
}
public QueryParam toQueryParam() {
return new QueryParam(this, Filter.encode(value));
}
}
Making use of the CustomQueryParam class
Now we come back to our initial example and see how we can simplify this.
Additionally we can solve our initial problem to also query by creation time easily.
We can easily create 2 QueryParam objects for the withParents URL parameter and for the creation time query - both as a (long) one-liner.
The get
method can then be called with the page size and both query parameters in one go.
Now allPages
nicely returns an Iterable with the result of our query.
int pageSize = 50;
QueryParam withParents = CustomQueryParam.WITH_PARENTS.setValue("true").toQueryParam();
QueryParam creationTimeQuery = CustomQueryParam.QUERY.setValue("creationTime.date ge " + "'" + CustomUtil.getDateWithTime() + "'").toQueryParam();
Iterable<ManagedObjectRepresentation> managedObjects = inventoryApi
.getManagedObjects()
.get(pageSize, withParents, creationTimeQuery)
.allPages();
The above code would result in the following API call:
GET /inventory/managedObjects?pageSize=50&withParents=true&query=$filter=creationTime.date gt '2023-06-12T12:00:00.000Z' and creationTime.date le '2023-06-12T12:59:59.999Z'
Conclusion
The above best practice shows that even any complex API calls using multiple URL parameters can be implemented much easier with the given convenience class and methods to allow a fast transfer from the raw API call into the microservice implementation.