Skip to content
This repository has been archived by the owner on May 7, 2020. It is now read-only.

Commit

Permalink
Mqtt transport: Redesign the service to cover more usecases. (#3715)
Browse files Browse the repository at this point in the history
* This will keep the 'old' MqttService API untouched for full backwards compatibility.
* MqttBrokerConnection is exported now.
* Improve comments all over the code.

MqttService:
* Allows to add / remove / enumerate broker connections, additionally to the existing textual configuration.
* A new listener interface allows to be notified of added / removed broker connections.
* All proxy methods are deprecated now. The MqttBrokerConnection can be accessed directly.
* Add unit tests

MqttWillAndTestament:
* Introduce an isValid() method used to make fromString return null if not all necessary properties are provided.

MqttSenderChannel:
* Is depreacted now. MqttBrokerConnection.publish() should be used instead, becaue the interface does not allow to implement an async behaviour.

MqttBrokerConnection:
* Reconnection is no longer handled via a fixed 60s interval timer, but fully customizable.
  The default implementation (PeriodicReconnectPolicy) resembles the old behaviour.
* A SSL broker connection no longer uses a non-configurable "trust all and everything" SSL context provider,
  but an interface that have to provide the SSL context provider. A default implementation, again,
  resembles the old behaviour.
* Remove 'async' configuration option. All communication aspects (connect/disconnect/publish/subscribe) are handled in an async fashion now.
* Add unit tests

Signed-off-by: David Gräff <david.graeff@web.de>
  • Loading branch information
David Gräff authored and Simon Kaufmann committed Jul 12, 2017
1 parent cb7b0ad commit 7474e58
Show file tree
Hide file tree
Showing 28 changed files with 1,498 additions and 861 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src/test/java"/>
<classpathentry kind="output" path="target/test-classes"/>
</classpath>
28 changes: 28 additions & 0 deletions bundles/io/org.eclipse.smarthome.io.transport.mqtt.test/.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.eclipse.smarthome.io.transport.mqtt.test</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.8
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#Fri Feb 18 22:39:16 CET 2011
activeProfiles=
eclipse.preferences.version=1
fullBuildGoals=process-test-resources
includeModules=false
resolveWorkspaceProjects=true
resourceFilterGoals=process-resources resources\:testResources
skipCompilerPlugin=true
version=1
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Tests for the Mqtt transport I/O bundle
Bundle-SymbolicName: org.eclipse.smarthome.io.transport.mqtt.test
Bundle-Version: 0.9.0.qualifier
Bundle-Vendor: Eclipse.org/SmartHome
Fragment-Host: org.eclipse.smarthome.io.transport.mqtt
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: org.eclipse.smarthome.io.transport.mqtt,
org.eclipse.smarthome.test,
org.eclipse.smarthome.test.java,
org.hamcrest;core=split,
org.junit,
org.junit.runner,
org.junit.runners,
org.mockito
Require-Bundle: org.junit,org.mockito,org.hamcrest
28 changes: 28 additions & 0 deletions bundles/io/org.eclipse.smarthome.io.transport.mqtt.test/about.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
<title>About</title>
</head>
<body lang="EN-US">
<h2>About This Content</h2>

<p>June 5, 2006</p>
<h3>License</h3>

<p>The Eclipse Foundation makes available all content in this plug-in (&quot;Content&quot;). Unless otherwise
indicated below, the Content is provided to you under the terms and conditions of the
Eclipse Public License Version 1.0 (&quot;EPL&quot;). A copy of the EPL is available
at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>.
For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>

<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is
being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may
apply to your use of any object code in the Content. Check the Redistributor's license that was
provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
indicated below, the terms and conditions of the EPL still apply to any source code in the Content
and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p>

</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
source.. = src/test/java/
output.. = target/test-classes/
bin.includes = META-INF/,\
about.html
39 changes: 39 additions & 0 deletions bundles/io/org.eclipse.smarthome.io.transport.mqtt.test/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

<parent>
<groupId>org.eclipse.smarthome.bundles</groupId>
<artifactId>io</artifactId>
<version>0.9.0-SNAPSHOT</version>
</parent>

<properties>
<bundle.symbolicName>org.eclipse.smarthome.io.transport.mqtt.test</bundle.symbolicName>
<bundle.namespace>org.eclipse.smarthome.io.transport.mqtt.test</bundle.namespace>
</properties>

<modelVersion>4.0.0</modelVersion>
<groupId>org.eclipse.smarthome.io</groupId>
<artifactId>org.eclipse.smarthome.io.transport.mqtt.test</artifactId>

<name>Eclipse SmartHome Mqtt Transport I/O Tests</name>

<packaging>eclipse-test-plugin</packaging>

<build>
<plugins>
<plugin>
<groupId>${tycho-groupid}</groupId>
<artifactId>target-platform-configuration</artifactId>
<configuration>
<environments combine.self="override"></environments>
</configuration>
</plugin>
<plugin>
<groupId>${tycho-groupid}</groupId>
<artifactId>tycho-surefire-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/**
* Copyright (c) 2014-2017 by the respective copyright holders.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.smarthome.io.transport.mqtt.test;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

import java.util.Arrays;

import javax.naming.ConfigurationException;

import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.smarthome.io.transport.mqtt.MqttBrokerConnection;
import org.eclipse.smarthome.io.transport.mqtt.MqttConnectionObserver;
import org.eclipse.smarthome.io.transport.mqtt.MqttMessageSubscriber;
import org.eclipse.smarthome.io.transport.mqtt.MqttWillAndTestament;
import org.junit.Test;

/**
* Tests the MqttBrokerConnection class
*
* @author David Graeff - Initial contribution
*/
public class MqttBrokerConnectionTests {
@Test
public void testConstructor() throws ConfigurationException {
// Test tcp and ssl URLs
MqttBrokerConnection a = new MqttBrokerConnection("name", "tcp://123.123.123.123", false);
MqttBrokerConnection b = new MqttBrokerConnection("name", "ssl://123.123.123.123", true);
assertFalse(a.isTextualConfiguredBroker());
assertTrue(b.isTextualConfiguredBroker());
}

@Test(expected = ConfigurationException.class)
public void testConstructorInvalidProtocol() throws ConfigurationException {
new MqttBrokerConnection("name", "unsupported://123.123.123.123", false);
}

@Test(expected = ConfigurationException.class)
public void testConstructorInvalidName() throws ConfigurationException, MqttException {
new MqttBrokerConnection(" ", "tcp://123.123.123.123", false);
}

@Test
public void messageConsumerTests() throws ConfigurationException, MqttException {
final String url = "tcp://123.123.123.123";
final String name = "TestName12@!";
MqttBrokerConnection a = new MqttBrokerConnection(name, url, false);
assertFalse(a.isConsumers());
MqttMessageSubscriber subscriber = mock(MqttMessageSubscriber.class);
when(subscriber.getTopic()).thenAnswer(i -> "topic");
a.addConsumer(subscriber);
assertTrue(a.isConsumers());
a.removeConsumer(subscriber);
assertFalse(a.isConsumers());
}

@Test
public void connectionObserverTests() throws ConfigurationException {
final String url = "tcp://123.123.123.123";
final String name = "TestName12@!";
MqttBrokerConnection a = new MqttBrokerConnection(name, url, false);
assertFalse(a.isConnectionObservers());
MqttConnectionObserver connectionObserver = mock(MqttConnectionObserver.class);
a.addConnectionObserver(connectionObserver);
// Adding a connection observer should immediately call its setConnected() method.
verify(connectionObserver).setConnected(false);

assertTrue(a.isConnectionObservers());
a.removeConnectionObserver(connectionObserver);
assertFalse(a.isConnectionObservers());
}

@Test
public void setterGetterTests() throws ConfigurationException {
final String url = "tcp://123.123.123.123";
final String name = "TestName12@!";
MqttBrokerConnection a = new MqttBrokerConnection(name, url, false);
assertEquals("URL getter", a.getUrl(), url);
assertEquals("Name getter", a.getName(), name);

a.setClientId("clientid");
assertEquals("ClientID getter/setter", "clientid", a.getClientId());
// client ids longer than 23 characters should be ignored
a.setClientId("clientidclientidclientidclientid");
assertEquals("ClientID too long check", "clientid", a.getClientId());

a.setCredentials("user@!", "password123@^");
assertEquals("User getter/setter", "user@!", a.getUser());
assertEquals("Password getter/setter", "password123@^", a.getPassword());

assertEquals(MqttBrokerConnection.DEFAULT_KEEPALIVE_INTERVAL, a.getKeepAliveInterval());
a.setKeepAliveInterval(80);
assertEquals(80, a.getKeepAliveInterval());

assertFalse(a.isRetain());
a.setRetain(true);
assertTrue(a.isRetain());

assertNull(a.getLastWill());
assertNull(MqttWillAndTestament.fromString(""));
a.setLastWill(MqttWillAndTestament.fromString("topic:message:1:true"));
assertTrue(a.getLastWill().getTopic().equals("topic"));
assertEquals(1, a.getLastWill().getQos());
assertEquals(true, a.getLastWill().isRetain());
byte b[] = { 'm', 'e', 's', 's', 'a', 'g', 'e' };
assertTrue(Arrays.equals(a.getLastWill().getPayload(), b));

assertEquals(MqttBrokerConnection.DEFAULT_QOS, a.getQos());
a.setQos(10);
assertEquals(MqttBrokerConnection.DEFAULT_QOS, a.getQos());
a.setQos(-10);
assertEquals(MqttBrokerConnection.DEFAULT_QOS, a.getQos());
a.setQos(2);
assertEquals(2, a.getQos());
a.setQos(1);
assertEquals(1, a.getQos());

// Check for default ssl context provider and reconnect policy
assertNotNull(a.getSSLContextProvider());
assertNotNull(a.getReconnectPolicy());

assertFalse(a.isConnected());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* Copyright (c) 2014-2017 by the respective copyright holders.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.smarthome.io.transport.mqtt.test;

import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Map;

import javax.naming.ConfigurationException;

import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.smarthome.io.transport.mqtt.MqttBrokerConnection;
import org.eclipse.smarthome.io.transport.mqtt.MqttBrokersObserver;
import org.eclipse.smarthome.io.transport.mqtt.MqttService;
import org.junit.Test;

/**
* Tests the MqttService class
*
* @author David Graeff - Initial contribution
*/
public class MqttServiceTests {
// Tests addBrokersListener/removeBrokersListener
@Test
public void brokerConnectionListenerTests() throws ConfigurationException {
MqttService service = new MqttService();
assertFalse(service.isBrokerObservers());
MqttBrokersObserver observer = mock(MqttBrokersObserver.class);

service.addBrokersListener(observer);
assertTrue(service.isBrokerObservers());

MqttBrokerConnection connection = new MqttBrokerConnection("name", "tcp://123.123.123.123", false);
assertTrue(service.addBrokerConnection(connection));
verify(observer).brokerAdded(connection);

service.removeBrokerConnection(connection);
verify(observer).brokerRemoved(connection);

service.removeBrokersListener(observer);
assertFalse(service.isBrokerObservers());
}

// Tests extractBrokerConfigurations() and addBrokerConnection(map)
@Test
public void textualConfigurationTests() throws ConfigurationException, MqttException {
MqttService service = new MqttService();

Dictionary<String, String> properties = new Hashtable<>();
properties.put("bam.name", "brokername");
properties.put("bam.url", "tcp://123.123.123.123");
Map<String, Map<String, String>> map = service.extractBrokerConfigurations(properties);
assertEquals(map.size(), 1);
Map<String, String> data = map.get("bam");
assertNotNull(data);
assertEquals("brokername", data.get("name"));
assertEquals("tcp://123.123.123.123", data.get("url"));

assertThat(service.getAllBrokerConnections(), hasSize(0));
service.addBrokerConnection(data);
assertThat(service.getAllBrokerConnections(), hasSize(1));
assertNotNull(service.getBrokerConnection("brokername"));
}

@Test
public void brokerConnectionAddRemoveEnumerateTests() {
MqttService service = new MqttService();
MqttBrokerConnection connection;
try {
connection = new MqttBrokerConnection("name", "tcp://123.123.123.123", false);
} catch (ConfigurationException c) {
fail("Couldn't create a MqttBrokerConnection object");
return;
}
// Add
assertThat(service.getAllBrokerConnections(), hasSize(0));
assertTrue(service.addBrokerConnection(connection));
assertFalse(service.addBrokerConnection(connection));

// Get/Enumerate
assertNotNull(service.getBrokerConnection("name"));
assertThat(service.getAllBrokerConnections(), hasSize(1));

// Remove
service.removeBrokerConnection(connection);
assertThat(service.getAllBrokerConnections(), hasSize(0));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ Import-Package: javax.net,
org.slf4j
Service-Component: OSGI-INF/mqtt-*.xml
Bundle-ClassPath: .
Bundle-ActivationPolicy: lazy
Loading

0 comments on commit 7474e58

Please sign in to comment.