/*
# Licensed Materials - Property of IBM
# Copyright IBM Corp. 2015
*/
package com.ibm.streamsx.topology.test.messaging.mqtt;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import com.ibm.json.java.JSONObject;
import com.ibm.streamsx.topology.TSink;
import com.ibm.streamsx.topology.TStream;
import com.ibm.streamsx.topology.Topology;
import com.ibm.streamsx.topology.context.ContextProperties;
import com.ibm.streamsx.topology.context.StreamsContext;
import com.ibm.streamsx.topology.context.StreamsContextFactory;
import com.ibm.streamsx.topology.function.Function;
import com.ibm.streamsx.topology.function.Predicate;
import com.ibm.streamsx.topology.function.Supplier;
import com.ibm.streamsx.topology.function.UnaryOperator;
import com.ibm.streamsx.topology.logic.Value;
import com.ibm.streamsx.topology.messaging.mqtt.MqttStreams;
import com.ibm.streamsx.topology.test.InitialDelay;
import com.ibm.streamsx.topology.test.TestTopology;
import com.ibm.streamsx.topology.tester.Condition;
import com.ibm.streamsx.topology.tester.Tester;
import com.ibm.streamsx.topology.tuple.Message;
import com.ibm.streamsx.topology.tuple.SimpleMessage;
/**
* MQTT integration tests.
* <p>
* N.B. Optional configuration properties:
* <ul>
* topic(s) to use. Defaults to "testTopic1,testTopic2".</li>
* <li>{@code com.ibm.streamsx.topology.test.messaging.mqtt.serverURI} the
* MQTT broker serverURI. Defaults to "tcp:://localhost:1883".</li>
* <li>{@code com.ibm.streamsx.topology.test.messaging.mqtt.authMode} the
* authentication mode: null, "password", "ssl", "sslClientAuth".
* Default is null - no authentication.</li>
* <li>{@code com.ibm.streamsx.topology.test.messaging.mqtt.userID}
* userID for authMode=password</li>
* <li>{@code com.ibm.streamsx.topology.test.messaging.mqtt.password}
* password for authMode=password</li>
* <li>{@code com.ibm.streamsx.topology.test.messaging.mqtt.trustStore}
* trustStore pathname for authMode=ssl,sslClientAuth.</li>
* <li>{@code com.ibm.streamsx.topology.test.messaging.mqtt.trustStorePassword}
* trustStore password for authMode=ssl,sslClientAuth.</li>
* <li>{@code com.ibm.streamsx.topology.test.messaging.mqtt.keyStore}
* keyStore pathname for authMode=sslClientAuth.</li>
* <li>{@code com.ibm.streamsx.topology.test.messaging.mqtt.keyStorePassword}
* keyStore password for authMode=sslClientAuth.</li>
* </ul>
*/
public class MqttStreamsTest extends TestTopology {
private static final String PROP_PREFIX = "com.ibm.streamsx.topology.test.messaging.mqtt.";
private static final int SEC_TIMEOUT = 30;
private static final int PUB_DELAY_MSEC = 5*1000;
private final String BASE_CLIENT_ID = "mqttStreamsTestClientId";
private static final String uniq = simpleTS();
private boolean captureArtifacts = false;
private boolean setAppTracingLevel = false;
private java.util.logging.Level appTracingLevel = java.util.logging.Level.FINE;
private static final Map<String,String> authInfo = new HashMap<>();
static {
System.setProperty(PROP_PREFIX+"userID", System.getProperty("user.name"));
System.setProperty(PROP_PREFIX+"password", "myMosquittoPw");
initAuthInfo("userID");
initAuthInfo("password");
initAuthInfo("trustStore");
initAuthInfo("trustStorePassword");
initAuthInfo("keyStore");
initAuthInfo("keyStorePassword");
}
private void setupDebug() {
if (captureArtifacts)
getConfig().put(ContextProperties.KEEP_ARTIFACTS, true);
if (setAppTracingLevel)
getConfig().put(ContextProperties.TRACING_LEVEL, appTracingLevel);
}
private static String simpleTS() {
return new SimpleDateFormat("HH:mm:ss.SSS").format(new Date());
}
private String newSubClientId(String name) {
return newClientId(name+"_Sub");
}
private String newPubClientId(String name) {
return newClientId(name+"_Pub");
}
private String newClientId(String name) {
String clientId = BASE_CLIENT_ID + "_" + name + "_" + uniq.replaceAll(":", "");
System.out.println("["+simpleTS()+"] "
+ "Using MQTT clientID " + clientId);
return clientId;
}
/**
* Class that implements Message
*/
private static class MyMsgSubtype implements Message {
private static final long serialVersionUID = 1L;
private String more = "I am a MessageSubtype";
private String msg;
private String key;
private String topic;
MyMsgSubtype(String msg) { this(msg, null); }
MyMsgSubtype(String msg, String topic) {
this.msg = msg;
this.topic = topic;
}
@Override
public String getMessage() { return msg; }
@Override
public String getKey() { return key; }
@Override
public String getTopic() { return topic; }
@Override
public String toString() { return String.format("{[%s] topic=%s key=%s message=%s", more, topic, key, msg); }
}
private String[] getMqttTopics() {
String csvTopics = System.getProperty("com.ibm.streamsx.topology.test.messaging.mqtt.csvTopics", "testTopic1,testTopic2");
String[] topics = csvTopics.split(",");
return topics;
}
private String getServerURI() {
return System.getProperty("com.ibm.streamsx.topology.test.messaging.mqtt.serverURI", "tcp://localhost:1883");
}
private static class MsgId {
private int seq;
private String uniq;
private String prefix;
MsgId(String prefix) {
this.prefix = prefix;
}
String next() {
if (uniq==null) {
uniq = new SimpleDateFormat("HH:mm:ss.SSS").format(new Date());
}
return String.format("[%s.%d %s]", uniq, seq++, prefix);
}
String pattern() {
return String.format(".*\\[%s\\.\\d+ %s\\].*", uniq, prefix);
}
}
private static class MsgGenerator {
private MsgId id;
MsgGenerator(String testName) {
id = new MsgId(testName);
}
String create(String topic, String baseContent) {
return String.format("%s [topic=%s] %s", id.next(), topic, baseContent);
}
String pattern() {
return id.pattern();
}
}
private Map<String,Object> createConfig(String clientId) {
Map<String,Object> props = new HashMap<>();
props.put("serverURI", getServerURI());
props.put("clientID", clientId);
props.putAll(authInfo);
return props;
}
private static void initAuthInfo(String item) {
String val = System.getProperty(PROP_PREFIX + item);
if (val != null) {
authInfo.put(item, val);
if (item.toLowerCase().contains("password"))
val = "*****";
System.out.println("Using "+item+"="+val);
}
}
private void checkAssumes() {
// can we run embedded (all-java ops) if streamsx.messaging jar is in classpath?
assumeTrue(!isEmbedded());
}
@Test (expected = IllegalArgumentException.class)
public void testSubscribeNullTopic() throws Exception {
Topology top = new Topology("testSubscribeNullTopic");
String subClientId = newSubClientId(top.getName());
MqttStreams mqtt = new MqttStreams(top, createConfig(subClientId));
mqtt.subscribe(null); // throws IAE
}
@Test
public void testConfigParams() throws Exception {
checkAssumes();
setupDebug();
Topology top = new Topology("testConfigParams");
MsgGenerator mgen = new MsgGenerator(top.getName());
String clientId = newPubClientId(top.getName());
String topicVal = getMqttTopics()[0];
Supplier<String> topic = new Value<String>(topicVal);
List<Message> msgs = createMsgs(mgen, null/*topic*/);
// Test more config properties to be sure we don't blow up
Map<String,Object> config = createConfig(clientId);
config.put("defaultQOS", 1);
config.put("keepAliveInterval", 20);
config.put("commandTimeoutMsec", 30000L);
config.put("reconnectDelayMsec", 5000L);
config.put("receiveBufferSize", 10);
config.put("reconnectionBound", 20);
config.put("retain", false);
config.put("userID", System.getProperty("user.name"));
config.put("password", "foobar");
config.put("trustStore", "/tmp/no-such-trustStore");
config.put("trustStorePassword", "woohoo");
config.put("keyStore", "/tmp/no-such-keyStore");
config.put("keyStorePassword", "woohoo");
MqttStreams mqtt = new MqttStreams(top, config);
Map<String,Object> pcfg = mqtt.getConfig();
for (String s : config.keySet())
assertEquals("property "+s, config.get(s), pcfg.get(s));
TStream<Message> msgsToPublish = top.constants(msgs)
.modify(new InitialDelay<Message>(PUB_DELAY_MSEC));
TSink sink = mqtt.publish(msgsToPublish, topic);
TStream<Message> rcvdMsgs = mqtt.subscribe(topic);
// for validation...
rcvdMsgs.print();
// bundle construction fails for unrecognized or incorrectly typed SPL op params
File actBundle = (File) StreamsContextFactory
.getStreamsContext(StreamsContext.Type.BUNDLE)
.submit(top, getConfig())
.get(15, TimeUnit.SECONDS);
System.out.println("bundle " + actBundle.getAbsolutePath());
assertTrue(actBundle != null);
actBundle.delete();
assertTrue(sink != null);
}
@Test
public void testConfigParamsSubmissionParam() throws Exception {
checkAssumes();
setupDebug();
Topology top = new Topology("testConfigParamsSubmissionParam");
MsgGenerator mgen = new MsgGenerator(top.getName());
String subClientId = newSubClientId(top.getName());
String pubClientId = newPubClientId(top.getName());
String topicVal = getMqttTopics()[0];
Supplier<String> topic = new Value<String>(topicVal);
List<Message> msgs = createMsgs(mgen, null/*topic*/);
// Test more config properties to be sure we don't blow up
Supplier<String> userID = top.createSubmissionParameter("userID", String.class);
Supplier<String> password = top.createSubmissionParameter("password", String.class);
Supplier<String> trustStore = top.createSubmissionParameter("trustStore", String.class);
Supplier<String> trustStorePassword = top.createSubmissionParameter("trustStorePassword", String.class);
Supplier<String> keyStore = top.createSubmissionParameter("keyStore", String.class);
Supplier<String> keyStorePassword = top.createSubmissionParameter("keyStorePassword", String.class);
Map<String,Object> producerConfig = createConfig(pubClientId);
producerConfig.put("userID", userID);
producerConfig.put("password", password);
producerConfig.put("trustStore", trustStore);
producerConfig.put("trustStorePassword", trustStorePassword);
producerConfig.put("keyStore", keyStore);
producerConfig.put("keyStorePassword", keyStorePassword);
Map<String,Object> consumerConfig = createConfig(subClientId);
consumerConfig.put("userID", userID);
consumerConfig.put("password", password);
consumerConfig.put("trustStore", trustStore);
consumerConfig.put("trustStorePassword", trustStorePassword);
consumerConfig.put("keyStore", keyStore);
consumerConfig.put("keyStorePassword", keyStorePassword);
MqttStreams producer = new MqttStreams(top, producerConfig);
MqttStreams consumer = new MqttStreams(top, consumerConfig);
TStream<Message> msgsToPublish = top.constants(msgs)
.modify(new InitialDelay<Message>(PUB_DELAY_MSEC));
TSink sink = producer.publish(msgsToPublish, topic);
TStream<Message> rcvdMsgs = consumer.subscribe(topic);
// for validation...
rcvdMsgs.print();
// bundle construction fails for unrecognized or incorrectly typed SPL op params
File actBundle = (File) StreamsContextFactory
.getStreamsContext(StreamsContext.Type.BUNDLE)
.submit(top, getConfig())
.get(15, TimeUnit.SECONDS);
System.out.println("bundle " + actBundle.getAbsolutePath());
assertTrue(actBundle != null);
actBundle.delete();
assertTrue(sink != null);
}
@Test
public void testReusableApp() throws Exception {
checkAssumes();
setupDebug();
Topology top = new Topology("testReusableApp");
MsgGenerator mgen = new MsgGenerator(top.getName());
String subClientId = newSubClientId(top.getName());
String pubClientId = newPubClientId(top.getName());
String topic = getMqttTopics()[0];
List<Message> msgs = createMsgs(mgen, null/*topic*/);
// Test an app structured more as a "reusable asset" - i.e.,
// where the mqtt connection info (URI, authInfo) and
// topic are defined at submission time.
// define/create the app's submission parameters
ParameterHelper params = new ParameterHelper(top);
params.definitions().put("mqtt.serverURI", String.class);
params.definitions().put("mqtt.userID", System.getProperty("user.name"));
params.definitions().put("mqtt.password", String.class);
params.definitions().put("mqtt.pub.topic", String.class);
params.definitions().put("mqtt.sub.topic", String.class);
params.createAll();
// add the actual param values for our call to submit()
Map<String,Object> submitParams = new HashMap<>();
submitParams.put("mqtt.serverURI", "tcp://localhost:1883");
// submitParams.put("mqtt.userID", System.getProperty("user.name"));
submitParams.put("mqtt.password", "myMosquittoPw");
submitParams.put("mqtt.pub.topic", topic);
submitParams.put("mqtt.sub.topic", topic);
getConfig().put(ContextProperties.SUBMISSION_PARAMS, submitParams);
// Produce and consume the msgs
Map<String,Object> pconfig = createConfig(pubClientId);
addMqttParams(pconfig, false, params);
Map<String,Object> cconfig = createConfig(subClientId);
addMqttParams(cconfig, true, params);
MqttStreams producer = new MqttStreams(top, pconfig);
MqttStreams consumer = new MqttStreams(top, cconfig);
TStream<Message> msgsToPublish = top.constants(msgs)
.modify(new InitialDelay<Message>(PUB_DELAY_MSEC));
TSink sink = producer.publish(msgsToPublish, params.getString("mqtt.pub.topic"));
TStream<Message> rcvdMsgs = consumer.subscribe(params.getString("mqtt.sub.topic"));
// for validation...
rcvdMsgs.print();
rcvdMsgs = selectMsgs(rcvdMsgs, mgen.pattern()); // just our msgs
TStream<String> rcvdAsString = rcvdMsgs.transform(msgToJSONStringFunc());
msgs = modifyList(msgs, setTopic(topic));
List<String> expectedAsString = mapList(msgs, msgToJSONStringFunc());
if (testBuildOnly(top))
return;
completeAndValidate(subClientId, top, rcvdAsString, SEC_TIMEOUT, expectedAsString.toArray(new String[0]));
assertTrue(sink != null);
}
private static void addMqttParams(Map<String,Object> config,
boolean isConsumer, ParameterHelper params) {
for (Map.Entry<String, Supplier<?>> e : params.parameters().entrySet()) {
String name = e.getKey();
String prefix = "mqtt.";
if (!name.startsWith(prefix))
continue;
name = name.substring(prefix.length());
prefix = isConsumer ? "sub." : "pub.";
if (name.startsWith("sub.") || name.startsWith("pub.")) {
if (name.equals(prefix + "topic"))
continue; // not a config param
if (name.startsWith("sub.") && !prefix.equals("sub.")
|| name.startsWith("pub.") && !prefix.equals("pub."))
continue;
name = name.substring(prefix.length());
}
config.put(name, e.getValue());
}
}
@Test
public void testExplicitTopicProducerSingleConnector() throws Exception {
checkAssumes();
setupDebug();
Topology top = new Topology("testExplicitTopicProducerSingleConnector");
MsgGenerator mgen = new MsgGenerator(top.getName());
String clientId = newPubClientId(top.getName());
String topicVal = getMqttTopics()[0];
Supplier<String> topic = new Value<String>(topicVal);
List<Message> msgs = createMsgs(mgen, null/*topic*/);
List<String> expectedAsString = mapList(modifyList(msgs, setTopic(topicVal)),
msgToJSONStringFunc());
// Test producer that takes an explicit topic and implicit config qos
MqttStreams mqtt = new MqttStreams(top, createConfig(clientId));
TStream<Message> msgsToPublish = top.constants(msgs)
.modify(new InitialDelay<Message>(PUB_DELAY_MSEC));
TSink sink = mqtt.publish(msgsToPublish, topic);
TStream<Message> rcvdMsgs = mqtt.subscribe(topic);
// for validation...
rcvdMsgs.print();
rcvdMsgs = selectMsgs(rcvdMsgs, mgen.pattern()); // just our msgs
TStream<String> rcvdAsString = rcvdMsgs.transform(msgToJSONStringFunc());
if (testBuildOnly(top))
return;
completeAndValidate(clientId, top, rcvdAsString, SEC_TIMEOUT, expectedAsString.toArray(new String[0]));
assertTrue(sink != null);
}
@Test
public void testExplicitTopicProducer() throws Exception {
checkAssumes();
setupDebug();
Topology top = new Topology("testNewExplicitTopicProducer");
MsgGenerator mgen = new MsgGenerator(top.getName());
String subClientId = newSubClientId(top.getName());
String pubClientId = newPubClientId(top.getName());
String topicVal = getMqttTopics()[0];
Supplier<String> topic = new Value<String>(topicVal);
List<Message> msgs = createMsgs(mgen, null/*topic*/);
List<String> expectedAsString = mapList(modifyList(msgs, setTopic(topicVal)),
msgToJSONStringFunc());
// Test producer that takes an explicit topic and implicit config qos
MqttStreams producer = new MqttStreams(top, createConfig(pubClientId));
MqttStreams consumer = new MqttStreams(top, createConfig(subClientId));
TStream<Message> msgsToPublish = top.constants(msgs)
.modify(new InitialDelay<Message>(PUB_DELAY_MSEC));
TSink sink = producer.publish(msgsToPublish, topic);
TStream<Message> rcvdMsgs = consumer.subscribe(topic);
// for validation...
rcvdMsgs.print();
rcvdMsgs = selectMsgs(rcvdMsgs, mgen.pattern()); // just our msgs
TStream<String> rcvdAsString = rcvdMsgs.transform(msgToJSONStringFunc());
if (testBuildOnly(top))
return;
completeAndValidate(subClientId, top, rcvdAsString, SEC_TIMEOUT, expectedAsString.toArray(new String[0]));
assertTrue(sink != null);
}
@Test
public void testImplicitTopicProducer() throws Exception {
checkAssumes();
setupDebug();
Topology top = new Topology("testImplicitTopicProducer");
MsgGenerator mgen = new MsgGenerator(top.getName());
String subClientId = newSubClientId(top.getName());
String pubClientId = newPubClientId(top.getName());
String topic = getMqttTopics()[0];
List<Message> msgs = createMsgs(mgen, topic);
List<String> expectedAsString = mapList(msgs, msgToJSONStringFunc());
// Test producer that takes an arbitrary TStream<T> and implicit topic
MqttStreams producer = new MqttStreams(top, createConfig(pubClientId));
MqttStreams consumer = new MqttStreams(top, createConfig(subClientId));
TStream<Message> msgsToPublish = top.constants(msgs)
.modify(new InitialDelay<Message>(PUB_DELAY_MSEC));
TSink sink = producer.publish(msgsToPublish);
TStream<Message> rcvdMsgs = consumer.subscribe(new Value<String>(topic));
// for validation...
rcvdMsgs.print();
rcvdMsgs = selectMsgs(rcvdMsgs, mgen.pattern()); // just our msgs
TStream<String> rcvdAsString = rcvdMsgs.transform(msgToJSONStringFunc());
if (testBuildOnly(top))
return;
completeAndValidate(subClientId, top, rcvdAsString, SEC_TIMEOUT, expectedAsString.toArray(new String[0]));
assertTrue(sink != null);
}
@Test
public void testMsgImplProducer() throws Exception {
checkAssumes();
setupDebug();
Topology top = new Topology("testMsgImplProducer");
MsgGenerator mgen = new MsgGenerator(top.getName());
String subClientId = newSubClientId(top.getName());
String pubClientId = newPubClientId(top.getName());
String topicVal = getMqttTopics()[0];
Supplier<String> topic = new Value<String>(topicVal);
List<Message> msgs = createMsgs(mgen, null/*topic*/);
List<String> expectedAsString = mapList(modifyList(msgs, setTopic(topicVal)),
msgToJSONStringFunc());
// Test producer that takes TStream<SimpleMessage> and an explicit topic.
MqttStreams producer = new MqttStreams(top, createConfig(pubClientId));
MqttStreams consumer = new MqttStreams(top, createConfig(subClientId));
TStream<Message> msgsToPublish = top.constants(msgs)
.modify(new InitialDelay<Message>(PUB_DELAY_MSEC));
TSink sink = producer.publish(msgsToPublish, topic);
TStream<Message> rcvdMsgs = consumer.subscribe(topic);
// for validation...
rcvdMsgs.print();
rcvdMsgs = selectMsgs(rcvdMsgs, mgen.pattern()); // just our msgs
TStream<String> rcvdAsString = rcvdMsgs.transform(msgToJSONStringFunc());
if (testBuildOnly(top))
return;
completeAndValidate(subClientId, top, rcvdAsString, SEC_TIMEOUT, expectedAsString.toArray(new String[0]));
assertTrue(sink != null);
}
@Test
public void testSubtypeExplicitTopicProducer() throws Exception {
checkAssumes();
setupDebug();
Topology top = new Topology("testSubtypeExplicitTopicProducer");
MsgGenerator mgen = new MsgGenerator(top.getName());
String subClientId = newSubClientId(top.getName());
String pubClientId = newPubClientId(top.getName());
String topicVal = getMqttTopics()[0];
Supplier<String> topic = new Value<String>(topicVal);
List<MyMsgSubtype> msgs = new ArrayList<>();
for(Message m : createMsgs(mgen, null/*topic*/)) {
msgs.add(new MyMsgSubtype(m.getMessage()));
}
List<String> expectedAsString = mapList(msgs, subtypeMsgToJSONStringFunc(topicVal));
// Test producer that takes a TStream<MyMsgSubtype>
MqttStreams producer = new MqttStreams(top, createConfig(pubClientId));
MqttStreams consumer = new MqttStreams(top, createConfig(subClientId));
TStream<MyMsgSubtype> msgsToPublish = top.constants(msgs).asType(MyMsgSubtype.class)
.modify(new InitialDelay<MyMsgSubtype>(PUB_DELAY_MSEC));
TSink sink = producer.publish(msgsToPublish, topic);
TStream<Message> rcvdMsgs = consumer.subscribe(topic);
// for validation...
rcvdMsgs.print();
rcvdMsgs = selectMsgs(rcvdMsgs, mgen.pattern()); // just our msgs
TStream<String> rcvdAsString = rcvdMsgs.transform(msgToJSONStringFunc());
if (testBuildOnly(top))
return;
completeAndValidate(subClientId, top, rcvdAsString, SEC_TIMEOUT, expectedAsString.toArray(new String[0]));
assertTrue(sink != null);
}
@Test
public void testSubtypeImplicitTopicProducer() throws Exception {
checkAssumes();
setupDebug();
Topology top = new Topology("testSubtypeImplicitTopicProducer");
MsgGenerator mgen = new MsgGenerator(top.getName());
String subClientId = newSubClientId(top.getName());
String pubClientId = newPubClientId(top.getName());
String topic = getMqttTopics()[0];
List<MyMsgSubtype> msgs = new ArrayList<>();
for(Message m : createMsgs(mgen, topic)) {
msgs.add(new MyMsgSubtype(m.getMessage(), m.getTopic()));
}
List<String> expectedAsString = mapList(msgs, subtypeMsgToJSONStringFunc(null));
// Test producer that takes a TStream<MyMsgSubtype> implicit topic
MqttStreams producer = new MqttStreams(top, createConfig(pubClientId));
MqttStreams consumer = new MqttStreams(top, createConfig(subClientId));
TStream<MyMsgSubtype> msgsToPublish = top.constants(msgs).asType(MyMsgSubtype.class)
.modify(new InitialDelay<MyMsgSubtype>(PUB_DELAY_MSEC));
TSink sink = producer.publish(msgsToPublish);
TStream<Message> rcvdMsgs = consumer.subscribe(new Value<String>(topic));
// for validation...
rcvdMsgs.print();
rcvdMsgs = selectMsgs(rcvdMsgs, mgen.pattern()); // just our msgs
TStream<String> rcvdAsString = rcvdMsgs.transform(msgToJSONStringFunc());
if (testBuildOnly(top))
return;
completeAndValidate(subClientId, top, rcvdAsString, SEC_TIMEOUT, expectedAsString.toArray(new String[0]));
assertTrue(sink != null);
}
@Test
public void testMultiTopicProducer() throws Exception {
checkAssumes();
setupDebug();
Topology top = new Topology("testMultiTopicProducer");
MsgGenerator mgen = new MsgGenerator(top.getName());
String subClientId = newSubClientId(top.getName());
String pubClientId = newPubClientId(top.getName());
String[] topics = getMqttTopics();
String topic1 = topics[0];
String topic2 = topics[1];
List<Message> topic1Msgs = createMsgs(mgen, topic1);
List<Message> topic2Msgs = createMsgs(mgen, topic2);
List<Message> msgs = new ArrayList<>(topic1Msgs);
msgs.addAll(topic2Msgs);
List<String> expectedAsString = mapList(msgs, msgToJSONStringFunc());
// Test producer that publishes to multiple topics (implies implicit topic)
MqttStreams producer = new MqttStreams(top, createConfig(pubClientId));
MqttStreams consumer = new MqttStreams(top, createConfig(subClientId));
TStream<Message> msgsToPublish = top.constants(msgs)
.modify(new InitialDelay<Message>(PUB_DELAY_MSEC));
TSink sink = producer.publish(msgsToPublish);
TStream<Message> rcvdTopic1Msgs = consumer.subscribe(new Value<String>(topic1));
TStream<Message> rcvdTopic2Msgs = consumer.subscribe(new Value<String>(topic2));
// for validation...
TStream<Message> rcvdMsgs = rcvdTopic1Msgs.union(rcvdTopic2Msgs);
rcvdMsgs.print();
rcvdMsgs = selectMsgs(rcvdMsgs, mgen.pattern()); // just our msgs
TStream<String> rcvdAsString = rcvdMsgs.transform(msgToJSONStringFunc());
if (testBuildOnly(top))
return;
completeAndValidateUnordered(subClientId, top, rcvdAsString, SEC_TIMEOUT, expectedAsString.toArray(new String[0]));
assertTrue(sink != null);
}
@Test
public void testMultiPubSub() throws Exception {
checkAssumes();
setupDebug();
Topology top = new Topology("testMultiPubSub");
MsgGenerator mgen = new MsgGenerator(top.getName());
String subClientId = newSubClientId(top.getName());
String pubClientId = newPubClientId(top.getName());
String[] topics = getMqttTopics();
String topic1 = topics[0];
String topic2 = topics[1];
List<Message> topic1Msgs = createMsgs(mgen, topic1);
List<Message> topic2Msgs = createMsgs(mgen, topic2);
List<Message> msgs = new ArrayList<>(topic1Msgs);
msgs.addAll(topic2Msgs);
List<String> expectedAsString = mapList(msgs, msgToJSONStringFunc());
// Multi pub / sub on a single connector
MqttStreams producer = new MqttStreams(top, createConfig(pubClientId));
MqttStreams consumer = new MqttStreams(top, createConfig(subClientId));
TStream<Message> topic1MsgsToPublish = top.constants(topic1Msgs)
.modify(new InitialDelay<Message>(PUB_DELAY_MSEC));
TStream<Message> topic2MsgsToPublish = top.constants(topic2Msgs)
.modify(new InitialDelay<Message>(PUB_DELAY_MSEC));
TSink sink1 = producer.publish(topic1MsgsToPublish, new Value<String>(topic1));
TSink sink2 = producer.publish(topic2MsgsToPublish, new Value<String>(topic2));
TStream<Message> rcvdTopic1Msgs = consumer.subscribe(new Value<String>(topic1));
TStream<Message> rcvdTopic2Msgs = consumer.subscribe(new Value<String>(topic2));
// for validation...
TStream<Message> rcvdMsgs = rcvdTopic1Msgs.union(rcvdTopic2Msgs);
rcvdMsgs.print();
rcvdMsgs = selectMsgs(rcvdMsgs, mgen.pattern()); // just our msgs
TStream<String> rcvdAsString = rcvdMsgs.transform(msgToJSONStringFunc());
if (testBuildOnly(top))
return;
completeAndValidateUnordered(subClientId, top, rcvdAsString, SEC_TIMEOUT, expectedAsString.toArray(new String[0]));
assertTrue(sink1 != null);
assertTrue(sink2 != null);
}
// would be nice if Tester provided this too
private void completeAndValidateUnordered(String msg, Topology topology,
TStream<String> stream, int secTimeout, String... expected)
throws Exception {
Tester tester = topology.getTester();
Condition<Long> sCount = tester.tupleCount(stream, expected.length);
Condition<List<String>> sContents = tester.stringContentsUnordered(stream, expected);
complete(tester, sCount, secTimeout, TimeUnit.SECONDS);
assertTrue(msg + " contents:" + sContents, sContents.valid());
assertTrue("valid:" + sCount, sCount.valid());
}
private void completeAndValidate(String msg, Topology topology,
TStream<String> stream, int secTimeout, String... expected)
throws Exception {
Tester tester = topology.getTester();
Condition<Long> sCount = tester.tupleCount(stream, expected.length);
Condition<List<String>> sContents = tester.stringContents(stream, expected);
complete(tester, sCount, secTimeout, TimeUnit.SECONDS);
assertTrue(msg + " contents:" + sContents, sContents.valid());
assertTrue("valid:" + sCount, sCount.valid());
}
private List<Message> createMsgs(MsgGenerator mgen, String topic) {
List<Message> msgs = new ArrayList<>();
msgs.add(new SimpleMessage(mgen.create(topic, "Hello"), null, topic));
msgs.add(new SimpleMessage(mgen.create(topic, "Are you there?"), null, topic));
return msgs;
}
private static TStream<Message> selectMsgs(TStream<Message> stream,
final String pattern) {
return stream.filter(
new Predicate<Message>() {
private static final long serialVersionUID = 1L;
@Override
public boolean test(Message tuple) {
return tuple.getMessage().matches(pattern)
|| (tuple.getKey()!=null
&& tuple.getKey().matches(pattern));
}
});
}
/**
* Modify List<T> => List<T> via func
*/
private <T> List<T> modifyList(List<T> list, UnaryOperator<T> func) {
List<T> l = new ArrayList<>();
for (T o : list) {
l.add(func.apply(o));
}
return l;
}
/**
* Transform List<T> => List<R> via func
*/
private <T,R> List<R> mapList(List<T> list, Function<T, R> func) {
List<R> l = new ArrayList<>();
for (T o : list) {
l.add(func.apply(o));
}
return l;
}
private static UnaryOperator<Message> setTopic(final String topic) {
return new UnaryOperator<Message>() {
private static final long serialVersionUID = 1L;
@Override
public Message apply(Message v) {
return new SimpleMessage(v.getMessage(), v.getKey(), topic);
}
};
}
/**
* Function for Message => toJSON().toString()
*/
private static Function<Message,String> msgToJSONStringFunc() {
return new Function<Message,String>() {
private static final long serialVersionUID = 1L;
@Override
public String apply(Message v) {
return toJson(v);
}
};
}
/**
* Function for MyMsgSubtype => toJSON().toString()
*/
private static Function<MyMsgSubtype,String> subtypeMsgToJSONStringFunc(final String topic) {
return new Function<MyMsgSubtype,String>() {
private static final long serialVersionUID = 1L;
@Override
public String apply(MyMsgSubtype v) {
if (topic!=null)
v = new MyMsgSubtype(v.msg, topic);
return toJson(v);
}
};
}
private static String toJson(Message m) {
JSONObject jo = new JSONObject();
jo.put("key", m.getKey());
jo.put("message", m.getMessage());
jo.put("topic", m.getTopic());
return jo.toString();
}
}