Create a REST endpoint on a Raspberry Pi using Amdatu
In this blogpost I am taking you on a short journey of creating an OSGi application using the Amdatu stack. The application can store a message and return the message using a REST endpoint. Of course we are going to develop using Intellij, which is a challenge compared to eclipse with the nice plugin called BndTools. As a dessert I’ll show you how to deploy to an OSGi container on the raspberry pi using Apache Ace.
We are going to create an application that returns a message when someone asks for it. That is fun, but we are going to implement the application as a number of OSGi bundles and we are going to expose it using rest. Well if that is not fun enough, there is a desert. The end result is going to be deployed to a raspberry pi using the apache Ace project. The application is a very basic message service. It has two operations, you can set a message and you can ask for the message. This sounds very easy, but even with such a basic application there are already so many things that can be demonstrated. I am not going into to much detail about what OSGi is, I have written about that in the past on my other blog: http://www.gridshore.nl and other have written a lot more about it. Check the references for a book around this topic by two colleagues of mine: Paul Bakker and Bert Ertman.
Application structure
The application consists of 4 modules:
- messages-api : Contains the interface as provided by the implementations of this api,
- messages-memory : Contains an in memory implementation of the messages-api bundle,
- messages-client : Console implementation of a client to interact with the provided message service,
- messages-rest : Module using the Amdatu rest module to expose the message service as a rest endpoint.
Setting up the project
Sources
If you want to check the sources, follow along or just run the sample, the sources are on Github. https://github.com/jettro/some-osgi-modules
Using Gradle
As mentioned we want to use Intellij to create the code. Intellij has good support for gradle and gradle has good support for OSGi. Therefore we are going to use Gradle as the glue. Grade has support for a multi module setup. This is configured in the <settings.gradle.
include 'message-api','message-memory','message-client','messages-rest'
This is straightforward, you should recognise the 4 modules mentioned before. Next the main build.gradle, this is sort of a parent build file that all other inherit. Still you can override it in the other build files. A good example is the version attribute. It is configured in the main file, however if you want to change it in one of the modules, just specify it there as well and you override the parent one.
subprojects {
apply plugin: 'java'
apply plugin: 'osgi'
group = 'nl.gridshore.tryout'
version = '1.0'
repositories {
mavenCentral()
}
dependencies {
compile('org.osgi:org.osgi.core:4.2.0') {
exclude module:'org.osgi.compendium'
}
compile ('org.apache.felix:org.apache.felix.dependencymanager:3.2.0') {
exclude module:'org.osgi.core'
exclude module:'org.osgi.compendium'
}
}
}
Notice that we use subproject to configure the properties for all the modules. Important is the apply plugin: ‘osgi’, this enables us to generate bundles from grade. Now we look at one of the modules. I am not going to discuss all the modules. Check the sources if you want to. I take the messages-rest module to explain.
version = '1.0.1'
dependencies {
compile files('../libs/org.amdatu.web.rest.jaxrs-1.0.5.jar')
compile project(':message-api')
}
jar {
manifest {
instruction 'Private-Package','nl.gridshore.tryout'
instruction 'Bundle-Vendor', 'Gridshore'
instruction 'Bundle-Description', 'MessageService REST api'
instruction 'Bundle-Activator', 'nl.gridshore.tryout.RESTActivator'
}
}
Notice the dependencies property. In here we specify the dependency on the Amdatu rest bundle that is in a local folder. It is a shame they are not in the central maven repository, but from Gradle this is not a big problem. The second dependency is on our local bundle, the messages-api. This is how it works with Gradle. The other important part is the jar property. In here we pass some configuration options for the manifest of the OSGi bundle to generate. You can now build the project using gradle.
gradle build
Enough about gradle, let us import the project into Intellij.
Intellij
For this I use Intellij 14, just do File > Open and select the build.gradle file in the root of the project. In the next screen I choose to use a local gradle distribution (2.2). Push the OK button and wait a few seconds. Now comes the part that is not ideal when using intellij and later an outside of intellij distribution at the same time. My workflow is to use grade clean before building with intellij. Than in intellij choose Build > Rebuild project. When not doing this, I had some issues with the compile paths. This will most likely be fixed in the future. Later on I’ll discuss how to create a runner for an OSGi container from within Intellij.
Checking the code
As mentioned before you can find the sources on github.
The api
The api bundle only contains an interface and a bundle activator. The activator does nothing since the api module only specifies what other modules will deliver. The interface has two methods as discussed before.
public interface MessageService {
String showMessage();
void storeMessage(String message);
}
The in memory implementation
To make working with OSGi a lot easier than in the past, I am using the felix dependency manager. Like the name says, it makes working with dependencies a lot easier. One of the things to use is the parent class for the activator. We use the DependencyActivatorBase class as a parent class to our activator class. With this parent class registering the service within the OSGi container becomes very easy.
public class ImplActivator extends DependencyActivatorBase {
public void init(BundleContext context, DependencyManager manager) throws Exception {
manager.add(createComponent()
.setInterface(MessageService.class.getName(), null)
.setImplementation(MessageServiceMemory.class));
}
}
Notice that we set the MessageService class from the messages-api bundle to be the interface to deliver. Than we set the implementation to be the class MessagesServiceMemory, which we are going to discuss next. Now client application can look up an implementation for the MessageService service.
public class MessageServiceMemory implements MessageService {
private String message = "This is the default message";
public String showMessage() {
return message;
}
public void storeMessage(String message) {
this.message = message;
}
}
That must be one of the easiest classes you have ever seen. That’s it. Next we have a look at a client class that makes use of our very nice service.
Verify that is works using the console
The client itself is again very basic. Just an object with a dependency on the MessageService class.
public class MessageServiceConsole {
private volatile MessageService messageService;
public void showMessage() {
System.out.println(messageService.showMessage());
}
public void enterMessage(String message) {
messageService.storeMessage(message);
}
}
Notice that the client is using the console to print out the message it obtains from the MessageService. When using Intellij you get a warning that the messageService is never assigned. That is correct from intellij perspective. Here we make use of the dependency manager. This is done in the activator of the client. Let’s have a look.
public class ClientActivator extends DependencyActivatorBase {
@Override
public void init(BundleContext context, DependencyManager manager) throws Exception {
Properties props = new Properties();
props.put(CommandProcessor.COMMAND_SCOPE, "messageservice");
props.put(CommandProcessor.COMMAND_FUNCTION, new String[]{"showMessage","enterMessage"});
manager.add(createComponent()
.setInterface(Object.class.getName(), props)
.setImplementation(MessageServiceConsole.class)
.add(createServiceDependency().setService(MessageService.class).setRequired(true)));
}
}
There are two important things to take note of in this activator class. The first few lines specify the commands that you can perform in the console. In this case there are two commands. The showMessage and the enterMessage command. The next part is about the service discovery as well as the dependency injection. It is the last line that is different from the ones we have seen before. Here we create a service dependency, notice that we just specify the interface to fulfil and that we make it required. If the service is required, the bundle will not start without it. In the section about running the sample we show you how it works.
Creating a rest endpoint
This is the final button we are going to discuss. We already discussed the dependency on the amdatu rest bundle for this module. The Amdatu rest bundle makes use of jaxrs. We have a bean with one property called message and there is a resource object containing some jaxrs annotations. This class makes use of the same MessageService as the client does that we discussed in the previous section.
@Path("message")
public class MessageResource {
private volatile MessageService messageService;
@GET
@Produces(APPLICATION_JSON)
public Message message() {
if (messageService == null) {
return new Message("No service found");
}
return new Message(messageService.showMessage());
}
@POST
@Produces(APPLICATION_JSON)
public Message message(Message message) {
if (messageService == null) {
return new Message("No service found");
}
messageService.storeMessage(message.getMessage());
return new Message(messageService.showMessage());
}
}
I am not going to discuss everything about jaxrs. Using the Path annotation we specify the endpoint. In the methods we specify how the Get and Post requests are handled. Notice that we do return messages if the message service is not available. This indicates that the MessageService is not required to function. That can be checked in the Activator for this bundle as well.
public class RESTActivator extends DependencyActivatorBase {
@Override
public void init(BundleContext context, DependencyManager manager) throws Exception {
manager.add(
createComponent()
.setInterface(Object.class.getName(), null)
.setImplementation(MessageResource.class)
.add(createServiceDependency().setService(MessageService.class))
);
}
}
The most important thing to notice here is that the MessageService is not configured as required.
Running the sample
So far so good, this all works fine, but now comes the part where Eclipse and BndTools shine. Making a container that runs our application.
Facets in Intellij
Intellij does not recognise the the osgi plugin in gradle. Therefore we have to manually add the OSGi facet to the modules. Open up project settings and open the facets tab. Now use the plus sign to add facets. The following image shows the screen you should see in front of you.
We need to do this for each module, than we have a few things to configure. Open the configuration tab for the facet and choose the activator and the symbolic name in the tab Manifest generation. The screen should look like the following image.
Intellij does have support for running an OSGi application. Open Run > Edit Configurations and use the plus sign to create a new configuration. Chose OSGi Bundles on the left (use the show all if you do not see it yet). The following image shows the screen that you should see as well. If you need more information about setting up felix in intellij, please check this link.
Just try it out, push the run button and check the prompt. Now you can issue some GoGo commands like lb. With this command you can watch the loaded bundles. But wait this is not fun, there is nothing here from our own bundles. So let us add some bundles. Go back to the run configuration screen and use the plus sign at the bottom to add some bundles. The following screen is greeting me, if you are following a long you most probably have a different screen. So what happens here?