Repository navigation
Client Library Introduction & Tutorial
This document is currently a work-in-progress and thus is both incomplete and barely tested.
Currently, the Arrowhead consortium provides application system developers with client skeletons. These are stand-alone code examples of how to implement the communication with the Arrowhead Core Systems. These skeletons are intended to be downloaded and then modified to fit the developers' current project. This approach allows for maximum flexibility as the developers can easily modify any part of the code.
However, using skeletons also come at a cost. The skeletons are essentially copied for every new application system made. The developers therefore leave numerous cases of code duplication in their wake. As any good practices in software development will state, code duplication is generally bad - or at the very least a bad smell. One of the more notable reasons being the issue of handling bugs. For example, if we say that serious security one were to be found in these skeletons, hundreds of application systems will have to be manually updated to fix it - most likely leaving Arrowhead in a state where many application systems have not been updated thus will have the security issue.
The client library is an alternative to the skeletons, proposed to reduce code duplication, make it easier to implement application systems, and allow the consortium to update the client code without requiring any actions by the application system developers (well, in some cases). Where skeletons are code that are downloaded and modified, libraries are not modified by the application system developers. They can therefore be distributed in a binary format and linked dynamically at runtime. As a result, the library can be updated without any modification to the application systems, as long as the API is either left intact or backward compatible.
On the plus side, a library reduces code duplication, but it also introduces a bit of rigidity. Where code can easily be modified in a skeleton, a library would have to explicitly support modifications, for example by allowing application system developers to inherit (and thereby override methods) or supply listeners to modify behaviour.
The client library described below have been designed to simplify the develop of application
systems, yet make it clear for the developers when they are communicating with an Arrowhead
Core System - and which one. It will therefore contain a number of classes with names ending
in with Client, like the OrchestrationClient. These emphasises the communication with a
core system and let the developer apply generic Arrowhead theory, which states that a
consumer should look up the provider using the Orchestration core system. The library has
also been designed to support a wide range of different use cases, letting the application
system developers tailor its behaviour to their needs. However, it has only been tested on a
very few limited use cases so far, and thus please do not hesitate to report missing
features or inconveniences, for example through
GitHub issues.
The Client library is developed under the official Arrowhead Client repository in the refactored branches, currently feature/refactored+common is the most recent head, which (in contrast to feature/refactored) supports installation of application systems with Debian packages. Note, that this branch matches with the Core repository branch feature/common, where the two in combination will allow core and client systems to be installed side-by-side on the same Debian-based server.
This guide is written for Linux, for other operating systems you may have to adapt some of the commands yourself. Also, I assume you are familiar with Git and Maven.
The following procedure describes how application system developers should currently
download and install the library. For a guide on how to develop on the client library (in
combination with the core system), see:
Setting up an Intellij IDEA project for Arrowhead development.
Note that building application systems with Maven (even through IntelliJ IDEA) requires the
mvn install step below.
Currently, no binary packages are provided. Ideally, the Client library should be provided within a Maven repository for developers to easily include this in their projects - preferably the official one, but alternatively in a 3rd party repository by the consortium. However, for now, the only way is to download the code and compile it for yourself:
git clone --depth 1 \
--branch feature/refactored+common \
https://github.com/arrowhead-f/client-java.git
cd client-java
mvn compileTo include the Client library in other Maven projects, install it into your local Maven repository:
mvn installWhen generating Debian install packages for your application systems, you will also need the packages for the client library. These can be generated with:
mvn packageThe packages can be found inside the target folder. Note, that this will generate both the
arrowhead-common and the arrowhead-client-common packages. The arrowhead-common
package is the same as generated for the core systems (assuming you used the
feature/common branch), and
thus is only required for systems not already having the core systems installed. Clients,
built with this client library, should work against all (recent) branches of the core
systems, you will only need the
feature/common branch to
install core and client systems side-by-side.
When deploying your application systems, install the common packages first:
dpkg -i arrowhead-client-common_*.deb arrowhead-common_*.debTo illustrate the basic use of the Client Library, we can redo the four skeleton examples. For simplicity I will skip the import lines, but they should be pretty straight forward to add. The full example is available in the System of System repository under the feature/refactored branch.
Let us call this project for "Demo", and make a parent pom.xml for it:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>eu.arrowhead</groupId>
<artifactId>demo</artifactId>
<version>4.1.1-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>client-demo-common</module>
<module>client-demo-consumer</module>
<module>client-demo-provider</module>
<module>client-demo-publisher</module>
<module>client-demo-subscriber</module>
</modules>
<properties>
<mvn.compiler.version>3.8.0</mvn.compiler.version>
<mvn.compile.target>1.8</mvn.compile.target>
<maven.jar.version>3.1.1</maven.jar.version>
<maven.dependency.version>3.1.1</maven.dependency.version>
<arrowhead.version>4.1.1-SNAPSHOT</arrowhead.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>eu.arrowhead</groupId>
<artifactId>arrowhead-client-common</artifactId>
<version>${arrowhead.version}</version>
</dependency>
<dependency>
<groupId>eu.arrowhead.demo</groupId>
<artifactId>client-demo-common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<defaultGoal>clean install</defaultGoal>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${mvn.compiler.version}</version>
<configuration>
<source>${mvn.compile.target}</source>
<target>${mvn.compile.target}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>Most projects will need a module for sharing common classes and functionality. For this Demo
project we will create a client-demo-common package to hold the data model used in the
communication. The pom.xml file is pretty straight forward:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>demo</artifactId>
<groupId>eu.arrowhead</groupId>
<version>4.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>eu.arrowhead.demo</groupId>
<artifactId>client-demo-common</artifactId>
</project>Here we add a single class, describing a temperature readout:
package eu.arrowhead.demo.model;
public class Temperature {
private long timestamp;
private double temperature;
public Temperature() {
}
public Temperature(long timestamp, double temperature) {
this.timestamp = timestamp;
this.temperature = temperature;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public double getTemperature() {
return temperature;
}
public void setTemperature(double temperature) {
this.temperature = temperature;
}
}Moving ahead to our first Application system, we first have to define the pom.xml for it.
This should refer to client library and the demo common library as dependencies:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>demo</artifactId>
<groupId>eu.arrowhead</groupId>
<version>4.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>eu.arrowhead.demo</groupId>
<artifactId>client-demo-provider</artifactId>
<dependencies>
<dependency>
<groupId>eu.arrowhead</groupId>
<artifactId>arrowhead-client-common</artifactId>
</dependency>
<dependency>
<groupId>eu.arrowhead.demo</groupId>
<artifactId>client-demo-common</artifactId>
</dependency>
</dependencies>
</project>A provider basically consists of two classes, a main class and a resource class. The resource class defines the webservice and should be quiet familiar if you are experienced with java webservices:
package eu.arrowhead.demo.provider;
@Path("temperature")
@Produces(MediaType.APPLICATION_JSON)
public class TemperatureResource extends ArrowheadResource {
public TemperatureResource(ArrowheadHttpServer server) throws ArrowheadException {
super(server);
}
@GET
public Response get() {
Temperature readout = new Temperature(System.currentTimeMillis(), 1);
return Response.status(200).entity(readout).build();
}
}Notes:
-
It should extend
ArrowheadResourceand provide a matching constructor, passing the arguments along. While we are not using any of this functionality in this simple example, it is useful for more complicated systems as it allows us to consume services and perform other more complicated tasks in our provider. -
The
get()method constructs our dummy temperature response, and replies to any requests. -
Security is implicitly added through a security filter in the web server, thus on secure clouds
get()will only be called if the caller provides a valid token and signature in accordance with the Arrowhead specification. To change security method, it is possible to override the security filter when configuring the web server in the main class.
Now, to the main class:
package eu.arrowhead.demo.provider;
public class ProviderMain extends ArrowheadApplication {
public ProviderMain(String[] args) throws ArrowheadException {
super(args);
}
@Override
protected void onStart() throws ArrowheadException {
final ArrowheadSecurityContext securityContext = ArrowheadSecurityContext
.createFromProperties();
final ArrowheadHttpServer server = ArrowheadGrizzlyHttpServer
.createFromProperties(securityContext)
.addResources(TemperatureResource.class)
.addPackages("eu.arrowhead.demo")
.start();
final ServiceRegistryClient registry = ServiceRegistryClient
.createFromProperties(securityContext);
registry.register(ServiceRegistryEntry.createFromProperties(server));
}
@Override
protected void onStop() {
}
public static void main(String[] args) throws ArrowheadException {
new ProviderMain(args).start();
}
}Notes:
-
The main class should inherit from the
ArrowheadApplicationclass. -
Add a matching constructor which just parses the parameters along.
-
Implement the
onStart()method, this will be called automatically byArrowheadApplicationwhen it is ready for your application system to start:-
Create a security context - you pretty much always need one of these. Notice the use of the static method
createFromProperties()which constructs the object based on the information in the configuration file (shown below). Many library classes implements these kinds of methods. It is also possible to configure them directly in the code or using another set of properties. -
Create and start the HTTP server, again we use the
createFromProperties()method, add our temperature resource class, and add the package containing the model classes. -
Finally, our new provider is registered in the Service Registry, by constructing a client to it and calling
register()with the Entry for our system. Everything created from the data in the configuration file.
-
-
Implement the
onStop()method. No need to do anything here. Classes inheriting fromArrowheadServerwill have theironStop()methods called automatically. This includes theArrowheadGrizzlyHttpServerinstance we just started, as it also inherits fromArrowheadServer. However, if we started something not from the client library, it should be stopped here. -
Finally, we add a main method for creating the application and calling
start(), which is an inherited method fromArrowheadApplication.
The configuration file default.conf, from which the create methods fetches the
information:
# Directories
cert_dir=config/certificates
# Webserver parameters
#address=0.0.0.0
port=8461
secure=true
# System registry entry parameters
system_name=client-demo-provider
service_name=temperature
service_uri=temperature
interfaces=JSON, XML
service_metadata=unit-celsius
# Key and trust stores
auth_pub=authorization.pub
keystore=client-demo-provider.p12
keystorepass=123456
truststore=truststore.p12
truststorepass=123456
keypass=123456
# Core systems
#sr_address=0.0.0.0
#eh_address=0.0.0.0
#ca_address=0.0.0.0
#orch_address=0.0.0.0
# Logging parameters
#log4j.rootLogger=INFO, CONSOLE
#log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
#log4j.appender.CONSOLE.target=System.err
#log4j.appender.CONSOLE.ImmediateFlush=true
#log4j.appender.CONSOLE.Threshold=debug
#log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
#log4j.appender.CONSOLE.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss}, %l, %p, %m%nThe configuration file is essentially a Java properties file with a list of key/value pairs.
Any comment line (starting with #) means that the client library will use the default
value - this are defined in the class ArrowheadProperties. A quick run through the
settings above:
-
cert_dir: Location of the certificates. -
address,port: The webserver will bind to this IP and port. -
secure: Whether to start the system in secure or insecure mode. -
system_name: Name of your system. -
service_name,service_uri,interfaces,service_metadata: Strings for the service registry entry we sent when registering our service. -
auth_pub,keystore,truststore: Keys and truststore for secure mode. -
keystorepass,truststorepass,keypass: Passwords for keys and truststore. -
sr_address,eh_address,ca_address,orch_address: Addresses of the core systems. -
log4j.*: Logging parameters.
Note that you can create a file app.conf beside default.conf to override values in the
latter. This is useful for avoiding passwords and other system specific values inversion
control systems when developing for example.
Notes on running the provider:
-
Configuration files will be loaded from the current working directory per default.
-
Use the VM option
-DconfDir=config-insecureto change the configuration directory toconfig-insecureunder the current working directory. -
Use the program arguments
-daemonand-dto start in daemon and debug mode.
The pom.xmlfor the consumer is essentially the same as for the provider:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>demo</artifactId>
<groupId>eu.arrowhead</groupId>
<version>4.1.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>eu.arrowhead.demo</groupId>
<artifactId>client-demo-consumer</artifactId>
<dependencies>
<dependency>
<groupId>eu.arrowhead</groupId>
<artifactId>arrowhead-client-common</artifactId>
</dependency>
<dependency>
<groupId>eu.arrowhead.demo</groupId>
<artifactId>client-demo-common</artifactId>
</dependency>
</dependencies>
</project>The consumer is just a single main class:
package eu.arrowhead.demo.consumer;
class ConsumerMain extends ArrowheadApplication {
public ConsumerMain(String[] args) throws ArrowheadException {
super(args);
}
@Override
protected void onStart() {
final ArrowheadSecurityContext securityContext = ArrowheadSecurityContext
.createFromProperties(true);
final ArrowheadSystem me = ArrowheadSystem.createFromProperties();
final ServiceRequestForm srf = new ServiceRequestForm.Builder(me)
.requestedService(getProps().getServiceName(), "json", getProps().isSecure())
.metadata(ServiceMetadata.Keys.UNIT, "celsius")
.flag(OrchestrationFlags.Flags.OVERRIDE_STORE, true)
.flag(OrchestrationFlags.Flags.PING_PROVIDERS, false)
.flag(OrchestrationFlags.Flags.METADATA_SEARCH, false)
.flag(OrchestrationFlags.Flags.ENABLE_INTER_CLOUD, true)
.build();
log.info("Service Request payload: " + ArrowheadConverter.json().toString(srf));
final OrchestrationClient orchestration = OrchestrationClient
.createFromProperties(securityContext);
final OrchestrationStrategy strategy = new OrchestrationStrategy
.Always(orchestration, srf);
final HttpClient client = new HttpClient(strategy, securityContext);
Timer timer = new Timer();
TimerTask authTask = new TimerTask() {
@Override
public void run() {
final Response response = client.request(HttpClient.Method.GET);
Temperature readout = response.readEntity(Temperature.class);
log.info("The indoor temperature is " +
readout.getTemperature() +
" degrees celsius.");
}
};
timer.schedule(authTask, 2L * 1000L, 8L * 1000L);
}
@Override
protected void onStop() {
}
public static void main(String[] args) throws ArrowheadException {
new ConsumerMain(args).start();
}
}-
Like in the provider, we inherit from
ArrowheadApplication, and implement a matching constructor. -
In the
onStart()method:-
Again, a security context is needed.
-
For the service request form, we need a
ArrowheadSystemdescribing the requester. Again, thecreateFromProperties()method is used to create this from the properties file. -
The
ServiceRequestFormis built using the builder pattern, you should check the documentation on orchestration system for the details on the flags. We get the service name from the properties file. -
This request should be sent to the orchestration system, thus we create a client for it.
-
Now, the tricky part. Local clouds are dynamic, thus application systems may join, leave, and be replaced by others over time. We can therefore not rely on an orchestration response for all eternity. Currently, we have three orchestration strategies to choose from:
-
OrchestrationStrategy.Never: Will never do orchestration, you have to provide details of the service manually. This is mostly used within the core system clients. -
OrchestrationStrategy.Once: Orchestration will only be done once (when the strategy is constructed). Meaning that subsequent request may fail if the service is no longer available. -
OrchestrationStrategy.Always: Orchestration will be done at every request made - this might generate extra overhead on your cloud, but you will always be up to date with the current service registry. -
Ideally, there should also be an on demand strategy that will only do orchestration on outdated tokens and when a service does not respond. If you create one, please submit it so we can include it.
-
-
To create requests, we create a HTTP client for the temperature service using the orchestration strategy and security context.
-
Now we create a timer task, that regularly performs a GET request against using this client and prints the temperature into the log. Note that since we picked the always strategy, the orchestration is automatically performed during the call to
request().
-
-
The
onStop()andmainmethods are also the same as in the provider.
The configuration file and run parameters are essentially the same as for the provider, but the client does not use the values for the service registry entry as we do not register any services in it.
Most of the basic elements for constructing application systems with the Client Library should be clear now, so I will skip the common elements, and just show the features for making a publisher.
When publishing event to the event handler core system, you have the option of receiving feedback on the results. To receive those, create a resource class, like in the provider:
package eu.arrowhead.demo.publisher;
@Path("publisher")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class PublisherResource extends ArrowheadResource {
public PublisherResource(ArrowheadHttpServer server) throws ArrowheadException {
super(server);
}
@POST
@Path("feedback")
public Response eventFeedback(Map<String, Boolean> results) {
log.info("Event publishing results: " + results.toString());
return Response.ok().build();
}
}Now this also mean that the main class have to start a web server like the provider does:
package eu.arrowhead.demo.publisher;
class PublisherMain extends ArrowheadApplication {
private PublisherMain(String[] args) throws ArrowheadException {
super(args);
}
@Override
protected void onStart() throws ArrowheadException {
final ArrowheadSecurityContext securityContext = ArrowheadSecurityContext
.createFromProperties(true);
final ArrowheadHttpServer server = ArrowheadGrizzlyHttpServer
.createFromProperties(securityContext)
.addResources(PublisherResource.class)
.setSecurityFilter(new ArrowheadSecurityFilter())
.start();
final EventHandlerClient eventHandler = EventHandlerClient
.createFromProperties(securityContext);
final ArrowheadSystem me = ArrowheadSystem.createFromProperties(server);
final ArrowheadProperties props = getProps();
final String eventType = props.getEventType();
final boolean secure = props.isSecure();
Timer timer = new Timer();
TimerTask authTask = new TimerTask() {
@Override
public void run() {
final Event event = new Event(eventType,
String.format("Hello %s World",
secure ? "secure" : "insecure"));
eventHandler.publish(event, me);
}
};
timer.schedule(authTask, 2L * 1000L, 8L * 1000L);
}
@Override
protected void onStop() {
}
public static void main(String[] args) throws ArrowheadException {
new PublisherMain(args).start();
}
}Notes on the publisher main class:
-
Notice the change of security filter when starting the web server. Currently the Event Handler does not use token/signature security, so we have to reduce the security of the server. You should be careful here if you are creating a combined publisher and provider, as this will also reduce security on your provider. In these cases, you can either create your own security filter (inheriting from
ContainerRequestFilter,ArrowheadSecurityFilterorArrowheadTokenSignatureSecurityFilter), or you can start multiple servers on different ports. -
Now, sending events requires interaction with the event handler, thus we create a client for it.
-
For each event we need to describe, who the publisher is and which event type we are sending. Also, for this scenario we need to know if the system is started in secure mode or not. The two latter we get directly as values from the configuration file.
-
The publishing is encapsulated in a timer task to perform it regularly, and consists of constructing an event and publishing it through the event handler client.
The configuration file contains three new entries:
# Event parameters
feedback=true
feedback_uri=publisher/feedback
event_type=hello-
feedbackcontrols whether the event handler client will send the feedback to our system or not. -
feedback_urithe path of the feedback service. -
event_typedescribes what type of event our system sends. Note that we explicitly have to obtain and use this value, in contrast to the other values which are implicitly used by thecreateFromProperties()methods. You can also add your own custom parameters to the configuration file, and get them with the primitive get methods from theArrowheadPropertiesclass.
Events are pushed to a web service, thus our subscriber also need to implement a resource class:
package eu.arrowhead.demo.subscriber;
@Path("subscriber")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class SubscriberResource extends ArrowheadResource {
public SubscriberResource(ArrowheadHttpServer server) throws ArrowheadException {
super(server);
}
@POST
@Path("notify")
public Response receiveEvent(Event event) {
log.info("Received new event: " + event.toString());
return Response.ok().build();
}
}The main class, which starts the web server for this resource:
package eu.arrowhead.demo.subscriber;
class SubscriberMain extends ArrowheadApplication {
private SubscriberMain(String[] args) throws ArrowheadException {
super(args);
}
@Override
protected void onStart() throws ArrowheadException {
final ArrowheadSecurityContext securityContext = ArrowheadSecurityContext
.createFromProperties(true);
final ArrowheadHttpServer server = ArrowheadGrizzlyHttpServer
.createFromProperties(securityContext)
.addResources(SubscriberResource.class)
.setSecurityFilter(new ArrowheadSecurityFilter())
.start();
final EventHandlerClient eventHandler = EventHandlerClient
.createFromProperties(securityContext);
final ArrowheadSystem me = ArrowheadSystem.createFromProperties(server);
eventHandler.subscribe(getProps().getEventType(), me);
}
@Override
protected void onStop() {
}
public static void main(String[] args) throws ArrowheadException {
new SubscriberMain(args).start();
}
}Notes on the subscriber main:
-
Like in the publisher, we have to reduce the security. Again, be aware of this if you are building a combined publisher and provider.
-
To subscribe to events, we need an event handler client first, and a description of the subscriber.
-
Then we can subscribe to events, notice that we get the type from the configuration file again.
Besides the already introduced properties, the configuration file for the subscriber contains one new entry, which describes where the event handler should send the events:
notify_uri=subscriber/notifyTo add Debian package generation to your application systems, first add a jdeb version
property to the properties list in your project pom.xml:
<jdeb.version>1.7</jdeb.version>First, we need to make the demo common module installable, so in its pom.xml add the build
section:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>${maven.dependency.version}</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/lib/</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>jdeb</artifactId>
<groupId>org.vafer</groupId>
<version>${jdeb.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jdeb</goal>
</goals>
<configuration>
<deb>target/${project.artifactId}_${project.version}.deb</deb>
<verbose>true</verbose>
<controlDir>${basedir}/src/deb/control</controlDir>
<dataSet>
<data>
<type>file</type>
<src>${project.build.directory}/${project.build.finalName}.jar</src>
<mapper>
<type>perm</type>
<prefix>/usr/share/arrowhead/lib/demo</prefix>
</mapper>
</data>
</dataSet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>-
First
maven-dependency-plugincopies all our dependencies into thelibfolder. -
Then we use the
jdebplugin to generate the install package with the jar file for the demo common module.
Now, in this simplistic demo project we have no additional dependencies. However if we had, these could be added by additional data sections. For example if we used PostgreSQL in our project:
<data>
<type>file</type>
<src>${project.build.directory}/lib/postgresql-${postgresql.version}.jar</src>
<mapper>
<type>perm</type>
<prefix>/usr/share/arrowhead/lib/demo</prefix>
</mapper>
</data>Note that you should install these under /usr/share/arrowhead/lib/PROJECT_NAME to avoid
conflicts with official arrowhead packages and other projects.
Then, in the directory src/deb/control add four files. First control (you should change
the values were appropriate):
Package: [[name]]
Version: [[version]]
Section: contrib/java
Priority: optional
Architecture: all
Maintainer: Your Name <your@email.address>
Homepage: http://www.arrowhead.eu
Description: Common library for the library demo
Distribution: development
Depends: java-runtime-headless, arrowhead-client-common
Then postinst:
#!/bin/sh
set -e
case "$1" in
configure)
;;
abort-upgrade|abort-remove|abort-deconfigure)
;;
*)
echo "postinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
exit 0And postrm:
#!/bin/sh
set -e
. /usr/share/debconf/confmodule
case "$1" in
purge)
AH_CONF_DIR="/etc/arrowhead"
AH_SYSTEMS_DIR="${AH_CONF_DIR}/systems"
rmdir ${AH_SYSTEMS_DIR} 2>/dev/null || true
rmdir ${AH_CONF_DIR} 2>/dev/null || true
rmdir /var/log/arrowhead 2>/dev/null || true
db_purge
if [ -d ${AH_CONF_DIR} ]; then
echo "Directory '${AH_CONF_DIR}' is not empty, please remove manually" >&2
fi
;;
remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
;;
*)
echo "postrm called with unknown argument \`$1'" >&2
exit 1
;;
esac
exit 0And preinst:
#!/bin/sh
set -e
case "$1" in
install|upgrade)
;;
abort-upgrade)
;;
*)
echo "preinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
exit 0And finally prerm:
#!/bin/sh
set -e
case "$1" in
remove|upgrade|deconfigure)
;;
failed-upgrade)
;;
*)
echo "prerm called with unknown argument \`$1'" >&2
exit 1
;;
esac
exit 0Now you can run mvn package to generate the Debian install package for the demo common
module.
In the pom.xml for the application system, add a build section:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven.jar.version}</version>
<configuration>
<archive>
<manifestEntries>
<Class-Path>
lib/demo/client-demo-common-${project.version}.jar
</Class-Path>
</manifestEntries>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>eu.arrowhead.demo.provider.ProviderMain</mainClass>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>${maven.dependency.version}</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/lib/</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>jdeb</artifactId>
<groupId>org.vafer</groupId>
<version>${jdeb.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jdeb</goal>
</goals>
<configuration>
<deb>target/${project.artifactId}_${project.version}.deb</deb>
<verbose>true</verbose>
<controlDir>${basedir}/src/deb/control</controlDir>
<dataSet>
<data>
<type>file</type>
<src>${project.build.directory}/${project.build.finalName}.jar</src>
<mapper>
<type>perm</type>
<prefix>/usr/share/arrowhead</prefix>
<user>arrowhead</user>
<filemode>755</filemode>
</mapper>
</data>
<data>
<type>link</type>
<linkName>/usr/share/arrowhead/${project.artifactId}.jar</linkName>
<linkTarget>/usr/share/arrowhead/${project.build.finalName}.jar</linkTarget>
</data>
<data>
<type>file</type>
<src>${project.basedir}/src/deb/client-demo-provider.service</src>
<mapper>
<type>perm</type>
<prefix>/etc/systemd/system</prefix>
<filemode>664</filemode>
</mapper>
</data>
</dataSet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>Notes on the pom.xml:
-
In the section for
maven-jar-pluginyou have to add all the libraries you install as part of your own packages. Otherwise, Java will not be able to find these. Also, remember to set a full reference to the main class. -
Again,
maven-dependency-pluginandjdebis used to generate the package. Note that we add a service file to start and stop the Arrowhead system using systemd. Again, if you have addition dependencies specifically to this application system, you should add them with additional data section, like described under thepom.xmlfor the common module.
Create the service file src/deb/client-demo-provider.service (if you want to install
the application system on systems without the core systems, you should remove the dependency
on the service registry):
[Unit]
Description=client-demo-provider
After=network.target arrowhead-serviceregistry-sql.service
Requires=arrowhead-serviceregistry-sql.service
[Service]
WorkingDirectory=/etc/arrowhead/systems/client-demo-provider
ExecStart=/usr/bin/java -jar /usr/share/arrowhead/client-demo-provider.jar -d -daemon
TimeoutStopSec=5
Type=simple
User=arrowhead
Group=arrowhead
[Install]
WantedBy=default.target
Again, in the src/deb/control directory, we need five files. First control:
Package: [[name]]
Version: [[version]]
Section: contrib/java
Priority: optional
Architecture: all
Maintainer: Your Name <your@email.address>
Homepage: http://www.arrowhead.eu
Description: Arrowhead Demo Provider
Distribution: development
Depends: java-runtime-headless, arrowhead-common, arrowhead-client-common,
arrowhead-core-common, client-demo-common
Note we added arrowhead-core-common here, because we will add auto generation of the
certificate to the installation scripts below. You can remove this dependency, but then you
also need to remove the generate code from the scripts below, and the dependency on the
service registry in the service file above.
Then postinst:
#!/bin/sh
set -e
. /usr/share/debconf/confmodule
SYSTEM_NAME="client-demo-provider"
PKG_NAME="client-demo-provider"
case "$1" in
configure)
. /usr/share/arrowhead/conf/ahconf.sh
SYSTEM_DIR="${AH_SYSTEMS_DIR}/${SYSTEM_NAME}"
if [ ! -d "${SYSTEM_DIR}" ]; then
mkdir -p ${SYSTEM_DIR}
fi
ah_cert_signed_system ${SYSTEM_NAME}
ah_cert_export_pub "${AH_SYSTEMS_DIR}/authorization" "authorization" "${SYSTEM_DIR}"
if [ ! -f "${SYSTEM_DIR}/default.conf" ]; then
/bin/cat <<EOF >${SYSTEM_DIR}/default.conf
# Directories
cert_dir=.
# Webserver parameters
address=$(hostname -I | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
port=8461
secure=true
# System registry entry parameters
system_name=client-demo-provider
service_name=temperature
service_uri=temperature
interfaces=JSON, XML
service_metadata=unit-celsius
# Key and trust stores
keystorepass=${AH_PASS_CERT}
truststorepass=${AH_PASS_CERT}
keypass=${AH_PASS_CERT}
# Core systems
#sr_address=0.0.0.0
#eh_address=0.0.0.0
#ca_address=0.0.0.0
#orch_address=0.0.0.0
# Logging parameters
log4j.rootLogger=INFO, DB, FILE, CONSOLE
log4j.appender.DB=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.DB.URL=jdbc:mysql://127.0.0.1:3306/arrowhead
log4j.appender.DB.user=arrowhead
log4j.appender.DB.password=${AH_PASS_DB}
log4j.appender.DB.sql=INSERT INTO logs(id, date, origin, level, message) \
VALUES(DEFAULT,'%d{yyyy-MM-dd HH:mm:ss}','%C','%p','%m')
log4j.appender.DB.layout=org.apache.log4j.PatternLayout
log4j.logger.org.hibernate=fatal
log4j.appender.FILE=org.apache.log4j.FileAppender
log4j.appender.FILE.File=/var/log/arrowhead/${SYSTEM_NAME}.log
log4j.appender.FILE.ImmediateFlush=true
log4j.appender.FILE.Threshold=debug
log4j.appender.FILE.Append=false
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss}, %C, %p, %m%n
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.target=System.err
log4j.appender.CONSOLE.ImmediateFlush=true
log4j.appender.CONSOLE.Threshold=debug
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss} %p %-160m \
%c{1}.%M(%F:%L)%n
EOF
chown root:arrowhead ${SYSTEM_DIR}/default.conf
chmod 640 ${SYSTEM_DIR}/default.conf
fi
echo "Restarting ${PKG_NAME}..." >&2
systemctl daemon-reload
systemctl restart ${PKG_NAME}.service
;;
abort-upgrade|abort-remove|abort-deconfigure)
;;
*)
echo "postinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
exit 0In postinst, we used the scripts from the core system Debian packages to automatically
generate a new certificate for the provider system. It also writes a new configuration file,
if it does not already exist. Finally, we also start the new system.
And postrm, where we simply remove what we created:
#!/bin/sh
set -e
. /usr/share/debconf/confmodule
SYSTEM_NAME="client-demo-provider"
case "$1" in
purge)
AH_CONF_DIR="/etc/arrowhead"
AH_SYSTEMS_DIR="${AH_CONF_DIR}/systems"
SYSTEM_DIR="${AH_SYSTEMS_DIR}/${SYSTEM_NAME}"
rm -f \
/var/log/arrowhead/${SYSTEM_NAME}.log \
${SYSTEM_DIR}/default.conf \
${SYSTEM_DIR}/authorization.pub \
${SYSTEM_DIR}/${SYSTEM_NAME}.p12
rmdir ${SYSTEM_DIR} 2>/dev/null || true
rmdir /var/log/arrowhead 2>/dev/null || true
db_purge
;;
remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
;;
*)
echo "postrm called with unknown argument \`$1'" >&2
exit 1
;;
esac
exit 0And preinst:
#!/bin/sh
set -e
case "$1" in
install|upgrade)
;;
abort-upgrade)
;;
*)
echo "preinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
exit 0Finally prerm, where we only stop the system before uninstalling it:
#!/bin/sh
set -e
PKG_NAME="client-demo-provider"
case "$1" in
remove)
systemctl stop ${PKG_NAME}.service
;;
upgrade|deconfigure)
;;
failed-upgrade)
;;
*)
echo "prerm called with unknown argument \`$1'" >&2
exit 1
;;
esac
exit 0Making the other demo systems installable should be the same as for the provider (with minor modifications).