Wisdom-Wamp is an extension to Wisdom-Framework to expose services using the WAMP v1 protocol. It supports:
- RPC invocation (client to server)
- Event notification (client to server and server to client)
Notice that WAMP(http://wamp.ws/) is based is a Web Socket sub-protocol. The WAMP v1 specification is available here (http://wamp.ws/spec/wamp1/).
It uses the OSGi Event Admin to deal with WAMP events.
Add the following dependency to your pom.xml
file:
<dependency>
<groupId>org.wisdom-framework</groupId>
<artifactId>wisdom-wamp</artifactId>
<version>${project.version}</version>
</dependency>
Or copy the jar file to the Wisdom's Application
directory, as well an implementation of the OSGi Event Admin
such as Apache Felix Event Admin (downloadable from the
Apache Felix Download Page.
We also recommend the use of the Autobahn JavaScript library to interact using the WAMP protocol from JavaScript. Autobahn is available within a WebJar:
<dependency>
<groupId>org.webjars</groupId>
<artifactId>autobahnjs</artifactId>
<version>0.8.2</version>
</dependency>
NOTE: the 0.8.2 is the last version of autobahn implementing WAMP v1.
The only thing to do is to allow the Wamp subprotocol. In the src/main/configuration/application.conf
file, add:
# Websocket subprotocols
wisdom.websocket.subprotocols = wamp
The WAMP support provides a org.wisdom.wamp.services.Wamp
service that let you register and unregister object
published using WAMP. In the following example, an instance of 'Calc' is registered.
package org.wisdom.wamp.sample;
import org.apache.felix.ipojo.annotations.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wisdom.api.DefaultController;
import org.wisdom.wamp.services.ExportedService;
import org.wisdom.wamp.services.RegistryException;
import org.wisdom.wamp.services.Wamp;
@Component
@Provides
@Instantiate
public class WampController extends DefaultController implements EventHandler {
@Requires
private Wamp wamp;
private ExportedService ref;
private final static Logger LOGGER = LoggerFactory.getLogger(WampController.class);
@Validate
public void start() throws RegistryException {
LOGGER.debug("Published service: " + wamp.getServices());
ref = wamp.register(new Calc(), "/calc");
}
@Invalidate
public void stop() {
if (ref != null) {
wamp.unregister(ref);
}
}
}
Registration is made using the register
method, taking as parameter the object to expose and the url. Notice that
you must unregister your service. The registration gives you an ExportService
that you have to use to unregister it.
The WAMP events are bridged to the OSGi Event Admin. So if we extend the previous example to receive and send events, there result looks like:
package org.wisdom.wamp.sample;
import com.google.common.collect.ImmutableMap;
import org.apache.felix.ipojo.annotations.*;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wisdom.api.DefaultController;
import org.wisdom.wamp.services.ExportedService;
import org.wisdom.wamp.services.RegistryException;
import org.wisdom.wamp.services.Wamp;
@Component
@Provides
@Instantiate
public class WampController extends DefaultController implements EventHandler {
@Requires
private Wamp wamp;
@Requires
private EventAdmin ea;
@ServiceProperty(name = EventConstants.EVENT_TOPIC)
private String[] topics = new String[]{"simple"};
private ExportedService ref;
private final static Logger LOGGER = LoggerFactory.getLogger(WampController.class);
@Validate
public void start() throws RegistryException {
LOGGER.debug("Published service: " + wamp.getServices());
ref = wamp.register(new Calc(), "/calc");
// Publishing an event.
ea.postEvent(new Event("wamp/data", ImmutableMap.of("message", "hello")));
}
@Invalidate
public void stop() {
if (ref != null) {
wamp.unregister(ref);
}
}
/**
* Called by the {@link org.osgi.service.event.EventAdmin} service to notify the listener of an
* event.
*
* @param event The event that occurred.
*/
@Override
public void handleEvent(Event event) {
LOGGER.info("Receiving message from {} with {}", event.getTopic(), event.getProperty(Wamp.WAMP_EVENT_PROPERTY));
}
}
The Event Admin service is retrieved using @Requires EventAdmin ea;
. Sending event is done using the EventAdmin .postEvent()
method. Events must be send on topics under /wamp
, as wamp/data
in the example. This topic is
translated to WAMP by just prefixing it with the server name and HTTP port,
such as : http://localhost:9000/wamp/data
.
Receiving events is also done using the Event Admin. You component must publish the EventHandler
service with the
topics
property selecting the listened topics. Unlike sending, the 'wamp' prefix is removed by the WAMP service.
Event sent on http://localhost:9000/wamp/data
are received on /data
.
Here is an example of JavaScript client using autobahn.js:
<!DOCTYPE html>
<html>
<head>
<title>Wamp Example</title>
<link rel="stylesheet" href="socket.css">
<script src="/libs/autobahn.js"></script>
<script>
// WAMP session object
var sess;
var wsuri = "ws://localhost:9000/wamp";
window.onload = function () {
// connect to WAMP server
ab.connect(wsuri,
// WAMP session was established
function (session) {
sess = session;
appendTextArea("Connected to " + wsuri);
// subscribe to topic, providing an event handler
sess.subscribe("http://localhost:9000/wamp/simple", onEvent);
},
// WAMP session is gone
function (code, reason) {
sess = null;
appendTextArea("Connection lost (" + reason + ")");
}
);
};
function onEvent(topic, event) {
appendTextArea("received event from " + topic + " : " + JSON.stringify(event));
}
function publishEvent() {
sess.publish("http://localhost:9000/wamp/simple", {a: "foo", b: "bar", c: 23});
}
function callProcedure() {
// issue an RPC, returns promise object
sess.call("http://localhost:9000/wamp/calc#add", 23, 7).then(
// RPC success callback
function (res) {
appendTextArea("got invocation result: " + res);
},
// RPC error callback
function (error, desc) {
appendTextArea("got invocation error: " + desc);
}
);
}
function appendTextArea(newData) {
var el = getTextAreaElement();
el.value = el.value + '\n> ' + newData;
}
function getTextAreaElement() {
return document.getElementById('responseText');
}
</script>
<script src="/assets/jquery-2.0.3.min.js"></script>
<link rel="stylesheet" href="/libs/css/bootstrap.min.css"/>
<link rel="stylesheet" href="/libs/css/bootstrap-theme.min.css"/>
<script src="/libs/jquery.js"></script>
<script src="/libs/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="starter-template">
<h1>AutobahnJS WAMP Client</h1>
<button onclick="publishEvent();">Publish Event</button>
<button onclick="callProcedure();">Call Procedure</button>
<h2>Interaction</h2>
<textarea id="responseText"></textarea>
</div>
</div>
</body>
</html>
Event sender can configure the eligible and exclusion list by setting the two following properties in the sent event:
wamp.exclusions
: the list of excluded clients (List<String>
)wamp.eligible
: the list of eligible clients (List<String>
)