
org.thethingsnetwork.data.amqp.Client Maven / Gradle / Ivy
/*
* The MIT License
*
* Copyright (c) 2016 The Things Network
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.thethingsnetwork.data.amqp;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.util.Base64;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.json.JSONObject;
import org.thethingsnetwork.data.common.AbstractClient;
import org.thethingsnetwork.data.common.Subscribable;
import org.thethingsnetwork.data.common.TriConsumer;
import org.thethingsnetwork.data.common.events.AbstractEventHandler;
import org.thethingsnetwork.data.common.events.ActivationHandler;
import org.thethingsnetwork.data.common.events.ConnectHandler;
import org.thethingsnetwork.data.common.events.ErrorHandler;
import org.thethingsnetwork.data.common.events.EventHandler;
import org.thethingsnetwork.data.common.events.UplinkHandler;
/**
*
* @author Romain Cambier
*/
public class Client implements AbstractClient {
/**
* Connection settings
*/
private final String appId;
private final ConnectionFactory factory;
private final String exchange;
/**
* Event settings
*/
private final ExecutorService executor = Executors.newCachedThreadPool();
private final Map> handlers = new HashMap<>();
/**
* Runtime vars
*/
private Connection connection;
private Channel channel;
/**
* Create a new Client from a custom broker
*
* @param _broker The broker address, including protocol and port
* @param _appId Your appId (or appEUI for staging)
* @param _appAccessKey Your appAccessKey
* @param _exchange The exchange to subscribe on
* @throws java.net.URISyntaxException if the provided broker address is malformed
*/
public Client(String _broker, String _appId, String _appAccessKey, String _exchange) throws URISyntaxException {
appId = _appId;
factory = new ConnectionFactory();
factory.setHost(_broker);
factory.setUsername(_appId);
factory.setPassword(_appAccessKey);
exchange = _exchange;
}
/**
* Create a new Client from a custom broker
*
* @param _broker The broker address, including protocol and port
* @param _appId Your appId (or appEUI for staging)
* @param _appAccessKey Your appAccessKey
* @throws java.net.URISyntaxException if the provided broker address is malformed
*/
public Client(String _broker, String _appId, String _appAccessKey) throws URISyntaxException {
this(_broker, _appId, _appAccessKey, "ttn.handler");
}
/**
* Return the connection factory so that it can be tuned
*
* @return the connection factoiry that will be used to open the connection
*/
public ConnectionFactory getConnectionFactory() {
if (connection != null) {
throw new RuntimeException("Can not be called while client is running");
}
return factory;
}
@Override
public Client start() throws Exception {
if (connection != null) {
throw new RuntimeException("Already connected");
}
connection = factory.newConnection();
channel = connection.createChannel();
final String queue = channel.queueDeclare().getQueue();
channel.basicConsume(queue, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, final byte[] body) throws IOException {
final String[] tokens = envelope.getRoutingKey().split("\\.");
if (tokens.length < 4) {
return;
}
switch (tokens[3]) {
case "up":
if (handlers.containsKey(UplinkHandler.class)) {
handlers.get(UplinkHandler.class).stream().forEach((handler) -> {
executor.submit(() -> {
try {
UplinkHandler uh = (UplinkHandler) handler;
if (uh.matches(tokens[2])) {
uh.handle(tokens[2], uh.transform(new String(body)));
}
} catch (final Exception ex) {
if (handlers.containsKey(ErrorHandler.class)) {
handlers.get(ErrorHandler.class).stream().forEach((org.thethingsnetwork.data.common.events.EventHandler handler1) -> {
executor.submit(() -> {
((ErrorHandler) handler1).safelyHandle(ex);
});
});
}
}
});
});
}
break;
case "events":
if (tokens.length > 5) {
switch (tokens[4]) {
case "activations":
if (handlers.containsKey(ActivationHandler.class)) {
handlers.get(ActivationHandler.class).stream().forEach((handler) -> {
executor.submit(() -> {
try {
ActivationHandler ah = (ActivationHandler) handler;
if (ah.matches(tokens[2])) {
ah.handle(tokens[2], new JSONObject(new String(body)));
}
} catch (final Exception ex) {
if (handlers.containsKey(ErrorHandler.class)) {
handlers.get(ErrorHandler.class).stream().forEach((org.thethingsnetwork.data.common.events.EventHandler handler1) -> {
executor.submit(() -> {
((ErrorHandler) handler1).safelyHandle(ex);
});
});
}
}
});
});
}
break;
default:
if (handlers.containsKey(AbstractEventHandler.class)) {
handlers.get(AbstractEventHandler.class).stream().forEach((handler) -> {
executor.submit(() -> {
try {
AbstractEventHandler aeh = (AbstractEventHandler) handler;
String event = concat(4, tokens);
if (aeh.matches(tokens[2], event)) {
aeh.handle(tokens[2], event, new JSONObject(new String(body)));
}
} catch (final Exception ex) {
if (handlers.containsKey(ErrorHandler.class)) {
handlers.get(ErrorHandler.class).stream().forEach((org.thethingsnetwork.data.common.events.EventHandler handler1) -> {
executor.submit(() -> {
((ErrorHandler) handler1).safelyHandle(ex);
});
});
}
}
});
});
}
}
}
break;
}
}
});
for (List ehl : handlers.values()) {
for (EventHandler eh : ehl) {
eh.subscribe(new Subscribable() {
private static final String WILDCARD_WORD = "*";
private static final String WILDCARD_PATH = "#";
@Override
public void subscibe(String[] _key) throws Exception {
StringJoiner sj = new StringJoiner(".");
for (String key : _key) {
sj.add(key);
}
channel.queueBind(queue, exchange, sj.toString());
}
@Override
public String getWordWildcard() {
return WILDCARD_WORD;
}
@Override
public String getPathWildcard() {
return WILDCARD_PATH;
}
});
}
}
if (handlers.containsKey(ConnectHandler.class)) {
handlers.get(ConnectHandler.class).stream().forEach((handler) -> {
executor.submit(() -> {
try {
((ConnectHandler) handler).handle(() -> channel);
} catch (final Exception ex) {
if (handlers.containsKey(ErrorHandler.class)) {
handlers.get(ErrorHandler.class).stream().forEach((org.thethingsnetwork.data.common.events.EventHandler handler1) -> {
executor.submit(() -> {
((ErrorHandler) handler1).safelyHandle(ex);
});
});
}
}
});
});
}
return this;
}
private String concat(int _ignore, String[] _tokens) {
StringJoiner sj = new StringJoiner(".");
for (int i = _ignore; i < _tokens.length; i++) {
sj.add(_tokens[i]);
}
return sj.toString();
}
@Override
public Client end() throws InterruptedException, IOException {
if (connection == null) {
throw new RuntimeException("Not connected");
}
return end(5000);
}
@Override
public Client end(long _timeout) throws InterruptedException, IOException {
if (connection == null) {
throw new RuntimeException("Not connected");
}
executor.awaitTermination(_timeout, TimeUnit.MILLISECONDS);
connection.close((int) _timeout);
if (!connection.isOpen()) {
connection = null;
}
return this;
}
@Override
public Client endNow() throws IOException {
if (connection == null) {
throw new RuntimeException("Not connected");
}
connection.abort();
connection = null;
return this;
}
@Override
public void send(String _devId, byte[] _payload, int _port) throws IOException {
JSONObject data = new JSONObject();
data.put("payload_raw", Base64.getEncoder().encodeToString(_payload));
data.put("port", _port != 0 ? _port : 1);
channel.basicPublish(exchange, appId + "/devices/" + _devId + "/down", null, data.toString().getBytes());
}
@Override
public void send(String _devId, JSONObject _payload, int _port) throws IOException {
JSONObject data = new JSONObject();
data.put("payload_fields", _payload);
data.put("port", _port != 0 ? _port : 1);
channel.basicPublish(exchange, appId + "/devices/" + _devId + "/down", null, data.toString().getBytes());
}
@Override
public void send(String _devId, ByteBuffer _payload, int _port) throws IOException {
JSONObject data = new JSONObject();
_payload.rewind();
byte[] payload = new byte[_payload.capacity() - _payload.remaining()];
_payload.get(payload);
data.put("payload_fields", Base64.getEncoder().encodeToString(payload));
data.put("port", _port != 0 ? _port : 1);
channel.basicPublish(exchange, appId + "/devices/" + _devId + "/down", null, data.toString().getBytes());
}
@Override
public Client onConnected(final Consumer _handler) {
if (connection != null) {
throw new RuntimeException("Already connected");
}
if (!handlers.containsKey(ConnectHandler.class)) {
handlers.put(ConnectHandler.class, new LinkedList<>());
}
handlers.get(ConnectHandler.class)
.add(new ConnectHandler() {
@Override
public void handle(org.thethingsnetwork.data.common.Connection _client) {
_handler.accept(_client);
}
});
return this;
}
@Override
public Client onError(final Consumer _handler) {
if (connection != null) {
throw new RuntimeException("Already connected");
}
if (!handlers.containsKey(ErrorHandler.class)) {
handlers.put(ErrorHandler.class, new LinkedList<>());
}
handlers.get(ErrorHandler.class).add(new ErrorHandler() {
@Override
public void handle(Throwable _error) {
_handler.accept(_error);
}
});
return this;
}
@Override
public Client onMessage(final String _devId, final String _field, final BiConsumer _handler) {
if (connection != null) {
throw new RuntimeException("Already connected");
}
if (!handlers.containsKey(UplinkHandler.class)) {
handlers.put(UplinkHandler.class, new LinkedList<>());
}
handlers.get(UplinkHandler.class).add(new UplinkHandler() {
@Override
public void handle(String _devId, Object _data) {
_handler.accept(_devId, _data);
}
@Override
public String getDevId() {
return _devId;
}
@Override
public String getField() {
return _field;
}
});
return this;
}
@Override
public Client onMessage(final String _devId, final BiConsumer _handler) {
return onMessage(_devId, null, _handler);
}
@Override
public Client onMessage(final BiConsumer _handler) {
return onMessage(null, null, _handler);
}
@Override
public Client onActivation(final String _devId, final BiConsumer _handler) {
if (connection != null) {
throw new RuntimeException("Already connected");
}
if (!handlers.containsKey(ActivationHandler.class)) {
handlers.put(ActivationHandler.class, new LinkedList<>());
}
handlers.get(ActivationHandler.class).add(new ActivationHandler() {
@Override
public void handle(String _devId, JSONObject _data) {
_handler.accept(_devId, _data);
}
@Override
public String getDevId() {
return _devId;
}
});
return this;
}
@Override
public Client onActivation(final BiConsumer _handler) {
return onActivation(null, _handler);
}
@Override
public Client onDevice(final String _devId, final String _event, final TriConsumer _handler) {
if (connection != null) {
throw new RuntimeException("Already connected");
}
if (!handlers.containsKey(AbstractEventHandler.class)) {
handlers.put(AbstractEventHandler.class, new LinkedList<>());
}
handlers.get(AbstractEventHandler.class).add(new AbstractEventHandler() {
@Override
public void handle(String _devId, String _event, JSONObject _data) {
_handler.accept(_devId, _event, _data);
}
@Override
public String getDevId() {
return _devId;
}
@Override
public String getEvent() {
return _event;
}
});
return this;
}
@Override
public Client onDevice(final String _devId, final TriConsumer _handler) {
return onDevice(_devId, null, _handler);
}
@Override
public Client onDevice(final TriConsumer _handler) {
return onDevice(null, null, _handler);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy