Source code

    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}