/* * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.cloud.pubsub.proxy.moquette; import static com.google.common.base.Preconditions.checkNotNull; import com.google.cloud.pubsub.proxy.ProxyContext; import com.google.cloud.pubsub.proxy.message.PublishMessage; import com.google.common.util.concurrent.AbstractFuture; import org.eclipse.moquette.proto.messages.AbstractMessage.QOSType; import org.eclipse.paho.client.mqttv3.IMqttActionListener; import org.eclipse.paho.client.mqttv3.IMqttToken; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttPersistenceException; import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence; import java.io.IOException; import java.util.concurrent.Future; /** * A class for sending MQTT messages to the Moquette broker running on localhost. */ final class MoquetteProxyContext implements ProxyContext { private final MqttAsyncClient client; private final MqttDefaultFilePersistence dataStore; // TODO move these values to a Constants file. private static final String MQTT_BROKER_HOST = "127.0.0.1"; private static final String MQTT_BROKER_PORT = "1883"; private static final String MQTT_PROTOCOL = "tcp"; private static final String MQTT_CLIENT_NAME = "localHost-Client"; /** * Initializes an object that can be used for sending messages to the broker * which is running on localhost. */ public MoquetteProxyContext() { try { this.dataStore = new MqttDefaultFilePersistence(); this.client = new MqttAsyncClient(getFullMqttBrokerUrl(), MQTT_CLIENT_NAME, dataStore); } catch (MqttException e) { // The exception is thrown when there is an unrecognized MQTT Message in the persistant // storage location. Messages are removed from persistant storage once the broker // sends the message to subscribers (does not wait for confirmation) throw new IllegalStateException("Unrecognized message in the persistent data store location." + " Consider clearing the default persistent storage location."); } } /** * Initializes an object that can be used for sending messages to the broker * which is running on localhost. * * @param persistenceDir the location of the persistent storage used by the MQTT client library. */ public MoquetteProxyContext(String persistenceDir) { try { dataStore = new MqttDefaultFilePersistence(checkNotNull(persistenceDir)); client = new MqttAsyncClient(getFullMqttBrokerUrl(), MQTT_CLIENT_NAME, dataStore); } catch (MqttException e) { // The exception is thrown when there is an unrecognized MQTT Message in the persistant // storage location. Messages are removed from persistant storage once the broker // sends the message to subscribers (does not wait for confirmation) throw new IllegalStateException("Unrecognized message in the persistent data store location." + " Consider clearing the default persistent storage location."); } } /** * Connects to the broker running on localhost, so that we can publish messages to the broker. * * @throws IOException when you're unable to connect to the server. */ public void open() throws IOException { // TODO properly handle the exception try { client.connect(); } catch (MqttException e) { // This exception is thrown if a client is already connected, connection in progress, // disconnecting, or client has been closed. throw new IOException("The client is in an inappropriate state for connecting." + e.getMessage()); } } /** * Publishes a message to the broker using the Paho client library. * You must call open() before using this method. * * @throws MqttException when the persistent storage location is already in use, * or the client is not in a proper state for publishing a message. */ @Override public Future<Boolean> publishToSubscribers(PublishMessage msg) throws IOException { // TODO Handle messages that should be retained checkNotNull(msg); try { // We will support QOS Type 1. // We must implement a retain datastore, so we don't need to set retain flag for these msgs MoquetteMqttListener publishFuture = new MoquetteMqttListener(); client.publish(msg.getMqttTopic(), msg.getMqttPaylaod(), QOSType.LEAST_ONE.ordinal(), false, null, publishFuture); return publishFuture; } catch (MqttPersistenceException e) { // persistent storage location in use throw new IOException("The MQTT Persistent Storage Location is already in use. MQTT Reason" + " Code: " + e.getReasonCode() + "\n" + e.getMessage()); } catch (MqttException e) { // inappropriate state for publishing throw new IOException("The client is in an inappropriate state for connecting." + e.getMessage()); } } /** * Terminates any resources that were used to publish messages to the broker. * * @throws IOException when the client is in an inappropriate state to disconnect. */ public void close() throws IOException { try { dataStore.close(); client.disconnect(); client.close(); } catch (MqttException e) { // This exception is thrown if a client is not in an appropriate state for disconnecting. throw new IOException("The client is in an innapropriate state for disconnecting." + e.getMessage()); } } private String getFullMqttBrokerUrl() { return MQTT_PROTOCOL + "://" + MQTT_BROKER_HOST + ":" + MQTT_BROKER_PORT; } /** * Callback class used for MQTT message operations. */ private final class MoquetteMqttListener extends AbstractFuture<Boolean> implements IMqttActionListener { @Override public void onSuccess(IMqttToken asyncActionToken) { set(true); } @Override public void onFailure(IMqttToken asyncActionToken, Throwable exception) { set(false); } } }