All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.thethingsnetwork.data.amqp.Client Maven / Gradle / Ivy

There is a newer version: 2.1.3
Show newest version
/*
 * The MIT License
 *
 * Copyright (c) 2017 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.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.thethingsnetwork.data.common.AbstractClient;
import static org.thethingsnetwork.data.common.AbstractClient.MAPPER;
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;
import org.thethingsnetwork.data.common.messages.ActivationMessage;
import org.thethingsnetwork.data.common.messages.DataMessage;
import org.thethingsnetwork.data.common.messages.DownlinkMessage;
import org.thethingsnetwork.data.common.messages.RawMessage;
import org.thethingsnetwork.data.common.messages.UplinkMessage;

/**
 *
 * @author Romain Cambier
 */
public class Client extends 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();

        String queue = channel.queueDeclare().getQueue();

        channel.basicConsume(queue, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String[] tokens = envelope.getRoutingKey().split("\\.");
                if (tokens.length < 4) {
                    return;
                }
                switch (tokens[3]) {
                    case "up":
                        if (handlers.containsKey(UplinkHandler.class)) {
                            String field;
                            if (tokens.length > 4) {
                                field = concat(4, tokens);
                            } else {
                                field = null;
                            }
                            handlers.get(UplinkHandler.class).stream()
                                    .forEach((handler) -> {
                                        executor.submit(() -> {
                                            try {
                                                UplinkHandler uh = (UplinkHandler) handler;

                                                if (uh.matches(tokens[2], field)) {
                                                    if (uh.isField()) {
                                                        uh.handle(tokens[2], new RawMessage() {
                                                            String str = new String(body);

                                                            @Override
                                                            public String asString() {
                                                                return str;
                                                            }
                                                        });
                                                    } else {
                                                        uh.handle(tokens[2], MAPPER.readValue(body, UplinkMessage.class));
                                                    }
                                                }
                                            } catch (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], MAPPER.readValue(body, ActivationMessage.class));
                                                    }
                                                } catch (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 RawMessage() {
                                                            String str = new String(body);

                                                            @Override
                                                            public String asString() {
                                                                return str;
                                                            }
                                                        });
                                                    }
                                                } catch (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 subscribe(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 (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, DownlinkMessage _payload) throws IOException {
        channel.basicPublish(exchange, appId + "/devices/" + _devId + "/down", null, MAPPER.writeValueAsBytes(_payload));
    }

    @Override
    public Client onConnected(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(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(String _devId, String _field, 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, DataMessage _data) {
                _handler.accept(_devId, _data);
            }

            @Override
            public String getDevId() {
                return _devId;
            }

            @Override
            public String getField() {
                return _field;
            }
        });
        return this;
    }

    @Override
    public Client onMessage(String _devId, BiConsumer _handler) {
        return onMessage(_devId, null, _handler);
    }

    @Override
    public Client onMessage(BiConsumer _handler) {
        return onMessage(null, null, _handler);
    }

    @Override
    public Client onActivation(String _devId, 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, ActivationMessage _data) {
                _handler.accept(_devId, _data);
            }

            @Override
            public String getDevId() {
                return _devId;
            }
        });
        return this;
    }

    @Override
    public Client onActivation(BiConsumer _handler) {
        return onActivation(null, _handler);
    }

    @Override
    public Client onDevice(String _devId, String _event, 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, RawMessage _data) {
                _handler.accept(_devId, _event, _data);
            }

            @Override
            public String getDevId() {
                return _devId;
            }

            @Override
            public String getEvent() {
                return _event;
            }
        });
        return this;
    }

    @Override
    public Client onDevice(String _devId, TriConsumer _handler) {
        return onDevice(_devId, null, _handler);
    }

    @Override
    public Client onDevice(TriConsumer _handler) {
        return onDevice(null, null, _handler);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy