- RELEASE NOTES
- Resource Access Proxy (RAP) Plugin
Added support for notification subscriptions and removed old API for listeners.
Updated dependency to SymbIoTeLibraries:6.0.0
Supports use with Java 8 and 11. Uses new version of SpringBoot 2.2.x and new version of SpringCloud Greenwich.SR1. Current deprecated classes and methods will be removed in 1.1.x
This version has new listener interfaces. The old ones are deprecated.
Versions until 0.3.5 had different artifact name. Now the artifact name is ResourceAccessProxyPluginStarter
The idea of RAP Plugin is to use it as dependency in implementation that connects your platform with SymbIoTe. Generic parts like RabbitMQ communication with RAP component is implemented in this library. That way a developer doesn't have to implement complex communication.
Example of project using RAP plugin starter is in following repository https://github.com/symbiote-h2020/RapPluginExample
Create new SpringBoot (2.2.7) project with no dependencies.
Add following dependencies for cutting edge version:
compile('com.github.symbiote-h2020:ResourceAccessProxyPluginStarter:develop-SNAPSHOT') { changing = true }
or add following for specific version:
compile('com.github.symbiote-h2020:ResourceAccessProxyPluginStarter:{version}')
Current version is 1.0.4
.
This is dependency from jitpack repository. In order to use
jitpack you need to put in build.gradle
following lines as
well:
repositories {
...
maven { url "https://jitpack.io" } // this is important to add
}
Configuration needs to be put in application.properties
or YML file. An example is here:
spring.application.name=RapPluginExample
rabbit.host=localhost
rabbit.username=guest
rabbit.password=guest
rabbit.replyTimeout=60000
rap-plugin.filtersSupported=false
rap-plugin.notificationsSupported=false
The first line is defining the name of this specific RAP plugin.
NOTE: When you register resource in the registration JSON
there is pluginId
which needs to be this name. In this case
it needs to be RapPluginExample
.
The second group of lines is configuration of RabbitMQ server. The RAP plugin communicates with RAP component by using RabbitMQ server. For connecting to RabbitMQ server RAP plugin needs to know host, username and password. The default values are in this example.
The third group of lines are configuration of RAP plugin.
If filtersSupported
is false
then
the RAP component will filter responses from RAP plugin. Then the RAP plugin needs to return
all observations when history is asked. If this value is true
then the RAP is responsible
for filtering data. The data is send in new listeners.
If notificationsSupported
is false
it means that RAP plugin can not stream data. If it
is true
then it can accept subscriptions and RAP plugin is responsible for pushing data
on changed resource. Subscriptions are supported by registering SubscriptionListener
.
There are following RAP plugin consumers:
- for reading resources there is
ResourceAccessListener
- for activating actuator there is
ActuatorAccessListener
- for invoking service there is
ServiceAccessListener
- for beginning and ending subscription there is
SubscriptionListener
Registering and unregistering resources is done by calling register...
or unregister...
methods
in RapPlugin
class. RapPlugin
class can be injected like any other bean.
When resource needs to be read the RAP component will send message to RAP plugin and that message will cause calling method in registered listener.
Listener ResourceAccessListener
has following methods:
String getResource(List<ResourceInfo> resourceInfo)
for reading last observation. The argumentresourceInfo
represents resource. Internal id can be extracted by usingUtils.getInternalResourceId(resourceInfo)
It returns last observed value converted to JSON ans returned asString
.String getResourceHistory(List<ResourceInfo> resourceInfo, int top, Query filterQuery)
for reading historical observed values. The arguments areresourceInfo
that represents resource,top
is value of how many observations needs to be returned andfilterQuery
are other filters (if propertyrap-plugin.filtersSupported
isfalse
it will always benull
). It returns observations that are convertes to JSON and returned asString
.
In the case that reading is not possible method should throw
RapPluginException
.
Here is example of registering and handling faked values for resource with
internal id rp_isen1
or isen1
:
rapPlugin.registerReadingResourceListener(new ResourceAccessListener() {
@Override
public String getResourceHistory(List<ResourceInfo> resourceInfo, int top, Query filterQuery) {
LOG.debug("reading resource history with info {}", resourceInfo);
String resourceId = Utils.getInternalResourceId(resourceInfo);
if("rp_isen1".equals(resourceId) || "isen1".equals(resourceId)) {
// This is the place to put reading history data of sensor.
List<Observation> observations = new LinkedList<>();
for (int i = 0; i < top; i++) {
observations.add(createObservation(resourceId));
}
try {
return mapper.writeValueAsString(observations);
} catch (JsonProcessingException e) {
throw new RapPluginException(500, "Can not convert observations to JSON", e);
}
} else {
throw new RapPluginException(404, "Sensor not found.");
}
}
@Override
public String getResource(List<ResourceInfo> resourceInfo) {
LOG.debug("reading resource with info {}", resourceInfo);
String resourceId = Utils.getInternalResourceId(resourceInfo);
if("rp_isen1".equals(resourceId) || "isen1".equals(resourceId)) {
// This is place to put reading data from sensor
try {
return mapper.writeValueAsString(createObservation(resourceId));
} catch (JsonProcessingException e) {
throw new RapPluginException(500, "Can not convert observation to JSON", e);
}
}
throw new RapPluginException(404, "Sensor not found.");
}
});
For actuating resource there is listener interface ActuatorAccessListener
with only one method:
void actuateResource(String internalId, Map<String, Map<String, Value>> capabilities)
.
Arguments are: internal resource ID and actuation capability map.
The key of map is capability name and value is parameters map.
Parameters map has key which
is parameter name and value is Value
interface. Value
interface has 4 implementations:
PrimitiveValue
, PrimitiveValueArray
, ComplexValue
and ComplexValueArray
.
This method can throw RapPluginException
when actuation can not be executed.
Here is example implementation:
rapPlugin.registerActuatingResourceListener(new ActuatorAccessListener() {
@Override
public void actuateResource(String internalId, Map<String, Map<String, Value>> capabilities) {
System.out.println("Called actuation for resource " + internalId);
// print capabilities
for (Entry<String, Map<String, Value>> capabilityEntry: capabilities.entrySet()) {
System.out.println("Capability: " + capabilityEntry.getKey());
for(Entry<String, Value> parameterEntry: capabilityEntry.getValue().entrySet()) {
System.out.print(" " + parameterEntry.getKey() + " = ");
PrimitiveValue primitiveValue = parameterEntry.getValue().asPrimitive();
if(primitiveValue.isString()) {
System.out.println(primitiveValue.asString());
} else if (primitiveValue.isInt()) {
System.out.println(primitiveValue.asInt());
} else {
System.out.println(primitiveValue.toString());
}
}
}
if("rp_iaid1".equals(internalId) || "iaid1".equals(internalId)) {
// This is place to put actuation code for resource with id
System.out.println("iaid1 is actuated");
return;
} else {
throw new RapPluginException(404, "Actuating entity not found.");
}
}
});
For invoking service there is listener interface ServiceAccessListener
with only one method:
String invokeService(String internalId, Map<String, Value> parameters)
.
Arguments are: internal resource ID and actuation parameters map.
Parameters map has key which
is parameter name and value is Value
interface (the same as in case of actuation,
see details there).
This method can throw RapPluginException
when invoking service can not be executed.
Here is example implementation:
rapPlugin.registerInvokingServiceListener(new ServiceAccessListener() {
@Override
public String invokeService(String internalId, Map<String, Value> parameters) {
System.out.println("In invoking service of resource " + internalId);
// print parameters
for(Entry<String, Value> parameterEntry: parameters.entrySet()) {
System.out.println(" Parameter - name: " + parameterEntry.getKey() + " value: " +
parameterEntry.getValue().asPrimitive().asString());
}
try {
if("rp_isrid1".equals(internalId)) {
return mapper.writeValueAsString("ok");
} else if ("isrid1".equals(internalId)) {
return mapper.writeValueAsString("some json");
} else {
throw new RapPluginException(404, "Service not found.");
}
} catch (JsonProcessingException e) {
throw new RapPluginException(500, "Can not convert service response to JSON", e);
}
});
For handling subscriptions there is SubscriptionListener
interface. It has two methods:
public void subscribeResource(String resourceId)
- called when subscription for resource with internal id is started
public void unsubscribeResource(String resourceId)
- called whrn subscription for resource is terminated
Plugin implementor must handle when to send notifications.
It could be on some event or periodic. The notification can be send to
subscriber by using method sendNotification
in rapPlugin
object.
This method accepts Observation
object. This method sends observation to RAP and RAP forwards it to users over websocket.
Here is complete example of periodic subscription:
rapPlugin.registerNotificationResourceListener(new SubscriptionListener() {
private Set<String> subscriptionSet = Collections.synchronizedSet(new HashSet<>()); // internal ids of subscribed resources
private volatile Thread subscriptionThread; // currently active thread
@Override
public void subscribeResource(String resourceId) {
LOG.info("Subscribing to {}", resourceId);
synchronized (subscriptionSet) {
subscriptionSet.add(resourceId);
if (subscriptionThread == null) {
subscriptionThread = new Thread(() -> { // thread that send notifications
while (!subscriptionSet.isEmpty()) {
sendPush(); // send notification for all subscribed resources
try {
Thread.sleep(5000); // notifications are send every 5 sec
} catch (InterruptedException e) {
e.printStackTrace();
}
}
subscriptionThread = null;
});
subscriptionThread.start();
}
}
}
private void sendPush() {
LOG.info("Sending notifications!!!!");
ObjectMapper mapper = new ObjectMapper();
synchronized (subscriptionSet) {
for (String id : subscriptionSet) {
Observation observation = createObservation(id);
rapPlugin.sendNotification(observation);
LOG.info("Notification for resource {}: {}", id, observation);
}
}
}
@Override
public void unsubscribeResource(String resourceId) {
LOG.info("Unsubscribe to {}", resourceId);
synchronized (subscriptionSet) {
subscriptionSet.remove(resourceId);
}
}
});
You can run this component as any other spring boot application.
./gradlew bootRun
or
java -jar build/libs/RapPluginExample-0.0.1-SNAPSHOT.jar
Note: In order to function correctly you need to start RabbitMQ and RAP component before.
public Observation createObservation(String sensorId) {
Location loc = new WGS84Location(48.2088475, 16.3734492, 158, "Stephansdome", Arrays.asList("City of Wien"));
TimeZone zoneUTC = TimeZone.getTimeZone("UTC");
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormat.setTimeZone(zoneUTC);
Date date = new Date();
String timestamp = dateFormat.format(date);
long ms = date.getTime() - 1000;
date.setTime(ms);
String samplet = dateFormat.format(date);
ObservationValue obsval =
new ObservationValue(
"7",
new Property("Temperature", "TempIRI", Arrays.asList("Air temperature")),
new UnitOfMeasurement("C", "degree Celsius", "C_IRI", null));
ArrayList<ObservationValue> obsList = new ArrayList<>();
obsList.add(obsval);
Observation obs = new Observation(sensorId, loc, timestamp, samplet , obsList);
try {
LOG.debug("Observation: \n{}", new ObjectMapper().writeValueAsString(obs));
} catch (JsonProcessingException e) {
LOG.error("Can not convert observation to JSON", e);
}
return obs;
}