001/* 002# Licensed Materials - Property of IBM 003# Copyright IBM Corp. 2015 004 */ 005package mqtt; 006 007import java.text.SimpleDateFormat; 008import java.util.Date; 009import java.util.HashMap; 010import java.util.Map; 011import java.util.concurrent.Future; 012 013import com.ibm.streamsx.topology.TStream; 014import com.ibm.streamsx.topology.Topology; 015import com.ibm.streamsx.topology.context.ContextProperties; 016import com.ibm.streamsx.topology.context.StreamsContextFactory; 017import com.ibm.streamsx.topology.function.Function; 018import com.ibm.streamsx.topology.function.Supplier; 019import com.ibm.streamsx.topology.function.UnaryOperator; 020import com.ibm.streamsx.topology.logic.Value; 021import com.ibm.streamsx.topology.messaging.mqtt.MqttStreams; 022import com.ibm.streamsx.topology.tuple.Message; 023import com.ibm.streamsx.topology.tuple.SimpleMessage; 024 025 026/** 027 * Demonstrate integrating with the MQTT messaging system 028 * <a href="http://mqtt.org">http://mqtt.org</a>. 029 * <p> 030 * {@link com.ibm.streamsx.topology.messaging.mqtt.MqttStreams MqttStreams} is 031 * a connector used to create a bridge between topology streams 032 * and an MQTT broker. 033 * <p> 034 * The sample publishes some messages to a MQTT topic. 035 * It also subscribes to the topic and reports the messages received. 036 * The messages received may include messages from prior runs of the sample. 037 * <p> 038 * By default, the sample requires a running MQTT broker with the following 039 * characteristics: 040 * <ul> 041 * <li>the broker's connection is {@code tcp://localhost:1883}</li> 042 * <li>the broker is configured for no authentication</li> 043 * </ul> 044 * <p> 045 * Required IBM Streams environment variables: 046 * <ul> 047 * <li>STREAMS_INSTALL - the Streams installation directory</li> 048 * <li>STREAMS_DOMAIN_ID - the Streams domain to use for context {@code DISTRIBUTED} 049 * <li>STREAMS_INSTANCE_ID - the Streams instance to use for context {@code DISTRIBUTED} 050 * </ul> 051 * <p> 052 * See the MQTT link above for information about setting up a MQTT broker. 053 * <p> 054 * This may be executed from the {@code samples/java/functional} directory as: 055 * <UL> 056 * <LI>{@code ant run.mqtt.distributed} - Using Apache Ant, this will run in distributed mode.</li> 057 * <LI>{@code ant run.mqtt} - Using Apache Ant, this will run in standalone mode.</li> 058 * <LI> 059 * {@code java -cp functionalsamples.jar:../../../com.ibm.streamsx.topology/lib/com.ibm.streamsx.topology.jar:$STREAMS_INSTALL/lib/com.ibm.streams.operator.samples.jar 060 * mqtt.MqttSample CONTEXT_TYPE 061 * [serverURI=<value>] 062 * [userID=<value>] [password=<value>] 063 * [trustStore=<value>] [trustStorePassword=<value>] 064 * [keyStore=<value>] [keyStorePassword=<value>] 065 * } - Run directly from the command line. 066 * </LI> 067 * Specify absolute pathnames if using the {@code trustStore} 068 * or {@code keyStore} arguments. 069 * <BR> 070 * <i>CONTEXT_TYPE</i> is one of: 071 * <UL> 072 * <LI>{@code DISTRIBUTED} - Run as an IBM Streams distributed application.</LI> 073 * <LI>{@code STANDALONE} - Run as an IBM Streams standalone application.</LI> 074 * <LI>{@code BUNDLE} - Create an IBM Streams application bundle.</LI> 075 * <LI>{@code TOOLKIT} - Create an IBM Streams application toolkit.</LI> 076 * </UL> 077 * <LI> 078 * An application execution within your IDE once you set the class path to include the correct jars.</LI> 079 * </UL> 080 */ 081public class MqttSample { 082 private static String SERVER_URI = "tcp://localhost:1883"; 083 084 private static final String TOPIC = "mqttSampleTopic"; 085 086 private static final int PUB_DELAY_MSEC = 5*1000; 087 private boolean captureArtifacts = false; 088 private boolean setAppTracingLevel = false; 089 private java.util.logging.Level appTracingLevel = java.util.logging.Level.FINE; 090 private static final Map<String,String> authInfo = new HashMap<>(); 091 092 public static void main(String[] args) throws Exception { 093 String contextType = "DISTRIBUTED"; 094 if (args.length > 0) 095 contextType = args[0]; 096 processArgs(args); 097 System.out.println("\nUsing MQTT broker at " + SERVER_URI +"\n"); 098 099 MqttSample app = new MqttSample(); 100 app.publishSubscribe(contextType); 101 } 102 103 /** 104 * Publish some messages to a topic; subscribe to the topic and report 105 * received messages. 106 * @param contextType string value of a {@code StreamsContext.Type} 107 * @throws Exception 108 */ 109 public void publishSubscribe(String contextType) throws Exception { 110 111 Map<String,Object> contextConfig = new HashMap<>(); 112 initContextConfig(contextConfig); 113 114 Topology top = new Topology("mqttSample"); 115 116 // A compile time MQTT topic value. 117 Supplier<String> topic = new Value<String>(TOPIC); 118 119 // Create the MQTT connector 120 MqttStreams mqtt = new MqttStreams(top, createMqttConfig()); 121 122 // Create a stream of messages and for the sample, give the 123 // consumer a change to become ready 124 TStream<Message> msgs = makeStreamToPublish(top) 125 .modify(initialDelayFunc(PUB_DELAY_MSEC)); 126 127 // Publish the message stream to the topic 128 mqtt.publish(msgs, topic); 129 130 // Subscribe to the topic and report received messages 131 TStream<Message> rcvdMsgs = mqtt.subscribe(topic); 132 rcvdMsgs.print(); 133 134 // Submit the topology, to send and receive the messages. 135 Future<?> future = StreamsContextFactory.getStreamsContext(contextType) 136 .submit(top, contextConfig); 137 138 if (contextType.contains("DISTRIBUTED")) { 139 System.out.println("\nSee the job's PE console logs for the topology output.\n" 140 + "Use Streams Studio or streamtool. e.g.,\n" 141 + " # identify the job's \"Print\" PE\n" 142 + " streamtool lspes --jobs " + future.get() + "\n" 143 + " # print the PE's console log\n" 144 + " streamtool viewlog --print --console --pe <the-peid>" 145 + "\n"); 146 System.out.println("Cancel the job using Streams Studio or streamtool. e.g.,\n" 147 + " streamtool canceljob " + future.get() 148 + "\n"); 149 } 150 else if (contextType.contains("STANDALONE")) { 151 Thread.sleep(15000); 152 future.cancel(true); 153 } 154 } 155 156 private Map<String,Object> createMqttConfig() { 157 Map<String,Object> props = new HashMap<>(); 158 props.put("serverURI", SERVER_URI); 159 props.putAll(authInfo); 160 return props; 161 } 162 163 private static void processArgs(String[] args) { 164 String item = "serverURI"; 165 String value = getArg(item, args); 166 if (value != null) { 167 SERVER_URI = value; 168 System.out.println("Using "+item+"="+value); 169 } 170 initAuthInfo("userID", args); 171 initAuthInfo("password", args); 172 if (authInfo.containsKey("password") && !authInfo.containsKey("userID")) 173 authInfo.put("userID", System.getProperty("user.name")); 174 initAuthInfo("trustStore", args); 175 initAuthInfo("trustStorePassword", args); 176 initAuthInfo("keyStore", args); 177 initAuthInfo("keyStorePassword", args); 178 } 179 180 private static void initAuthInfo(String item, String[] args) { 181 String value = getArg(item, args); 182 if (value != null) { 183 authInfo.put(item, value); 184 if (item.toLowerCase().contains("password")) 185 value = "*****"; 186 System.out.println("Using "+item+"="+value); 187 } 188 } 189 190 private static String getArg(String item, String[] args) { 191 for (String arg : args) { 192 String[] parts = arg.split("="); 193 if (item.equals(parts[0])) 194 return parts[1]; 195 } 196 return null; 197 } 198 199 @SuppressWarnings("serial") 200 private static TStream<Message> makeStreamToPublish(Topology top) { 201 return top.strings("Hello", "Are you there?", 202 "3 of 5", "4 of 5", "5 of 5" 203 ).transform(new Function<String,Message>() { 204 private String timestamp; 205 @Override 206 public Message apply(String v) { 207 if (timestamp == null) 208 timestamp = new SimpleDateFormat("HH:mm:ss.SSS ").format(new Date()); 209 return new SimpleMessage(timestamp + v); 210 } 211 }); 212 } 213 214 private void initContextConfig(Map<String,Object> contextConfig) { 215 if (captureArtifacts) 216 contextConfig.put(ContextProperties.KEEP_ARTIFACTS, true); 217 if (setAppTracingLevel) 218 contextConfig.put(ContextProperties.TRACING_LEVEL, appTracingLevel); 219 } 220 221 @SuppressWarnings("serial") 222 private static UnaryOperator<Message> initialDelayFunc(final int delayMsec) { 223 return new UnaryOperator<Message>() { 224 private int initialDelayMsec = delayMsec; 225 226 @Override 227 public Message apply(Message v) { 228 if (initialDelayMsec != -1) { 229 try { 230 Thread.sleep(initialDelayMsec); 231 } catch (InterruptedException e) { 232 // done delaying 233 } 234 initialDelayMsec = -1; 235 } 236 return v; 237 } 238 }; 239 } 240}