diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..17c00679
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,59 @@
+### /.gitignore-boilerplates/Global/Eclipse.gitignore
+*.pydevproject
+.metadata
+bin/
+tmp/**
+tmp/**/*
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.loadpath
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# CDT-specific
+.cproject
+
+# PDT-specific
+.buildpath
+
+### .gitignore-boilerplates/Global/OSX.gitignore
+.DS_Store
+.AppleDouble
+.LSOverride
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear on external disk
+.Spotlight-V100
+.Trashes
+
+### .gitignore-boilerplates/Java.gitignore
+*.class
+
+# Package Files #
+*.jar
+*.war
+*.ear
+
+# Eclipse
+.classpath
+.project
+.settings/
+
+# Intellij
+.idea/
+*.iml
+*.iws
+
+# Maven
+log/
+target/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..dd02491a
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,87 @@
+Contributing to Paho
+====================
+
+Thanks for your interest in this project.
+
+Project description:
+--------------------
+
+The Paho project has been created to provide reliable open-source implementations of open and standard messaging protocols aimed at new, existing, and emerging applications for Machine-to-Machine (M2M) and Internet of Things (IoT).
+Paho reflects the inherent physical and cost constraints of device connectivity. Its objectives include effective levels of decoupling between devices and applications, designed to keep markets open and encourage the rapid growth of scalable Web and Enterprise middleware and applications.
+
+- [Project web site](https://www.eclipse.org/paho)
+- [Project information](https://projects.eclipse.org/projects/iot.paho)
+
+Source
+------
+
+The Paho Java client and Android service are stored in a git repository. The URLs to access it are:
+
+ssh://@git.eclipse.org:29418/paho/org.eclipse.paho.mqtt.java
+https://@git.eclipse.org/r/paho/org.eclipse.paho.mqtt.java
+
+A [web browsable repository](http://git.eclipse.org/c/paho/org.eclipse.paho.mqtt.java.git) is available.
+
+Contributing a patch
+--------------------
+
+The Paho repositories are accessed through Gerrit, the code review
+project, which makes it possible for anybody to clone the repository, make
+changes and push them back for review and eventual acceptance into the project.
+
+To do this, you must follow a few steps. The first of these are described at
+
+- [Contributing via git](https://wiki.eclipse.org/Development_Resources/Contributing_via_Git)
+
+* Sign the Eclipse CLA
+* Use a valid commit record, including a signed-off-by entry.
+
+There are further details at
+
+- [Handling Git Contributions](https://wiki.eclipse.org/Development_Resources/Handling_Git_Contributions)
+
+Once the patch is pushed back to Gerrit, the project committers will be
+informed and they will undertake a review of the code. The patch may need
+modifying for some reason. In order to make amending commits more
+straightforward, the steps at
+https://git.eclipse.org/r/Documentation/cmd-hook-commit-msg.html should be
+followed. This automatically inserts a "Change-Id" entry to your commit message
+which allows you to amend commits and have Gerrit track them as the same
+change.
+
+What happens next depends on the content of the patch. If it is 100% authored
+by the contributor and is less than 1000 lines (and meets the needs of the
+project), then it can be committed to the main repository. If not, more steps
+are required. These are detailed in the
+[legal process poster](http://www.eclipse.org/legal/EclipseLegalProcessPoster.pdf).
+
+
+Developer resources:
+--------------------
+
+Information regarding source code management, builds, coding standards, and more.
+
+- [https://projects.eclipse.org/projects/iot.paho/developer](https://projects.eclipse.org/projects/iot.paho/developer)
+
+Contributor License Agreement:
+------------------------------
+
+Before your contribution can be accepted by the project, you need to create and electronically sign the Eclipse Foundation [Contributor License Agreement (CLA)](http://www.eclipse.org/legal/CLA.php).
+
+Contact:
+--------
+
+Contact the project developers via the project's development
+[mailing list](https://dev.eclipse.org/mailman/listinfo/paho-dev).
+
+Search for bugs:
+----------------
+
+This project uses [Bugzilla](https://bugs.eclipse.org/bugs/buglist.cgi?product=Paho) to track ongoing development and issues.
+
+Create a new bug:
+-----------------
+
+Be sure to search for existing bugs before you create another one. Remember that contributions are always welcome!
+
+- [Create new Paho bug](https://bugs.eclipse.org/bugs/enter_bug.cgi?product=Paho)
diff --git a/HOW_TO_BUILD.TXT b/HOW_TO_BUILD.TXT
new file mode 100644
index 00000000..8403ceeb
--- /dev/null
+++ b/HOW_TO_BUILD.TXT
@@ -0,0 +1,3 @@
+export JAVA_HOME=`/usr/libexec/java_home -v '1.7*'`
+~/app/maven/bin/mvn clean compile package -DskipTests
+~/app/maven/bin/mvn deploy -DskipTests
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..74737d28
--- /dev/null
+++ b/README.md
@@ -0,0 +1,30 @@
+Paho Java client for MQTT
+
+This is to be completed... (Ian Craggs)
+
+
+Updating to a new version number
+-------------------------------
+
+Ok. There are some Maven commands to update releases, but so far I've not been able to determine what those should be.
+
+In the develop branch, we want the releases to be the vNext-SNAPSHOT
+
+But in the master branch, we want
+
+
+Maven command to update versions:
+
+mvn versions:set -DnewVersion=1.0.2-SNAPSHOT
+
+this will work for pom.xml files. However we have OSGi manifests as well. Linux commands to update versions:
+
+find | grep "MANIFEST\.MF" | xargs sed -i "s/1\.0\.2/1\.0\.3\.qualifier/g"
+find | grep "feature.xml" | xargs sed -i "s/1\.0\.2/1\.0\.3\.qualifier/g"
+find | grep "build.xml" | xargs sed -i "s/1\.0\.2/1\.0\.3\.qualifier/g"
+find | grep "category.xml" | xargs sed -i "s/1\.0\.2/1\.0\.3\.qualifier/g"
+find | grep "ui.app.product" | xargs sed -i "s/1\.0\.2/1\.0\.3\.qualifier/g"
+
+Example Linux command to find all files with instances of a version number:
+
+find | xargs grep -s "1\.0\.2"
diff --git a/about.html b/about.html
new file mode 100644
index 00000000..6555a44e
--- /dev/null
+++ b/about.html
@@ -0,0 +1,28 @@
+
+
+
+About
+
+
+
About This Content
+
+
December 9, 2013
+
License
+
+
The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise
+indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 ("EPL") and Eclipse Distribution License Version 1.0 ("EDL").
+A copy of the EPL is available at
+http://www.eclipse.org/legal/epl-v10.html
+and a copy of the EDL is available at
+http://www.eclipse.org/org/documents/edl-v10.php.
+For purposes of the EPL, "Program" will mean the Content.
+
+
If you did not receive this Content directly from the Eclipse Foundation, the Content is
+being redistributed by another party ("Redistributor") 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 http://www.eclipse.org.
+
+
diff --git a/edl-v10 b/edl-v10
new file mode 100644
index 00000000..cf989f14
--- /dev/null
+++ b/edl-v10
@@ -0,0 +1,15 @@
+
+Eclipse Distribution License - v 1.0
+
+Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors.
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ Neither the name of the Eclipse Foundation, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/epl-v10 b/epl-v10
new file mode 100644
index 00000000..79e486c3
--- /dev/null
+++ b/epl-v10
@@ -0,0 +1,70 @@
+Eclipse Public License - v 1.0
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and
+b) in the case of each subsequent Contributor:
+i) changes to the Program, and
+ii) additions to the Program;
+where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program.
+"Contributor" means any person or entity that distributes the Program.
+
+"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program.
+
+"Program" means the Contributions distributed in accordance with this Agreement.
+
+"Recipient" means anyone who receives the Program under this Agreement, including all Contributors.
+
+2. GRANT OF RIGHTS
+
+a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form.
+b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder.
+c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program.
+d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement.
+3. REQUIREMENTS
+
+A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that:
+
+a) it complies with the terms and conditions of this Agreement; and
+b) its license agreement:
+i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose;
+ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits;
+iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and
+iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange.
+When the Program is made available in source code form:
+
+a) it must be made available under this Agreement; and
+b) a copy of this Agreement must be included with each copy of the Program.
+Contributors may not remove or alter any copyright notices contained within the Program.
+
+Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense.
+
+For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive.
+
+Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation.
diff --git a/notice.html b/notice.html
new file mode 100644
index 00000000..f19c483b
--- /dev/null
+++ b/notice.html
@@ -0,0 +1,108 @@
+
+
+
+
+
+Eclipse Foundation Software User Agreement
+
+
+
+
Eclipse Foundation Software User Agreement
+
February 1, 2011
+
+
Usage Of Content
+
+
THE ECLIPSE FOUNDATION MAKES AVAILABLE SOFTWARE, DOCUMENTATION, INFORMATION AND/OR OTHER MATERIALS FOR OPEN SOURCE PROJECTS
+ (COLLECTIVELY "CONTENT"). USE OF THE CONTENT IS GOVERNED BY THE TERMS AND CONDITIONS OF THIS AGREEMENT AND/OR THE TERMS AND
+ CONDITIONS OF LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW. BY USING THE CONTENT, YOU AGREE THAT YOUR USE
+ OF THE CONTENT IS GOVERNED BY THIS AGREEMENT AND/OR THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR
+ NOTICES INDICATED OR REFERENCED BELOW. IF YOU DO NOT AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT AND THE TERMS AND
+ CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW, THEN YOU MAY NOT USE THE CONTENT.
+
+
Applicable Licenses
+
+
Unless otherwise indicated, all Content made available by the Eclipse Foundation is provided to you under the terms and conditions of the Eclipse Public License Version 1.0
+ ("EPL"). A copy of the EPL is provided with this Content and is also available at http://www.eclipse.org/legal/epl-v10.html.
+ For purposes of the EPL, "Program" will mean the Content.
+
+
Content includes, but is not limited to, source code, object code, documentation and other files maintained in the Eclipse Foundation source code
+ repository ("Repository") in software modules ("Modules") and made available as downloadable archives ("Downloads").
+
+
+
Content may be structured and packaged into modules to facilitate delivering, extending, and upgrading the Content. Typical modules may include plug-ins ("Plug-ins"), plug-in fragments ("Fragments"), and features ("Features").
+
Each Plug-in or Fragment may be packaged as a sub-directory or JAR (Java™ ARchive) in a directory named "plugins".
+
A Feature is a bundle of one or more Plug-ins and/or Fragments and associated material. Each Feature may be packaged as a sub-directory in a directory named "features". Within a Feature, files named "feature.xml" may contain a list of the names and version numbers of the Plug-ins
+ and/or Fragments associated with that Feature.
+
Features may also include other Features ("Included Features"). Within a Feature, files named "feature.xml" may contain a list of the names and version numbers of Included Features.
+
+
+
The terms and conditions governing Plug-ins and Fragments should be contained in files named "about.html" ("Abouts"). The terms and conditions governing Features and
+Included Features should be contained in files named "license.html" ("Feature Licenses"). Abouts and Feature Licenses may be located in any directory of a Download or Module
+including, but not limited to the following locations:
+
+
+
The top-level (root) directory
+
Plug-in and Fragment directories
+
Inside Plug-ins and Fragments packaged as JARs
+
Sub-directories of the directory named "src" of certain Plug-ins
+
Feature directories
+
+
+
Note: if a Feature made available by the Eclipse Foundation is installed using the Provisioning Technology (as defined below), you must agree to a license ("Feature Update License") during the
+installation process. If the Feature contains Included Features, the Feature Update License should either provide you with the terms and conditions governing the Included Features or
+inform you where you can locate them. Feature Update Licenses may be found in the "license" property of files named "feature.properties" found within a Feature.
+Such Abouts, Feature Licenses, and Feature Update Licenses contain the terms and conditions (or references to such terms and conditions) that govern your use of the associated Content in
+that directory.
+
+
THE ABOUTS, FEATURE LICENSES, AND FEATURE UPDATE LICENSES MAY REFER TO THE EPL OR OTHER LICENSE AGREEMENTS, NOTICES OR TERMS AND CONDITIONS. SOME OF THESE
+OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT ARE NOT LIMITED TO):
IT IS YOUR OBLIGATION TO READ AND ACCEPT ALL SUCH TERMS AND CONDITIONS PRIOR TO USE OF THE CONTENT. If no About, Feature License, or Feature Update License is provided, please
+contact the Eclipse Foundation to determine what terms and conditions govern that particular Content.
+
+
+
Use of Provisioning Technology
+
+
The Eclipse Foundation makes available provisioning software, examples of which include, but are not limited to, p2 and the Eclipse
+ Update Manager ("Provisioning Technology") for the purpose of allowing users to install software, documentation, information and/or
+ other materials (collectively "Installable Software"). This capability is provided with the intent of allowing such users to
+ install, extend and update Eclipse-based products. Information about packaging Installable Software is available at http://eclipse.org/equinox/p2/repository_packaging.html
+ ("Specification").
+
+
You may use Provisioning Technology to allow other parties to install Installable Software. You shall be responsible for enabling the
+ applicable license agreements relating to the Installable Software to be presented to, and accepted by, the users of the Provisioning Technology
+ in accordance with the Specification. By using Provisioning Technology in such a manner and making it available in accordance with the
+ Specification, you further acknowledge your agreement to, and the acquisition of all necessary rights to permit the following:
+
+
+
A series of actions may occur ("Provisioning Process") in which a user may execute the Provisioning Technology
+ on a machine ("Target Machine") with the intent of installing, extending or updating the functionality of an Eclipse-based
+ product.
+
During the Provisioning Process, the Provisioning Technology may cause third party Installable Software or a portion thereof to be
+ accessed and copied to the Target Machine.
+
Pursuant to the Specification, you will provide to the user the terms and conditions that govern the use of the Installable
+ Software ("Installable Software Agreement") and such Installable Software Agreement shall be accessed from the Target
+ Machine in accordance with the Specification. Such Installable Software Agreement must inform the user of the terms and conditions that govern
+ the Installable Software and must solicit acceptance by the end user in the manner prescribed in such Installable Software Agreement. Upon such
+ indication of agreement by the user, the provisioning Technology will complete installation of the Installable Software.
+
+
+
Cryptography
+
+
Content may contain encryption software. The country in which you are currently may have restrictions on the import, possession, and use, and/or re-export to
+ another country, of encryption software. BEFORE using any encryption software, please check the country's laws, regulations and policies concerning the import,
+ possession, or use, and re-export of encryption software, to see if this is permitted.
+
+
Java and all Java-based trademarks are trademarks of Oracle Corporation in the United States, other countries, or both.
+
+
diff --git a/org.eclipse.paho.android.service/.gitignore b/org.eclipse.paho.android.service/.gitignore
new file mode 100644
index 00000000..a8b4a719
--- /dev/null
+++ b/org.eclipse.paho.android.service/.gitignore
@@ -0,0 +1,56 @@
+# Copyright: Benjamin Weiss (keyboardsurfer) https://github.com/keyboardsurfer
+# Under CC-BY-SA V3.0 (https://creativecommons.org/licenses/by-sa/3.0/legalcode)
+
+# built application files
+*.apk
+*.ap_
+#*.jar
+
+# lint files
+lint.xml
+
+# files for the dex VM
+*.dex
+
+# Java class files
+*.class
+
+# generated files
+bin/
+gen/
+classes/
+gen-external-apklibs/
+
+# maven output folder
+target
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Eclipse project files
+.classpath
+.project
+.metadata
+.settings
+
+# IntelliJ files
+#.idea
+*.iml
+
+# OSX files
+.DS_Store
+
+# Windows files
+Thumbs.db
+
+# vi swap files
+*.swp
+
+# backup files
+*.bak
+
+# gradle directory
+.gradle
+
+#for oh-my-zsh jira plugin (https://github.com/robbyrussell/oh-my-zsh/wiki/Plugins#jira)
+.jira-url
\ No newline at end of file
diff --git a/org.eclipse.paho.android.service/README.md b/org.eclipse.paho.android.service/README.md
new file mode 100644
index 00000000..08994ec3
--- /dev/null
+++ b/org.eclipse.paho.android.service/README.md
@@ -0,0 +1,8 @@
+#Android Service Client
+
+#Generate JavaDoc
+1. Switch directory to ```cd org.eclipse.paho.android.service```
+2. ```android update -p .```
+3. Build Android project ```ant debug```
+4. Documentation is in ```out\docs```
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/.classpath b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/.classpath
new file mode 100644
index 00000000..d0166965
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/.classpath
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/.project b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/.project
new file mode 100644
index 00000000..89851694
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/.project
@@ -0,0 +1,33 @@
+
+
+ org.eclipse.paho.android.service.sample
+
+
+
+
+
+ com.android.ide.eclipse.adt.ResourceManagerBuilder
+
+
+
+
+ com.android.ide.eclipse.adt.PreCompilerBuilder
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ com.android.ide.eclipse.adt.ApkBuilder
+
+
+
+
+
+ com.android.ide.eclipse.adt.AndroidNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/AndroidManifest.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/AndroidManifest.xml
new file mode 100644
index 00000000..cd1008e8
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/AndroidManifest.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/assets/file b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/assets/file
new file mode 100644
index 00000000..e69de29b
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/build.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/build.xml
new file mode 100644
index 00000000..5a59bf3a
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/build.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/ic_launcher-web.png b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/ic_launcher-web.png
new file mode 100644
index 00000000..420c8f4b
Binary files /dev/null and b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/ic_launcher-web.png differ
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/libs/android-support-v4.jar b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/libs/android-support-v4.jar
new file mode 100644
index 00000000..96644edb
Binary files /dev/null and b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/libs/android-support-v4.jar differ
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/proguard-project.txt b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/proguard-project.txt
new file mode 100644
index 00000000..f2fe1559
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/project.properties b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/project.properties
new file mode 100644
index 00000000..4ab12569
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/project.properties
@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-19
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-hdpi/arrow.png b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-hdpi/arrow.png
new file mode 100644
index 00000000..1a1d32bf
Binary files /dev/null and b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-hdpi/arrow.png differ
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-hdpi/ic_launcher.png b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 00000000..d2dbf5fa
Binary files /dev/null and b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-hdpi/ic_launcher.png differ
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-ldpi/arrow.png b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-ldpi/arrow.png
new file mode 100644
index 00000000..b44c3319
Binary files /dev/null and b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-ldpi/arrow.png differ
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-ldpi/ic_launcher.png b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 00000000..d3fc2077
Binary files /dev/null and b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-ldpi/ic_launcher.png differ
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-mdpi/arrow.png b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-mdpi/arrow.png
new file mode 100644
index 00000000..32bfaa93
Binary files /dev/null and b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-mdpi/arrow.png differ
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-mdpi/ic_launcher.png b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 00000000..ee154667
Binary files /dev/null and b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-mdpi/ic_launcher.png differ
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-xhdpi/arrow.png b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-xhdpi/arrow.png
new file mode 100644
index 00000000..9c136ac4
Binary files /dev/null and b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-xhdpi/arrow.png differ
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-xhdpi/ic_launcher.png b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..fbc42311
Binary files /dev/null and b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/activity_advanced.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/activity_advanced.xml
new file mode 100644
index 00000000..3e741f3e
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/activity_advanced.xml
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/activity_connection_details.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/activity_connection_details.xml
new file mode 100644
index 00000000..2cc1a6be
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/activity_connection_details.xml
@@ -0,0 +1,17 @@
+
+
\ No newline at end of file
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/activity_new_connection.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/activity_new_connection.xml
new file mode 100644
index 00000000..0f5a50c9
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/activity_new_connection.xml
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/activity_publish.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/activity_publish.xml
new file mode 100644
index 00000000..d0cdf128
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/activity_publish.xml
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/activity_subscribe.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/activity_subscribe.xml
new file mode 100644
index 00000000..08daf42a
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/activity_subscribe.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/client_connections.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/client_connections.xml
new file mode 100644
index 00000000..6944e3b8
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/client_connections.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/connection_text_view.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/connection_text_view.xml
new file mode 100644
index 00000000..4b0f593c
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/connection_text_view.xml
@@ -0,0 +1,20 @@
+
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/filedialogitem.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/filedialogitem.xml
new file mode 100644
index 00000000..cf1450b7
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/filedialogitem.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/list_view_text_view.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/list_view_text_view.xml
new file mode 100644
index 00000000..c6d20447
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/layout/list_view_text_view.xml
@@ -0,0 +1,19 @@
+
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_advanced.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_advanced.xml
new file mode 100644
index 00000000..ad08a847
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_advanced.xml
@@ -0,0 +1,24 @@
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_client_connections_contextual.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_client_connections_contextual.xml
new file mode 100644
index 00000000..52fdbfdf
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_client_connections_contextual.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_connection_details.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_connection_details.xml
new file mode 100644
index 00000000..0ae5de9b
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_connection_details.xml
@@ -0,0 +1,14 @@
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_connection_details_disconnected.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_connection_details_disconnected.xml
new file mode 100644
index 00000000..78a22615
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_connection_details_disconnected.xml
@@ -0,0 +1,15 @@
+
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_connections.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_connections.xml
new file mode 100644
index 00000000..e382946c
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_connections.xml
@@ -0,0 +1,24 @@
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_connections_logging.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_connections_logging.xml
new file mode 100644
index 00000000..5ead6612
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_connections_logging.xml
@@ -0,0 +1,24 @@
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_last_will.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_last_will.xml
new file mode 100644
index 00000000..0343d3e6
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_last_will.xml
@@ -0,0 +1,19 @@
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_new_connection.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_new_connection.xml
new file mode 100644
index 00000000..3a0aa888
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_new_connection.xml
@@ -0,0 +1,22 @@
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_publish.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_publish.xml
new file mode 100644
index 00000000..8044c225
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_publish.xml
@@ -0,0 +1,20 @@
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_publish_disconnected.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_publish_disconnected.xml
new file mode 100644
index 00000000..a9ea3547
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_publish_disconnected.xml
@@ -0,0 +1,20 @@
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_subscribe.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_subscribe.xml
new file mode 100644
index 00000000..9a2ff7ec
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_subscribe.xml
@@ -0,0 +1,20 @@
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_subscribe_disconnected.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_subscribe_disconnected.xml
new file mode 100644
index 00000000..5956f2e5
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/menu/activity_subscribe_disconnected.xml
@@ -0,0 +1,20 @@
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/raw/jsr47android.properties b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/raw/jsr47android.properties
new file mode 100644
index 00000000..1ffd17c5
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/raw/jsr47android.properties
@@ -0,0 +1,85 @@
+# Properties file which configures the operation of the JDK logging facility.
+#
+# The configuration in this file is the suggesgted configuration
+# for collecting trace for helping debug problems related to the
+# Paho MQTT client. It configures trace to be continuosly collected
+# in memory with minimal impact on performance.
+#
+# When the push trigger (by default a Severe level message) or a
+# specific request is made to "push" the in memory trace then it
+# is "pushed" to the configured target handler. By default
+# this is the standard java.util.logging.FileHandler. The Paho Debug
+# class can be used to push the memory trace to its target
+#
+# To enable trace either:
+# - use this properties file as is and set the logging facility up
+# to use it by configuring the util logging system property e.g.
+#
+# >java -Djava.util.logging.config.file=\jsr47android.properties
+#
+# - explicitly load this file as the configuration file for a LogManager
+#
+# - This contents of this file can also be merged with another
+# java.util.logging config file to ensure provide wider logging
+# and trace including Paho trace
+
+# Global logging properties.
+# ------------------------------------------
+# The set of handlers to be loaded upon startup.
+# Comma-separated list of class names.
+handlers=java.util.logging.FileHandler
+
+# Default global logging level.
+# Loggers and Handlers may override this level
+#.level=INFO
+
+# Loggers
+# ------------------------------------------
+# A FileHandler is attached to the paho packages
+# and the level specified to collected all trace related
+# to paho packages. This will override any root/global
+# level handlers if set.
+org.eclipse.paho.client.mqttv3.handlers=java.util.logging.FileHandler
+
+org.eclipse.paho.client.mqttv3.level=ALL
+# It is possible to set more granular trace on a per class basis e.g.
+#org.eclipse.paho.client.mqttv3.internal.ClientComms.level=ALL
+
+# Handlers
+# -----------------------------------------
+# Note: the target handler that is associated with the MemoryHandler is not a root handler
+# and hence not returned when getting the handlers from root. It appears accessing
+# target handler programatically is not possible as target is a private variable in
+# class MemoryHandler
+java.util.logging.MemoryHandler.level=FINEST
+java.util.logging.MemoryHandler.size=10000
+java.util.logging.MemoryHandler.push=SEVERE
+java.util.logging.MemoryHandler.target=java.util.logging.FileHandler
+#java.util.logging.MemoryHandler.target=java.util.logging.ConsoleHandler
+
+
+# --- FileHandler ---
+# Override of global logging level
+java.util.logging.FileHandler.level=ALL
+
+# Naming style for the output file:
+# (The output file is placed in the directory
+# defined by the "java.io.tmpdir" System property.)
+# See java.util.logging for more options
+java.util.logging.FileHandler.pattern=%t/paho%u.log
+
+# Limiting size of output file in bytes:
+java.util.logging.FileHandler.limit=200000
+
+# Number of output files to cycle through, by appending an
+# integer to the base file name:
+java.util.logging.FileHandler.count=3
+
+# Style of output (Simple or XML):
+java.util.logging.FileHandler.formatter=org.eclipse.paho.client.mqttv3.logging.SimpleLogFormatter
+
+# --- ConsoleHandler ---
+# Override of global logging level
+#java.util.logging.ConsoleHandler.level=INFO
+#java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
+#java.util.logging.ConsoleHandler.formatter=org.eclipse.paho.client.mqttv3.internal.logging.SimpleLogFormatter
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/values-v11/styles.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/values-v11/styles.xml
new file mode 100644
index 00000000..3c02242a
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/values-v11/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/values-v14/styles.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/values-v14/styles.xml
new file mode 100644
index 00000000..a91fd037
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/values-v14/styles.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/values/attrs_arrow.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/values/attrs_arrow.xml
new file mode 100644
index 00000000..712b79ef
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/values/attrs_arrow.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/values/strings.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/values/strings.xml
new file mode 100644
index 00000000..f044fbbd
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/values/strings.xml
@@ -0,0 +1,110 @@
+
+
+
+ MQTT
+ Settings
+ Server
+ Port
+ Connect
+
+ New Connection
+ +
+
+ New Connection
+ Client ID
+ Connected to
+
+ Disconnect
+ Subscribe
+ Publish
+ Subscribe
+ Disconnected from
+ Topic
+ History
+ History
+ Subscribed to
+ org.eclipse.paho.android.service.sample.ClientConnections
+ Unknown connection status to
+ Connecting to
+ Disconnecting from
+ An error occurred connecting to
+
+ Publish
+ Message
+ 0
+ 1
+ 2
+
+ QOS
+ Retained
+ Clean Session
+ Advanced Options
+ Advanced
+ Advanced
+ User Name
+ Password
+ SSL
+ Time Out
+ Keep Alive \nTimeout
+
+ Last Will Message
+ Set Last Will Message
+ Set Last Will Message
+ Last Will
+ MQTT Message Received
+ MQTT Client Has Lost Connection
+
+ Save
+ Connection Details
+ Enable Logging
+ Disable Logging
+ ClientID, server address or Port number is missing. Please correct or press back arrow cancel.
+
+ dd/MM/yy \'at\' HH:mm:ss
+ Unknown Error
+ Delete Connection
+ Disconnect Client?
+ The selected client is currently connected or connecting, deleting this connection will cause it to be disconnected.
+ Continue
+ Cancel
+
+
+ Received message %1$s <br/> <small>Topic: %2$s </small>
+ <br/> <small> %1$s </small>
+ %1$s has received %2$s on topic %3$s
+ Published message: %1$s to topic: %2$s
+ Subscribed to %1$s
+ Disconnected
+ Failed to publish message: %1$s to topic: %2$s
+ Failed to subscribe to %1$s
+ %1$s has lost connection to %2$s
+ Disconnect failed.<br/> <small>Reason: %s</small>
+ Client failed to connect.<br/> <small>Reason: %s</small>
+ Client connected successfully
+
+
+ example.example.com
+ 1883
+ Enable SSL
+ Clean Session
+ hello
+ Hello World
+ user
+ password
+ 60
+ 200
+ file://
+ select
+
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/values/styles.xml b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/values/styles.xml
new file mode 100644
index 00000000..6ce89c7b
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/res/values/styles.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/ActionListener.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/ActionListener.java
new file mode 100644
index 00000000..c490b99c
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/ActionListener.java
@@ -0,0 +1,234 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service.sample;
+
+import org.eclipse.paho.android.service.sample.Connection.ConnectionStatus;
+import org.eclipse.paho.client.mqttv3.IMqttActionListener;
+import org.eclipse.paho.client.mqttv3.IMqttToken;
+
+import android.content.Context;
+import android.widget.Toast;
+
+/**
+ * This Class handles receiving information from the
+ * {@link MqttAndroidClient} and updating the {@link Connection} associated with
+ * the action
+ */
+class ActionListener implements IMqttActionListener {
+
+ /**
+ * Actions that can be performed Asynchronously and associated with a
+ * {@link ActionListener} object
+ *
+ */
+ enum Action {
+ /** Connect Action **/
+ CONNECT,
+ /** Disconnect Action **/
+ DISCONNECT,
+ /** Subscribe Action **/
+ SUBSCRIBE,
+ /** Publish Action **/
+ PUBLISH
+ }
+
+ /**
+ * The {@link Action} that is associated with this instance of
+ * ActionListener
+ **/
+ private Action action;
+ /** The arguments passed to be used for formatting strings**/
+ private String[] additionalArgs;
+ /** Handle of the {@link Connection} this action was being executed on **/
+ private String clientHandle;
+ /** {@link Context} for performing various operations **/
+ private Context context;
+
+ /**
+ * Creates a generic action listener for actions performed form any activity
+ *
+ * @param context
+ * The application context
+ * @param action
+ * The action that is being performed
+ * @param clientHandle
+ * The handle for the client which the action is being performed
+ * on
+ * @param additionalArgs
+ * Used for as arguments for string formating
+ */
+ public ActionListener(Context context, Action action,
+ String clientHandle, String... additionalArgs) {
+ this.context = context;
+ this.action = action;
+ this.clientHandle = clientHandle;
+ this.additionalArgs = additionalArgs;
+ }
+
+ /**
+ * The action associated with this listener has been successful.
+ *
+ * @param asyncActionToken
+ * This argument is not used
+ */
+ @Override
+ public void onSuccess(IMqttToken asyncActionToken) {
+ switch (action) {
+ case CONNECT :
+ connect();
+ break;
+ case DISCONNECT :
+ disconnect();
+ break;
+ case SUBSCRIBE :
+ subscribe();
+ break;
+ case PUBLISH :
+ publish();
+ break;
+ }
+
+ }
+
+ /**
+ * A publish action has been successfully completed, update connection
+ * object associated with the client this action belongs to, then notify the
+ * user of success
+ */
+ private void publish() {
+
+ Connection c = Connections.getInstance(context).getConnection(clientHandle);
+ String actionTaken = context.getString(R.string.toast_pub_success,
+ (Object[]) additionalArgs);
+ c.addAction(actionTaken);
+ Notify.toast(context, actionTaken, Toast.LENGTH_SHORT);
+ }
+
+ /**
+ * A subscribe action has been successfully completed, update the connection
+ * object associated with the client this action belongs to and then notify
+ * the user of success
+ */
+ private void subscribe() {
+ Connection c = Connections.getInstance(context).getConnection(clientHandle);
+ String actionTaken = context.getString(R.string.toast_sub_success,
+ (Object[]) additionalArgs);
+ c.addAction(actionTaken);
+ Notify.toast(context, actionTaken, Toast.LENGTH_SHORT);
+
+ }
+
+ /**
+ * A disconnection action has been successfully completed, update the
+ * connection object associated with the client this action belongs to and
+ * then notify the user of success.
+ */
+ private void disconnect() {
+ Connection c = Connections.getInstance(context).getConnection(clientHandle);
+ c.changeConnectionStatus(ConnectionStatus.DISCONNECTED);
+ String actionTaken = context.getString(R.string.toast_disconnected);
+ c.addAction(actionTaken);
+
+ }
+
+ /**
+ * A connection action has been successfully completed, update the
+ * connection object associated with the client this action belongs to and
+ * then notify the user of success.
+ */
+ private void connect() {
+
+ Connection c = Connections.getInstance(context).getConnection(clientHandle);
+ c.changeConnectionStatus(Connection.ConnectionStatus.CONNECTED);
+ c.addAction("Client Connected");
+
+ }
+
+ /**
+ * The action associated with the object was a failure
+ *
+ * @param token
+ * This argument is not used
+ * @param exception
+ * The exception which indicates why the action failed
+ */
+ @Override
+ public void onFailure(IMqttToken token, Throwable exception) {
+ switch (action) {
+ case CONNECT :
+ connect(exception);
+ break;
+ case DISCONNECT :
+ disconnect(exception);
+ break;
+ case SUBSCRIBE :
+ subscribe(exception);
+ break;
+ case PUBLISH :
+ publish(exception);
+ break;
+ }
+
+ }
+
+ /**
+ * A publish action was unsuccessful, notify user and update client history
+ *
+ * @param exception
+ * This argument is not used
+ */
+ private void publish(Throwable exception) {
+ Connection c = Connections.getInstance(context).getConnection(clientHandle);
+ String action = context.getString(R.string.toast_pub_failed,
+ (Object[]) additionalArgs);
+ c.addAction(action);
+ Notify.toast(context, action, Toast.LENGTH_SHORT);
+
+ }
+
+ /**
+ * A subscribe action was unsuccessful, notify user and update client history
+ * @param exception This argument is not used
+ */
+ private void subscribe(Throwable exception) {
+ Connection c = Connections.getInstance(context).getConnection(clientHandle);
+ String action = context.getString(R.string.toast_sub_failed,
+ (Object[]) additionalArgs);
+ c.addAction(action);
+ Notify.toast(context, action, Toast.LENGTH_SHORT);
+
+ }
+
+ /**
+ * A disconnect action was unsuccessful, notify user and update client history
+ * @param exception This argument is not used
+ */
+ private void disconnect(Throwable exception) {
+ Connection c = Connections.getInstance(context).getConnection(clientHandle);
+ c.changeConnectionStatus(ConnectionStatus.DISCONNECTED);
+ c.addAction("Disconnect Failed - an error occured");
+
+ }
+
+ /**
+ * A connect action was unsuccessful, notify the user and update client history
+ * @param exception This argument is not used
+ */
+ private void connect(Throwable exception) {
+ Connection c = Connections.getInstance(context).getConnection(clientHandle);
+ c.changeConnectionStatus(Connection.ConnectionStatus.ERROR);
+ c.addAction("Client failed to connect");
+
+ }
+
+}
\ No newline at end of file
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/ActivityConstants.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/ActivityConstants.java
new file mode 100644
index 00000000..509cf2fb
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/ActivityConstants.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service.sample;
+
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+/**
+ * This Class provides constants used for returning results from an activity
+ *
+ */
+public class ActivityConstants {
+
+ /** Application TAG for logs where class name is not used*/
+ static final String TAG = "MQTT Android";
+
+ /*Default values **/
+
+ /** Default QOS value*/
+ static final int defaultQos = 0;
+ /** Default timeout*/
+ static final int defaultTimeOut = 1000;
+ /** Default keep alive value*/
+ static final int defaultKeepAlive = 10;
+ /** Default SSL enabled flag*/
+ static final boolean defaultSsl = false;
+ /** Default message retained flag */
+ static final boolean defaultRetained = false;
+ /** Default last will message*/
+ static final MqttMessage defaultLastWill = null;
+ /** Default port*/
+ static final int defaultPort = 1883;
+
+ /** Connect Request Code */
+ static final int connect = 0;
+ /** Advanced Connect Request Code **/
+ static final int advancedConnect = 1;
+ /** Last will Request Code **/
+ static final int lastWill = 2;
+ /** Show History Request Code **/
+ static final int showHistory = 3;
+
+ /* Bundle Keys */
+
+ /** Server Bundle Key **/
+ static final String server = "server";
+ /** Port Bundle Key **/
+ static final String port = "port";
+ /** ClientID Bundle Key **/
+ static final String clientId = "clientId";
+ /** Topic Bundle Key **/
+ static final String topic = "topic";
+ /** History Bundle Key **/
+ static final String history = "history";
+ /** Message Bundle Key **/
+ static final String message = "message";
+ /** Retained Flag Bundle Key **/
+ static final String retained = "retained";
+ /** QOS Value Bundle Key **/
+ static final String qos = "qos";
+ /** User name Bundle Key **/
+ static final String username = "username";
+ /** Password Bundle Key **/
+ static final String password = "password";
+ /** Keep Alive value Bundle Key **/
+ static final String keepalive = "keepalive";
+ /** Timeout Bundle Key **/
+ static final String timeout = "timeout";
+ /** SSL Enabled Flag Bundle Key **/
+ static final String ssl = "ssl";
+ /** SSL Key File Bundle Key **/
+ static final String ssl_key = "ssl_key";
+ /** Connections Bundle Key **/
+ static final String connections = "connections";
+ /** Clean Session Flag Bundle Key **/
+ static final String cleanSession = "cleanSession";
+ /** Action Bundle Key **/
+ static final String action = "action";
+
+ /* Property names */
+
+ /** Property name for the history field in {@link Connection} object for use with {@link java.beans.PropertyChangeEvent} **/
+ static final String historyProperty = "history";
+
+ /** Property name for the connection status field in {@link Connection} object for use with {@link java.beans.PropertyChangeEvent} **/
+ static final String ConnectionStatusProperty = "connectionStatus";
+
+ /* Useful constants*/
+
+ /** Space String Literal **/
+ static final String space = " ";
+ /** Empty String for comparisons **/
+ static final String empty = new String();
+
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/Advanced.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/Advanced.java
new file mode 100644
index 00000000..5e6feced
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/Advanced.java
@@ -0,0 +1,276 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service.sample;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.NavUtils;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import org.eclipse.paho.android.service.sample.R;
+
+/**
+ * Advanced connection options activity
+ *
+ */
+public class Advanced extends Activity {
+
+ /**
+ * Reference to this class used in {@link Advanced.Listener} methods
+ */
+ private Advanced advanced = this;
+ /**
+ * Holds the result data from activities launched from this activity
+ */
+ private Bundle resultData = null;
+
+ private int openfileDialogId = 0;
+
+ /**
+ * @see Activity#onCreate(Bundle)
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_advanced);
+
+ ((Button) findViewById(R.id.sslKeyBut)).setOnClickListener(new OnClickListener(){
+
+ @Override
+ public void onClick(View v) {
+ //showFileChooser();
+ showDialog(openfileDialogId);
+ }});
+
+ ((CheckBox) findViewById(R.id.sslCheckBox)).setOnClickListener(new OnClickListener(){
+
+ @Override
+ public void onClick(View v) {
+ if(((CheckBox)v).isChecked())
+ {
+ ((Button)findViewById(R.id.sslKeyBut)).setClickable(true);
+ }else
+ {
+ ((Button)findViewById(R.id.sslKeyBut)).setClickable(false);
+ }
+
+ }});
+
+ ((Button)findViewById(R.id.sslKeyBut)).setClickable(false);
+ }
+
+ /**
+ * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.activity_advanced, menu);
+
+ Listener listener = new Listener();
+ menu.findItem(R.id.setLastWill).setOnMenuItemClickListener(listener);
+ menu.findItem(R.id.ok).setOnMenuItemClickListener(listener);
+
+ return true;
+ }
+
+ /**
+ * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home :
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ /**
+ * @see android.app.Activity#onActivityResult(int, int, android.content.Intent)
+ */
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode,
+ Intent intent) {
+ // get the last will data
+ if (resultCode == RESULT_CANCELED) {
+ return;
+ }
+ resultData = intent.getExtras();
+
+ }
+
+ /**
+ * @see android.app.Activity#onCreateDialog(int)
+ */
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ if (id == openfileDialogId) {
+ Map images = new HashMap();
+ images.put(OpenFileDialog.sRoot, R.drawable.ic_launcher);
+ images.put(OpenFileDialog.sParent, R.drawable.ic_launcher);
+ images.put(OpenFileDialog.sFolder, R.drawable.ic_launcher);
+ images.put("bks", R.drawable.ic_launcher);
+ images.put(OpenFileDialog.sEmpty, R.drawable.ic_launcher);
+ Dialog dialog = OpenFileDialog.createDialog(id, this, "openfile",
+ new CallbackBundle() {
+ @Override
+ public void callback(Bundle bundle) {
+ String filepath = bundle.getString("path");
+ // setTitle(filepath);
+ ((EditText) findViewById(R.id.sslKeyLocaltion))
+ .setText(filepath);
+ }
+ }, ".bks;", images);
+ return dialog;
+ }
+ return null;
+ }
+
+ /**
+ * Deals with button clicks for the advanced options page
+ *
+ */
+ private class Listener implements OnMenuItemClickListener {
+
+ /**
+ * @see android.view.MenuItem.OnMenuItemClickListener#onMenuItemClick(MenuItem)
+ */
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+
+ int button = item.getItemId();
+
+ switch (button) {
+ case R.id.ok :
+ ok();
+ break;
+
+ case R.id.setLastWill :
+ lastWill();
+ break;
+ }
+ return false;
+ }
+
+ /**
+ * Packs the default options into an intent
+ * @return intent packed with default options
+ */
+ @SuppressWarnings("unused")
+ private Intent packDefaults() {
+ Intent intent = new Intent();
+
+ // check to see if there is any result data if there is not any
+ // result data build some with defaults
+
+ intent.putExtras(resultData);
+ intent.putExtra(ActivityConstants.username, ActivityConstants.empty);
+ intent.putExtra(ActivityConstants.password, ActivityConstants.empty);
+
+ intent.putExtra(ActivityConstants.timeout, ActivityConstants.defaultTimeOut);
+ intent.putExtra(ActivityConstants.keepalive,
+ ActivityConstants.defaultKeepAlive);
+ intent.putExtra(ActivityConstants.ssl, ActivityConstants.defaultSsl);
+
+ return intent;
+ }
+
+ /**
+ * Starts an activity to collect last will options
+ */
+ private void lastWill() {
+
+ Intent intent = new Intent();
+ intent.setClassName(advanced, "org.eclipse.paho.android.service.sample.LastWill");
+ advanced.startActivityForResult(intent, ActivityConstants.lastWill);
+
+ }
+
+ /**
+ * Packs all the options the user has chosen, along with defaults the user has not chosen
+ */
+ private void ok() {
+
+ int keepalive;
+ int timeout;
+
+ Intent intent = new Intent();
+
+ if (resultData == null) {
+ resultData = new Bundle();
+ resultData.putString(ActivityConstants.message, ActivityConstants.empty);
+ resultData.putString(ActivityConstants.topic, ActivityConstants.empty);
+ resultData.putInt(ActivityConstants.qos, ActivityConstants.defaultQos);
+ resultData.putBoolean(ActivityConstants.retained,
+ ActivityConstants.defaultRetained);
+ }
+
+ intent.putExtras(resultData);
+
+ // get all advance options
+ String username = ((EditText) findViewById(R.id.uname)).getText()
+ .toString();
+ String password = ((EditText) findViewById(R.id.password))
+ .getText().toString();
+ String sslkey = null;
+ boolean ssl = ((CheckBox) findViewById(R.id.sslCheckBox)).isChecked();
+ if(ssl)
+ {
+ sslkey = ((EditText) findViewById(R.id.sslKeyLocaltion))
+ .getText().toString();
+ }
+ try {
+ timeout = Integer
+ .parseInt(((EditText) findViewById(R.id.timeout))
+ .getText().toString());
+ }
+ catch (NumberFormatException nfe) {
+ timeout = ActivityConstants.defaultTimeOut;
+ }
+ try {
+ keepalive = Integer
+ .parseInt(((EditText) findViewById(R.id.keepalive))
+ .getText().toString());
+ }
+ catch (NumberFormatException nfe) {
+ keepalive = ActivityConstants.defaultKeepAlive;
+ }
+
+ //put the daya collected into the intent
+ intent.putExtra(ActivityConstants.username, username);
+ intent.putExtra(ActivityConstants.password, password);
+
+ intent.putExtra(ActivityConstants.timeout, timeout);
+ intent.putExtra(ActivityConstants.keepalive, keepalive);
+ intent.putExtra(ActivityConstants.ssl, ssl);
+ intent.putExtra(ActivityConstants.ssl_key, sslkey);
+ //set the result as okay, with the data, and finish
+ advanced.setResult(RESULT_OK, intent);
+ advanced.finish();
+ }
+
+ }
+
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/CallbackBundle.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/CallbackBundle.java
new file mode 100644
index 00000000..c848ea3e
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/CallbackBundle.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service.sample;
+
+
+import android.os.Bundle;
+
+/**
+ * For File selector to share data
+ *
+ */
+public interface CallbackBundle {
+ abstract void callback(Bundle bundle);
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/ClientConnections.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/ClientConnections.java
new file mode 100644
index 00000000..80ba9a04
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/ClientConnections.java
@@ -0,0 +1,494 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service.sample;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.util.Map;
+
+import org.eclipse.paho.android.service.MqttAndroidClient;
+import org.eclipse.paho.android.service.sample.Connection.ConnectionStatus;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttSecurityException;
+
+import android.app.AlertDialog;
+import android.app.ListActivity;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemLongClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+/**
+ * ClientConnections is the main activity for the sample application, it
+ * displays all the active connections.
+ *
+ */
+public class ClientConnections extends ListActivity {
+
+ /**
+ * Token to pass to the MQTT Service
+ */
+ final static String TOKEN = "org.eclipse.paho.android.service.sample.ClientConnections";
+
+ /**
+ * ArrayAdapter to populate the list view
+ */
+ private ArrayAdapter arrayAdapter = null;
+
+ /**
+ * {@link ChangeListener} for use with all {@link Connection} objects created by this instance of ClientConnections
+ */
+ private ChangeListener changeListener = new ChangeListener();
+
+ /**
+ * This instance of ClientConnections used to update the UI in {@link ChangeListener}
+ */
+ private ClientConnections clientConnections = this;
+
+ /**
+ * Contextual action bar active or not
+ */
+ private boolean contextualActionBarActive = false;
+
+ /**
+ * @see android.app.ListActivity#onCreate(Bundle)
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ ListView connectionList = getListView();
+ connectionList.setOnItemLongClickListener(new LongClickItemListener());
+ connectionList.setTextFilterEnabled(true);
+ arrayAdapter = new ArrayAdapter(this,
+ R.layout.connection_text_view);
+ setListAdapter(arrayAdapter);
+
+ // get all the available connections
+ Map connections = Connections.getInstance(this)
+ .getConnections();
+
+ if (connections != null) {
+ for (String s : connections.keySet())
+ {
+ arrayAdapter.add(connections.get(s));
+ }
+ }
+
+ }
+
+ /**
+ * Creates the action bar for the activity
+ *
+ * @see ListActivity#onCreateOptionsMenu(Menu)
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+
+ OnMenuItemClickListener menuItemClickListener = new Listener(this);
+
+ //load the correct menu depending on the status of logging
+ if (Listener.logging)
+ {
+ getMenuInflater().inflate(R.menu.activity_connections_logging, menu);
+ menu.findItem(R.id.endLogging).setOnMenuItemClickListener(menuItemClickListener);
+ }
+ else {
+ getMenuInflater().inflate(R.menu.activity_connections, menu);
+ menu.findItem(R.id.startLogging).setOnMenuItemClickListener(menuItemClickListener);
+ }
+
+ menu.findItem(R.id.newConnection).setOnMenuItemClickListener(
+ menuItemClickListener);
+
+ return true;
+ }
+
+ /**
+ * Listens for item clicks on the view
+ *
+ * @param listView
+ * The list view where the click originated from
+ * @param view
+ * The view which was clicked
+ * @param position
+ * The position in the list that was clicked
+ */
+ @Override
+ protected void onListItemClick(ListView listView, View view, int position,
+ long id) {
+ super.onListItemClick(listView, view, position, id);
+
+ if (!contextualActionBarActive) {
+ Connection c = arrayAdapter.getItem(position);
+
+ // start the connectionDetails activity to display the details about the
+ // selected connection
+ Intent intent = new Intent();
+ intent.setClassName(getApplicationContext().getPackageName(),
+ "org.eclipse.paho.android.service.sample.ConnectionDetails");
+ intent.putExtra("handle", c.handle());
+ startActivity(intent);
+ }
+
+ }
+
+ /**
+ * @see ListActivity#onActivityResult(int,int,Intent)
+ */
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+
+ if (resultCode == RESULT_CANCELED) {
+ return;
+ }
+
+ Bundle dataBundle = data.getExtras();
+
+ // perform connection create and connect
+ connectAction(dataBundle);
+
+ }
+
+ /**
+ * @see ListActivity#onResume()
+ */
+ @Override
+ protected void onResume() {
+ super.onResume();
+ arrayAdapter.notifyDataSetChanged();
+
+ //Recover connections.
+ Map connections = Connections.getInstance(this).getConnections();
+
+ //Register receivers again
+ for (Connection connection : connections.values()){
+ connection.getClient().registerResources(this);
+ connection.getClient().setCallback(new MqttCallbackHandler(this, connection.getClient().getServerURI()+connection.getClient().getClientId()));
+ }
+ }
+
+ /**
+ * @see ListActivity#onDestroy()
+ */
+ @Override
+ protected void onDestroy() {
+
+ Map connections = Connections.getInstance(this).getConnections();
+
+ for (Connection connection : connections.values()){
+ connection.registerChangeListener(changeListener);
+ connection.getClient().unregisterResources();
+ }
+ super.onDestroy();
+ }
+
+ /**
+ * Process data from the connect action
+ *
+ * @param data the {@link Bundle} returned by the {@link NewConnection} Acitivty
+ */
+ private void connectAction(Bundle data) {
+ MqttConnectOptions conOpt = new MqttConnectOptions();
+ /*
+ * Mutal Auth connections could do something like this
+ *
+ *
+ * SSLContext context = SSLContext.getDefault();
+ * context.init({new CustomX509KeyManager()},null,null); //where CustomX509KeyManager proxies calls to keychain api
+ * SSLSocketFactory factory = context.getSSLSocketFactory();
+ *
+ * MqttConnectOptions options = new MqttConnectOptions();
+ * options.setSocketFactory(factory);
+ *
+ * client.connect(options);
+ *
+ */
+
+ // The basic client information
+ String server = (String) data.get(ActivityConstants.server);
+ String clientId = (String) data.get(ActivityConstants.clientId);
+ int port = Integer.parseInt((String) data.get(ActivityConstants.port));
+ boolean cleanSession = (Boolean) data.get(ActivityConstants.cleanSession);
+
+ boolean ssl = (Boolean) data.get(ActivityConstants.ssl);
+ String ssl_key = (String) data.get(ActivityConstants.ssl_key);
+ String uri = null;
+ if (ssl) {
+ Log.e("SSLConnection", "Doing an SSL Connect");
+ uri = "ssl://";
+
+ }
+ else {
+ uri = "tcp://";
+ }
+
+ uri = uri + server + ":" + port;
+
+ MqttAndroidClient client;
+ client = Connections.getInstance(this).createClient(this, uri, clientId);
+
+ if (ssl){
+ try {
+ if(ssl_key != null && !ssl_key.equalsIgnoreCase(""))
+ {
+ FileInputStream key = new FileInputStream(ssl_key);
+ conOpt.setSocketFactory(client.getSSLSocketFactory(key,
+ "mqtttest"));
+ }
+
+ } catch (MqttSecurityException e) {
+ Log.e(this.getClass().getCanonicalName(),
+ "MqttException Occured: ", e);
+ } catch (FileNotFoundException e) {
+ Log.e(this.getClass().getCanonicalName(),
+ "MqttException Occured: SSL Key file not found", e);
+ }
+ }
+
+ // create a client handle
+ String clientHandle = uri + clientId;
+
+ // last will message
+ String message = (String) data.get(ActivityConstants.message);
+ String topic = (String) data.get(ActivityConstants.topic);
+ Integer qos = (Integer) data.get(ActivityConstants.qos);
+ Boolean retained = (Boolean) data.get(ActivityConstants.retained);
+
+ // connection options
+
+ String username = (String) data.get(ActivityConstants.username);
+
+ String password = (String) data.get(ActivityConstants.password);
+
+ int timeout = (Integer) data.get(ActivityConstants.timeout);
+ int keepalive = (Integer) data.get(ActivityConstants.keepalive);
+
+ Connection connection = new Connection(clientHandle, clientId, server, port,
+ this, client, ssl);
+ arrayAdapter.add(connection);
+
+ connection.registerChangeListener(changeListener);
+ // connect client
+
+ String[] actionArgs = new String[1];
+ actionArgs[0] = clientId;
+ connection.changeConnectionStatus(ConnectionStatus.CONNECTING);
+
+ conOpt.setCleanSession(cleanSession);
+ conOpt.setConnectionTimeout(timeout);
+ conOpt.setKeepAliveInterval(keepalive);
+ if (!username.equals(ActivityConstants.empty)) {
+ conOpt.setUserName(username);
+ }
+ if (!password.equals(ActivityConstants.empty)) {
+ conOpt.setPassword(password.toCharArray());
+ }
+
+ final ActionListener callback = new ActionListener(this,
+ ActionListener.Action.CONNECT, clientHandle, actionArgs);
+
+ boolean doConnect = true;
+
+ if ((!message.equals(ActivityConstants.empty))
+ || (!topic.equals(ActivityConstants.empty))) {
+ // need to make a message since last will is set
+ try {
+ conOpt.setWill(topic, message.getBytes(), qos.intValue(),
+ retained.booleanValue());
+ }
+ catch (Exception e) {
+ Log.e(this.getClass().getCanonicalName(), "Exception Occured", e);
+ doConnect = false;
+ callback.onFailure(null, e);
+ }
+ }
+ client.setCallback(new MqttCallbackHandler(this, clientHandle));
+
+
+ //set traceCallback
+ client.setTraceCallback(new MqttTraceCallback());
+
+ connection.addConnectionOptions(conOpt);
+ Connections.getInstance(this).addConnection(connection);
+ if (doConnect) {
+ try {
+ client.connect(conOpt, null, callback);
+ }
+ catch (MqttException e) {
+ Log.e(this.getClass().getCanonicalName(),
+ "MqttException Occured", e);
+ }
+ }
+
+ }
+
+ /**
+ * LongClickItemListener deals with enabling and disabling the contextual action bar and
+ * processing the actions selected.
+ *
+ */
+ private class LongClickItemListener implements OnItemLongClickListener, ActionMode.Callback, OnClickListener {
+
+ /** The index of the item selected, or -1 if an item is not selected **/
+ private int selected = -1;
+ /** The view of the item selected **/
+ private View selectedView = null;
+ /** The connection the view is representing **/
+ private Connection connection = null;
+
+ /* (non-Javadoc)
+ * @see android.widget.AdapterView.OnItemLongClickListener#onItemLongClick(android.widget.AdapterView, android.view.View, int, long)
+ */
+ @Override
+ public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
+ clientConnections.startActionMode(this);
+ selected = position;
+ selectedView = view;
+ clientConnections.getListView().setSelection(position);
+ view.setBackgroundColor(getResources().getColor(android.R.color.holo_blue_dark));
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode, android.view.MenuItem)
+ */
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ selectedView.setBackgroundColor(getResources().getColor(android.R.color.white));
+ switch (item.getItemId()) {
+ case R.id.delete :
+ delete();
+ mode.finish();
+ return true;
+ default :
+ return false;
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode, android.view.Menu)
+ */
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ MenuInflater inflater = mode.getMenuInflater();
+ inflater.inflate(R.menu.activity_client_connections_contextual, menu);
+ clientConnections.contextualActionBarActive = true;
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see android.view.ActionMode.Callback#onDestroyActionMode(android.view.ActionMode)
+ */
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ selected = -1;
+ selectedView = null;
+
+ }
+
+ /* (non-Javadoc)
+ * @see android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode, android.view.Menu)
+ */
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+
+ /**
+ * Deletes the connection, disconnecting if required.
+ */
+ private void delete()
+ {
+ connection = arrayAdapter.getItem(selected);
+ if (connection.isConnectedOrConnecting()) {
+
+ //display a dialog
+ AlertDialog.Builder builder = new AlertDialog.Builder(clientConnections);
+ builder.setTitle(R.string.disconnectClient)
+ .setMessage(getString(R.string.deleteDialog))
+ .setNegativeButton(R.string.cancelBtn, new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface arg0, int arg1) {
+ //do nothing user cancelled action
+ }
+ })
+ .setPositiveButton(R.string.continueBtn, this)
+ .show();
+ }
+ else {
+ arrayAdapter.remove(connection);
+ Connections.getInstance(clientConnections).removeConnection(connection);
+ }
+
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ //user pressed continue disconnect client and delete
+ try {
+ connection.getClient().disconnect();
+ }
+ catch (MqttException e) {
+ e.printStackTrace();
+ }
+ arrayAdapter.remove(connection);
+ Connections.getInstance(clientConnections).removeConnection(connection);
+
+ }
+ }
+
+ /**
+ * This class ensures that the user interface is updated as the Connection objects change their states
+ *
+ *
+ */
+ private class ChangeListener implements PropertyChangeListener {
+
+ /**
+ * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
+ */
+ @Override
+ public void propertyChange(PropertyChangeEvent event) {
+
+ if (!event.getPropertyName().equals(ActivityConstants.ConnectionStatusProperty)) {
+ return;
+ }
+ clientConnections.runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ clientConnections.arrayAdapter.notifyDataSetChanged();
+ }
+
+ });
+
+ }
+
+ }
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/Connection.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/Connection.java
new file mode 100644
index 00000000..0ffd7548
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/Connection.java
@@ -0,0 +1,393 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service.sample;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import org.eclipse.paho.android.service.sample.R;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import android.content.Context;
+import android.text.Html;
+import android.text.Spanned;
+import org.eclipse.paho.android.service.MqttAndroidClient;
+
+/**
+ *
+ * Represents a {@link MqttAndroidClient} and the actions it has performed
+ *
+ */
+public class Connection {
+
+ /*
+ * Basic Information about the client
+ */
+ /** ClientHandle for this Connection Object**/
+ private String clientHandle = null;
+ /** The clientId of the client associated with this Connection object **/
+ private String clientId = null;
+ /** The host that the {@link MqttAndroidClient} represented by this Connection is represented by **/
+ private String host = null;
+ /** The port on the server this client is connecting to **/
+ private int port = 0;
+ /** {@link ConnectionStatus} of the {@link MqttAndroidClient} represented by this Connection object. Default value is {@link ConnectionStatus#NONE} **/
+ private ConnectionStatus status = ConnectionStatus.NONE;
+ /** The history of the {@link MqttAndroidClient} represented by this Connection object **/
+ private ArrayList history = null;
+ /** The {@link MqttAndroidClient} instance this class represents**/
+ private MqttAndroidClient client = null;
+
+ /** Collection of {@link PropertyChangeListener} **/
+ private ArrayList listeners = new ArrayList();
+
+ /** The {@link Context} of the application this object is part of**/
+ private Context context = null;
+
+ /** The {@link MqttConnectOptions} that were used to connect this client**/
+ private MqttConnectOptions conOpt;
+
+ /** True if this connection is secured using SSL **/
+ private boolean sslConnection = false;
+
+ /** Persistence id, used by {@link Persistence} **/
+ private long persistenceId = -1;
+
+ /**
+ * Connections status for a connection
+ */
+ enum ConnectionStatus {
+
+ /** Client is Connecting **/
+ CONNECTING,
+ /** Client is Connected **/
+ CONNECTED,
+ /** Client is Disconnecting **/
+ DISCONNECTING,
+ /** Client is Disconnected **/
+ DISCONNECTED,
+ /** Client has encountered an Error **/
+ ERROR,
+ /** Status is unknown **/
+ NONE
+ }
+
+ /**
+ * Creates a connection from persisted information in the database store, attempting
+ * to create a {@link MqttAndroidClient} and the client handle.
+ * @param clientId The id of the client
+ * @param host the server which the client is connecting to
+ * @param port the port on the server which the client will attempt to connect to
+ * @param context the application context
+ * @param sslConnection true if the connection is secured by SSL
+ * @return a new instance of Connection
+ */
+ public static Connection createConnection(String clientId, String host,
+ int port, Context context, boolean sslConnection) {
+ String handle = null;
+ String uri = null;
+ if (sslConnection) {
+ uri = "ssl://" + host + ":" + port;
+ handle = uri + clientId;
+ }
+ else {
+ uri = "tcp://" + host + ":" + port;
+ handle = uri + clientId;
+ }
+ MqttAndroidClient client = new MqttAndroidClient(context, uri, clientId);
+ return new Connection(handle, clientId, host, port, context, client, sslConnection);
+
+ }
+
+ /**
+ * Creates a connection object with the server information and the client
+ * hand which is the reference used to pass the client around activities
+ * @param clientHandle The handle to this Connection object
+ * @param clientId The Id of the client
+ * @param host The server which the client is connecting to
+ * @param port The port on the server which the client will attempt to connect to
+ * @param context The application context
+ * @param client The MqttAndroidClient which communicates with the service for this connection
+ * @param sslConnection true if the connection is secured by SSL
+ */
+ public Connection(String clientHandle, String clientId, String host,
+ int port, Context context, MqttAndroidClient client, boolean sslConnection) {
+ //generate the client handle from its hash code
+ this.clientHandle = clientHandle;
+ this.clientId = clientId;
+ this.host = host;
+ this.port = port;
+ this.context = context;
+ this.client = client;
+ this.sslConnection = sslConnection;
+ history = new ArrayList();
+ StringBuffer sb = new StringBuffer();
+ sb.append("Client: ");
+ sb.append(clientId);
+ sb.append(" created");
+ addAction(sb.toString());
+ }
+
+ /**
+ * Add an action to the history of the client
+ * @param action the history item to add
+ */
+ public void addAction(String action) {
+
+ Object[] args = new String[1];
+ SimpleDateFormat sdf = new SimpleDateFormat(context.getString(R.string.dateFormat));
+ args[0] = sdf.format(new Date());
+
+ String timestamp = context.getString(R.string.timestamp, args);
+ history.add(action + timestamp);
+
+ notifyListeners(new PropertyChangeEvent(this, ActivityConstants.historyProperty, null, null));
+ }
+
+ /**
+ * Generate an array of Spanned items representing the history of this
+ * connection.
+ *
+ * @return an array of history entries
+ */
+ public Spanned[] history() {
+
+ int i = 0;
+ Spanned[] array = new Spanned[history.size()];
+
+ for (String s : history) {
+ if (s != null) {
+ array[i] = Html.fromHtml(s);
+ i++;
+ }
+ }
+
+ return array;
+
+ }
+
+ /**
+ * Gets the client handle for this connection
+ * @return client Handle for this connection
+ */
+ public String handle() {
+ return clientHandle;
+ }
+
+ /**
+ * Determines if the client is connected
+ * @return is the client connected
+ */
+ public boolean isConnected() {
+ return status == ConnectionStatus.CONNECTED;
+ }
+
+ /**
+ * Changes the connection status of the client
+ * @param connectionStatus The connection status of this connection
+ */
+ public void changeConnectionStatus(ConnectionStatus connectionStatus) {
+ status = connectionStatus;
+ notifyListeners((new PropertyChangeEvent(this, ActivityConstants.ConnectionStatusProperty, null, null)));
+ }
+
+ /**
+ * A string representing the state of the client this connection
+ * object represents
+ *
+ *
+ * @return A string representing the state of the client
+ */
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(clientId);
+ sb.append("\n ");
+
+ switch (status) {
+
+ case CONNECTED :
+ sb.append(context.getString(R.string.connectedto));
+ break;
+ case DISCONNECTED :
+ sb.append(context.getString(R.string.disconnected));
+ break;
+ case NONE :
+ sb.append(context.getString(R.string.no_status));
+ break;
+ case CONNECTING :
+ sb.append(context.getString(R.string.connecting));
+ break;
+ case DISCONNECTING :
+ sb.append(context.getString(R.string.disconnecting));
+ break;
+ case ERROR :
+ sb.append(context.getString(R.string.connectionError));
+ }
+ sb.append(" ");
+ sb.append(host);
+
+ return sb.toString();
+ }
+
+ /**
+ * Determines if a given handle refers to this client
+ * @param handle The handle to compare with this clients handle
+ * @return true if the handles match
+ */
+ public boolean isHandle(String handle) {
+ return clientHandle.equals(handle);
+ }
+
+ /**
+ * Compares two connection objects for equality
+ * this only takes account of the client handle
+ * @param o The object to compare to
+ * @return true if the client handles match
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Connection)) {
+ return false;
+ }
+
+ Connection c = (Connection) o;
+
+ return clientHandle.equals(c.clientHandle);
+
+ }
+
+ /**
+ * Get the client Id for the client this object represents
+ * @return the client id for the client this object represents
+ */
+ public String getId() {
+ return clientId;
+ }
+
+ /**
+ * Get the host name of the server that this connection object is associated with
+ * @return the host name of the server this connection object is associated with
+ */
+ public String getHostName() {
+
+ return host;
+ }
+
+ /**
+ * Determines if the client is in a state of connecting or connected.
+ * @return if the client is connecting or connected
+ */
+ public boolean isConnectedOrConnecting() {
+ return (status == ConnectionStatus.CONNECTED) || (status == ConnectionStatus.CONNECTING);
+ }
+
+ /**
+ * Client is currently not in an error state
+ * @return true if the client is in not an error state
+ */
+ public boolean noError() {
+ return status != ConnectionStatus.ERROR;
+ }
+
+ /**
+ * Gets the client which communicates with the android service.
+ * @return the client which communicates with the android service
+ */
+ public MqttAndroidClient getClient() {
+ return client;
+ }
+
+ /**
+ * Add the connectOptions used to connect the client to the server
+ * @param connectOptions the connectOptions used to connect to the server
+ */
+ public void addConnectionOptions(MqttConnectOptions connectOptions) {
+ conOpt = connectOptions;
+
+ }
+
+ /**
+ * Get the connectOptions used to connect this client to the server
+ * @return The connectOptions used to connect the client to the server
+ */
+ public MqttConnectOptions getConnectionOptions()
+ {
+ return conOpt;
+ }
+
+ /**
+ * Register a {@link PropertyChangeListener} to this object
+ * @param listener the listener to register
+ */
+ public void registerChangeListener(PropertyChangeListener listener)
+ {
+ listeners.add(listener);
+ }
+
+ /**
+ * Remove a registered {@link PropertyChangeListener}
+ * @param listener A reference to the listener to remove
+ */
+ public void removeChangeListener(PropertyChangeListener listener)
+ {
+ if (listener != null) {
+ listeners.remove(listener);
+ }
+ }
+
+ /**
+ * Notify {@link PropertyChangeListener} objects that the object has been updated
+ * @param propertyChangeEvent
+ */
+ private void notifyListeners(PropertyChangeEvent propertyChangeEvent)
+ {
+ for (PropertyChangeListener listener : listeners)
+ {
+ listener.propertyChange(propertyChangeEvent);
+ }
+ }
+
+ /**
+ * Gets the port that this connection connects to.
+ * @return port that this connection connects to
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * Determines if the connection is secured using SSL, returning a C style integer value
+ * @return 1 if SSL secured 0 if plain text
+ */
+ public int isSSL() {
+ return sslConnection ? 1 : 0;
+ }
+
+ /**
+ * Assign a persistence ID to this object
+ * @param id the persistence id to assign
+ */
+ public void assignPersistenceId(long id) {
+ persistenceId = id;
+ }
+
+ /**
+ * Returns the persistence ID assigned to this object
+ * @return the persistence ID assigned to this object
+ */
+ public long persistenceId() {
+ return persistenceId;
+ }
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/ConnectionDetails.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/ConnectionDetails.java
new file mode 100644
index 00000000..f3da9565
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/ConnectionDetails.java
@@ -0,0 +1,334 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service.sample;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.ArrayList;
+import org.eclipse.paho.android.service.sample.R;
+import android.app.ActionBar;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentPagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.view.Menu;
+
+/**
+ * The connection details activity operates the fragments that make up the
+ * connection details screen.
+ *
+ * The fragments which this FragmentActivity uses are
+ *
+ *
{@link HistoryFragment}
+ *
{@link PublishFragment}
+ *
{@link SubscribeFragment}
+ *
+ *
+ */
+public class ConnectionDetails extends FragmentActivity implements
+ ActionBar.TabListener {
+
+ /**
+ * {@link SectionsPagerAdapter} that is used to get pages to display
+ */
+ SectionsPagerAdapter sectionsPagerAdapter;
+ /**
+ * {@link ViewPager} object allows pages to be flipped left and right
+ */
+ ViewPager viewPager;
+
+ /** The currently selected tab **/
+ private int selected = 0;
+
+ /**
+ * The handle to the {@link Connection} which holds the data for the client
+ * selected
+ **/
+ private String clientHandle = null;
+
+ /** This instance of ConnectionDetails **/
+ private final ConnectionDetails connectionDetails = this;
+
+ /**
+ * The instance of {@link Connection} that the clientHandle
+ * represents
+ **/
+ private Connection connection = null;
+
+ /**
+ * The {@link ChangeListener} this object is using for the connection
+ * updates
+ **/
+ private ChangeListener changeListener = null;
+
+ /**
+ * @see android.support.v4.app.FragmentActivity#onCreate(android.os.Bundle)
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ clientHandle = getIntent().getStringExtra("handle");
+
+ setContentView(R.layout.activity_connection_details);
+ // Create the adapter that will return a fragment for each of the pages
+ sectionsPagerAdapter = new SectionsPagerAdapter(
+ getSupportFragmentManager());
+
+ // Set up the action bar for tab navigation
+ final ActionBar actionBar = getActionBar();
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+
+ // add the sectionsPagerAdapter
+ viewPager = (ViewPager) findViewById(R.id.pager);
+ viewPager.setAdapter(sectionsPagerAdapter);
+
+ viewPager
+ .setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
+
+ @Override
+ public void onPageSelected(int position) {
+ // select the tab that represents the current page
+ actionBar.setSelectedNavigationItem(position);
+
+ }
+ });
+
+ // Create the tabs for the screen
+ for (int i = 0; i < sectionsPagerAdapter.getCount(); i++) {
+ ActionBar.Tab tab = actionBar.newTab();
+ tab.setText(sectionsPagerAdapter.getPageTitle(i));
+ tab.setTabListener(this);
+ actionBar.addTab(tab);
+ }
+
+ connection = Connections.getInstance(this).getConnection(clientHandle);
+ changeListener = new ChangeListener();
+ connection.registerChangeListener(changeListener);
+ }
+
+ @Override
+ protected void onDestroy() {
+ connection.removeChangeListener(null);
+ super.onDestroy();
+ }
+
+ /**
+ * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ int menuID;
+ Integer button = null;
+ boolean connected = Connections.getInstance(this)
+ .getConnection(clientHandle).isConnected();
+
+ // Select the correct action bar menu to display based on the
+ // connectionStatus and which tab is selected
+ if (connected) {
+
+ switch (selected) {
+ case 0 : // history view
+ menuID = R.menu.activity_connection_details;
+ break;
+ case 1 : // subscribe view
+ menuID = R.menu.activity_subscribe;
+ button = R.id.subscribe;
+ break;
+ case 2 : // publish view
+ menuID = R.menu.activity_publish;
+ button = R.id.publish;
+ break;
+ default :
+ menuID = R.menu.activity_connection_details;
+ break;
+ }
+ }
+ else {
+ switch (selected) {
+ case 0 : // history view
+ menuID = R.menu.activity_connection_details_disconnected;
+ break;
+ case 1 : // subscribe view
+ menuID = R.menu.activity_subscribe_disconnected;
+ button = R.id.subscribe;
+ break;
+ case 2 : // publish view
+ menuID = R.menu.activity_publish_disconnected;
+ button = R.id.publish;
+ break;
+ default :
+ menuID = R.menu.activity_connection_details_disconnected;
+ break;
+ }
+ }
+ // inflate the menu selected
+ getMenuInflater().inflate(menuID, menu);
+ Listener listener = new Listener(this, clientHandle);
+ // add listeners
+ if (button != null) {
+ // add listeners
+ menu.findItem(button).setOnMenuItemClickListener(listener);
+ if (!Connections.getInstance(this).getConnection(clientHandle)
+ .isConnected()) {
+ menu.findItem(button).setEnabled(false);
+ }
+ }
+ // add the listener to the disconnect or connect menu option
+ if (connected) {
+ menu.findItem(R.id.disconnect).setOnMenuItemClickListener(listener);
+ }
+ else {
+ menu.findItem(R.id.connectMenuOption).setOnMenuItemClickListener(
+ listener);
+ }
+
+ return true;
+ }
+
+ /**
+ * @see android.app.ActionBar.TabListener#onTabUnselected(android.app.ActionBar.Tab,
+ * android.app.FragmentTransaction)
+ */
+ @Override
+ public void onTabUnselected(ActionBar.Tab tab,
+ FragmentTransaction fragmentTransaction) {
+ // Don't need to do anything when a tab is unselected
+ }
+
+ /**
+ * @see android.app.ActionBar.TabListener#onTabSelected(android.app.ActionBar.Tab,
+ * android.app.FragmentTransaction)
+ */
+ @Override
+ public void onTabSelected(ActionBar.Tab tab,
+ FragmentTransaction fragmentTransaction) {
+ // When the given tab is selected, switch to the corresponding page in
+ // the ViewPager.
+ viewPager.setCurrentItem(tab.getPosition());
+ selected = tab.getPosition();
+ // invalidate the options menu so it can be updated
+ invalidateOptionsMenu();
+ // history fragment is at position zero so get this then refresh its
+ // view
+ ((HistoryFragment) sectionsPagerAdapter.getItem(0)).refresh();
+ }
+
+ /**
+ * @see android.app.ActionBar.TabListener#onTabReselected(android.app.ActionBar.Tab,
+ * android.app.FragmentTransaction)
+ */
+ @Override
+ public void onTabReselected(ActionBar.Tab tab,
+ FragmentTransaction fragmentTransaction) {
+ // Don't need to do anything when the tab is reselected
+ }
+
+ /**
+ * Provides the Activity with the pages to display for each tab
+ *
+ */
+ public class SectionsPagerAdapter extends FragmentPagerAdapter {
+
+ // Stores the instances of the pages
+ private ArrayList fragments = null;
+
+ /**
+ * Only Constructor, requires a the activity's fragment managers
+ *
+ * @param fragmentManager
+ */
+ public SectionsPagerAdapter(FragmentManager fragmentManager) {
+ super(fragmentManager);
+ fragments = new ArrayList();
+ // create the history view, passes the client handle as an argument
+ // through a bundle
+ Fragment fragment = new HistoryFragment();
+ Bundle args = new Bundle();
+ args.putString("handle", getIntent().getStringExtra("handle"));
+ fragment.setArguments(args);
+ // add all the fragments for the display to the fragments list
+ fragments.add(fragment);
+ fragments.add(new SubscribeFragment());
+ fragments.add(new PublishFragment());
+
+ }
+
+ /**
+ * @see android.support.v4.app.FragmentPagerAdapter#getItem(int)
+ */
+ @Override
+ public Fragment getItem(int position) {
+ return fragments.get(position);
+ }
+
+ /**
+ * @see android.support.v4.view.PagerAdapter#getCount()
+ */
+ @Override
+ public int getCount() {
+ return fragments.size();
+ }
+
+ /**
+ *
+ * @see FragmentPagerAdapter#getPageTitle(int)
+ */
+ @Override
+ public CharSequence getPageTitle(int position) {
+ switch (position) {
+ case 0 :
+ return getString(R.string.history).toUpperCase();
+ case 1 :
+ return getString(R.string.subscribe).toUpperCase();
+ case 2 :
+ return getString(R.string.publish).toUpperCase();
+ }
+ // return null if there is no title matching the position
+ return null;
+ }
+
+ }
+
+ /**
+ * ChangeListener updates the UI when the {@link Connection}
+ * object it is associated with updates
+ *
+ */
+ private class ChangeListener implements PropertyChangeListener {
+
+ /**
+ * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
+ */
+ @Override
+ public void propertyChange(PropertyChangeEvent event) {
+ // connection object has change refresh the UI
+
+ connectionDetails.runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ connectionDetails.invalidateOptionsMenu();
+ ((HistoryFragment) connectionDetails.sectionsPagerAdapter
+ .getItem(0)).refresh();
+
+ }
+ });
+
+ }
+ }
+
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/Connections.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/Connections.java
new file mode 100644
index 00000000..fbd4b01f
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/Connections.java
@@ -0,0 +1,136 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service.sample;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import android.content.Context;
+
+import org.eclipse.paho.android.service.MqttAndroidClient;
+
+/**
+ * Connections is a singleton class which stores all the connection objects
+ * in one central place so they can be passed between activities using a client
+ * handle
+ *
+ */
+public class Connections {
+
+ /** Singleton instance of Connections**/
+ private static Connections instance = null;
+
+ /** List of {@link Connection} objects**/
+ private HashMap connections = null;
+
+ /** {@link Persistence} object used to save, delete and restore connections**/
+ private Persistence persistence = null;
+
+ /**
+ * Create a Connections object
+ * @param context Applications context
+ */
+ private Connections(Context context)
+ {
+ connections = new HashMap();
+
+ //attempt to restore state
+ persistence = new Persistence(context);
+ try {
+ List l = persistence.restoreConnections(context);
+ for (Connection c : l) {
+ connections.put(c.handle(), c);
+ }
+ }
+ catch (PersistenceException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ /**
+ * Returns an already initialised instance of Connections, if Connections has yet to be created, it will
+ * create and return that instance
+ * @param context The applications context used to create the Connections object if it is not already initialised
+ * @return Connections instance
+ */
+ public synchronized static Connections getInstance(Context context)
+ {
+ if (instance == null) {
+ instance = new Connections(context);
+ }
+
+ return instance;
+ }
+
+ /**
+ * Finds and returns a connection object that the given client handle points to
+ * @param handle The handle to the Connection to return
+ * @return a connection associated with the client handle, null if one is not found
+ */
+ public Connection getConnection(String handle)
+ {
+
+ return connections.get(handle);
+ }
+
+ /**
+ * Adds a Connection object to the collection of connections associated with this object
+ * @param connection connection to add
+ */
+ public void addConnection(Connection connection)
+ {
+ connections.put(connection.handle(), connection);
+ try {
+ persistence.persistConnection(connection);
+ }
+ catch (PersistenceException e)
+ {
+ //error persisting well lets just swallow this
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Create a fully initialised MqttAndroidClient for the parameters given
+ * @param context The Applications context
+ * @param serverURI The ServerURI to connect to
+ * @param clientId The clientId for this client
+ * @return new instance of MqttAndroidClient
+ */
+ public MqttAndroidClient createClient(Context context, String serverURI, String clientId)
+ {
+ MqttAndroidClient client = new MqttAndroidClient(context, serverURI, clientId);
+ return client;
+ }
+
+ /**
+ * Get all the connections associated with this Connections object.
+ * @return Map of connections
+ */
+ public Map getConnections()
+ {
+ return connections;
+ }
+
+ /**
+ * Removes a connection from the map of connections
+ * @param connection connection to be removed
+ */
+ public void removeConnection(Connection connection) {
+ connections.remove(connection.handle());
+ persistence.deleteConnection(connection);
+ }
+
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/HistoryFragment.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/HistoryFragment.java
new file mode 100644
index 00000000..6feeb59f
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/HistoryFragment.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service.sample;
+
+import android.os.Bundle;
+import android.support.v4.app.ListFragment;
+import android.text.Spanned;
+import android.widget.ArrayAdapter;
+import org.eclipse.paho.android.service.sample.R;
+
+/**
+ * This fragment displays the history information for a client
+ *
+ */
+public class HistoryFragment extends ListFragment {
+
+ /** Client handle to a {@link Connection} object **/
+ String clientHandle = null;
+ /** {@link ArrayAdapter} to display the formatted text **/
+ ArrayAdapter arrayAdapter = null;
+
+ /**
+ * @see ListFragment#onCreate(Bundle)
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ //Pull history information out of bundle
+
+ clientHandle = getArguments().getString("handle");
+ Connection connection = Connections.getInstance(getActivity()).getConnection(clientHandle);
+
+ Spanned[] history = connection.history();
+
+ //Initialise the arrayAdapter, view and add data
+ arrayAdapter = new ArrayAdapter(getActivity(), R.layout.list_view_text_view);
+
+ arrayAdapter.addAll(history);
+ setListAdapter(arrayAdapter);
+
+ }
+
+ /**
+ * Updates the data displayed to match the current history
+ */
+ public void refresh() {
+ if (arrayAdapter != null) {
+ arrayAdapter.clear();
+ arrayAdapter.addAll(Connections.getInstance(getActivity()).getConnection(clientHandle).history());
+ arrayAdapter.notifyDataSetChanged();
+ }
+
+ }
+
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/LastWill.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/LastWill.java
new file mode 100644
index 00000000..62ef09a8
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/LastWill.java
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service.sample;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.RadioGroup;
+import org.eclipse.paho.android.service.sample.R;
+
+/**
+ * Activity for setting the last will message for the client
+ *
+ */
+public class LastWill extends Activity {
+
+ /**
+ * Reference to the current instance of LastWill for use with anonymous listener
+ */
+ private LastWill last = this;
+
+ /**
+ * @see Activity#onCreate(Bundle)
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_publish);
+
+ }
+
+ /**
+ * @see Activity#onCreateOptionsMenu(Menu)
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.activity_last_will, menu);
+
+ menu.findItem(R.id.publish).setOnMenuItemClickListener(new OnMenuItemClickListener() {
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+
+ Intent result = new Intent();
+
+ String message = ((EditText) findViewById(R.id.lastWill)).getText().toString();
+ String topic = ((EditText) findViewById(R.id.lastWillTopic)).getText().toString();
+
+ RadioGroup radio = (RadioGroup) findViewById(R.id.qosRadio);
+ int checked = radio.getCheckedRadioButtonId();
+ int qos = ActivityConstants.defaultQos;
+
+ //determine which qos value has been selected
+ switch (checked)
+ {
+ case R.id.qos0 :
+ qos = 0;
+ break;
+ case R.id.qos1 :
+ qos = 1;
+ break;
+ case R.id.qos2 :
+ qos = 2;
+ break;
+ }
+
+ boolean retained = ((CheckBox) findViewById(R.id.retained)).isChecked();
+
+ //package the data collected into the intent
+ result.putExtra(ActivityConstants.message, message);
+ result.putExtra(ActivityConstants.topic, topic);
+ result.putExtra(ActivityConstants.qos, qos);
+ result.putExtra(ActivityConstants.retained, retained);
+
+ //set the result and finish activity
+ last.setResult(RESULT_OK, result);
+ last.finish();
+
+ return false;
+ }
+
+ });
+ return true;
+ }
+
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/Listener.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/Listener.java
new file mode 100644
index 00000000..a57a9943
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/Listener.java
@@ -0,0 +1,324 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service.sample;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.LogManager;
+
+import org.eclipse.paho.android.service.sample.R;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttSecurityException;
+
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.RadioGroup;
+
+import org.eclipse.paho.android.service.sample.ActionListener.Action;
+import org.eclipse.paho.android.service.sample.Connection.ConnectionStatus;
+import org.eclipse.paho.android.service.MqttAndroidClient;
+
+/**
+ * Deals with actions performed in the {@link ClientConnections} activity
+ * and the {@link ConnectionDetails} activity and associated fragments
+ *
+ */
+public class Listener implements OnMenuItemClickListener {
+
+ /** The handle to a {@link Connection} object which contains the {@link MqttAndroidClient} associated with this object **/
+ private String clientHandle = null;
+
+ /** {@link ConnectionDetails} reference used to perform some actions**/
+ private ConnectionDetails connectionDetails = null;
+ /** {@link ClientConnections} reference used to perform some actions**/
+ private ClientConnections clientConnections = null;
+ /** {@link Context} used to load and format strings **/
+ private Context context = null;
+
+ /** Whether Paho is logging is enabled**/
+ static boolean logging = false;
+
+ /**
+ * Constructs a listener object for use with {@link ConnectionDetails} activity and
+ * associated fragments.
+ * @param connectionDetails The instance of {@link ConnectionDetails}
+ * @param clientHandle The handle to the client that the actions are to be performed on
+ */
+ public Listener(ConnectionDetails connectionDetails, String clientHandle)
+ {
+ this.connectionDetails = connectionDetails;
+ this.clientHandle = clientHandle;
+ context = connectionDetails;
+
+ }
+
+ /**
+ * Constructs a listener object for use with {@link ClientConnections} activity.
+ * @param clientConnections The instance of {@link ClientConnections}
+ */
+ public Listener(ClientConnections clientConnections) {
+ this.clientConnections = clientConnections;
+ context = clientConnections;
+ }
+
+ /**
+ * Perform the needed action required based on the button that
+ * the user has clicked.
+ *
+ * @param item The menu item that was clicked
+ * @return If there is anymore processing to be done
+ *
+ */
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+
+ int id = item.getItemId();
+
+ switch (id)
+ {
+ case R.id.publish :
+ publish();
+ break;
+ case R.id.subscribe :
+ subscribe();
+ break;
+ case R.id.newConnection :
+ createAndConnect();
+ break;
+ case R.id.disconnect :
+ disconnect();
+ break;
+ case R.id.connectMenuOption :
+ reconnect();
+ break;
+ case R.id.startLogging :
+ enablePahoLogging();
+ break;
+ case R.id.endLogging :
+ disablePahoLogging();
+ break;
+ }
+
+ return false;
+ }
+
+ /**
+ * Reconnect the selected client
+ */
+ private void reconnect() {
+
+ Connections.getInstance(context).getConnection(clientHandle).changeConnectionStatus(ConnectionStatus.CONNECTING);
+
+ Connection c = Connections.getInstance(context).getConnection(clientHandle);
+ try {
+ c.getClient().connect(c.getConnectionOptions(), null, new ActionListener(context, Action.CONNECT, clientHandle, null));
+ }
+ catch (MqttSecurityException e) {
+ Log.e(this.getClass().getCanonicalName(), "Failed to reconnect the client with the handle " + clientHandle, e);
+ c.addAction("Client failed to connect");
+ }
+ catch (MqttException e) {
+ Log.e(this.getClass().getCanonicalName(), "Failed to reconnect the client with the handle " + clientHandle, e);
+ c.addAction("Client failed to connect");
+ }
+
+ }
+
+ /**
+ * Disconnect the client
+ */
+ private void disconnect() {
+
+ Connection c = Connections.getInstance(context).getConnection(clientHandle);
+
+ //if the client is not connected, process the disconnect
+ if (!c.isConnected()) {
+ return;
+ }
+
+ try {
+ c.getClient().disconnect(null, new ActionListener(context, Action.DISCONNECT, clientHandle, null));
+ c.changeConnectionStatus(ConnectionStatus.DISCONNECTING);
+ }
+ catch (MqttException e) {
+ Log.e(this.getClass().getCanonicalName(), "Failed to disconnect the client with the handle " + clientHandle, e);
+ c.addAction("Client failed to disconnect");
+ }
+
+ }
+
+ /**
+ * Subscribe to a topic that the user has specified
+ */
+ private void subscribe()
+ {
+ String topic = ((EditText) connectionDetails.findViewById(R.id.topic)).getText().toString();
+ ((EditText) connectionDetails.findViewById(R.id.topic)).getText().clear();
+
+ RadioGroup radio = (RadioGroup) connectionDetails.findViewById(R.id.qosSubRadio);
+ int checked = radio.getCheckedRadioButtonId();
+ int qos = ActivityConstants.defaultQos;
+
+ switch (checked) {
+ case R.id.qos0 :
+ qos = 0;
+ break;
+ case R.id.qos1 :
+ qos = 1;
+ break;
+ case R.id.qos2 :
+ qos = 2;
+ break;
+ }
+
+ try {
+ String[] topics = new String[1];
+ topics[0] = topic;
+ Connections.getInstance(context).getConnection(clientHandle).getClient()
+ .subscribe(topic, qos, null, new ActionListener(context, Action.SUBSCRIBE, clientHandle, topics));
+ }
+ catch (MqttSecurityException e) {
+ Log.e(this.getClass().getCanonicalName(), "Failed to subscribe to" + topic + " the client with the handle " + clientHandle, e);
+ }
+ catch (MqttException e) {
+ Log.e(this.getClass().getCanonicalName(), "Failed to subscribe to" + topic + " the client with the handle " + clientHandle, e);
+ }
+ }
+
+ /**
+ * Publish the message the user has specified
+ */
+ private void publish()
+ {
+ String topic = ((EditText) connectionDetails.findViewById(R.id.lastWillTopic))
+ .getText().toString();
+
+ ((EditText) connectionDetails.findViewById(R.id.lastWillTopic)).getText().clear();
+
+ String message = ((EditText) connectionDetails.findViewById(R.id.lastWill)).getText()
+ .toString();
+
+ ((EditText) connectionDetails.findViewById(R.id.lastWill)).getText().clear();
+
+ RadioGroup radio = (RadioGroup) connectionDetails.findViewById(R.id.qosRadio);
+ int checked = radio.getCheckedRadioButtonId();
+ int qos = ActivityConstants.defaultQos;
+
+ switch (checked) {
+ case R.id.qos0 :
+ qos = 0;
+ break;
+ case R.id.qos1 :
+ qos = 1;
+ break;
+ case R.id.qos2 :
+ qos = 2;
+ break;
+ }
+
+ boolean retained = ((CheckBox) connectionDetails.findViewById(R.id.retained))
+ .isChecked();
+
+ String[] args = new String[2];
+ args[0] = message;
+ args[1] = topic+";qos:"+qos+";retained:"+retained;
+
+ try {
+ Connections.getInstance(context).getConnection(clientHandle).getClient()
+ .publish(topic, message.getBytes(), qos, retained, null, new ActionListener(context, Action.PUBLISH, clientHandle, args));
+ }
+ catch (MqttSecurityException e) {
+ Log.e(this.getClass().getCanonicalName(), "Failed to publish a messged from the client with the handle " + clientHandle, e);
+ }
+ catch (MqttException e) {
+ Log.e(this.getClass().getCanonicalName(), "Failed to publish a messged from the client with the handle " + clientHandle, e);
+ }
+
+ }
+
+ /**
+ * Create a new client and connect
+ */
+ private void createAndConnect()
+ {
+ Intent createConnection;
+
+ //start a new activity to gather information for a new connection
+ createConnection = new Intent();
+ createConnection.setClassName(
+ clientConnections.getApplicationContext(),
+ "org.eclipse.paho.android.service.sample.NewConnection");
+
+ clientConnections.startActivityForResult(createConnection,
+ ActivityConstants.connect);
+ }
+
+ /**
+ * Enables logging in the Paho MQTT client
+ */
+ private void enablePahoLogging() {
+
+ try {
+ InputStream logPropStream = context.getResources().openRawResource(R.raw.jsr47android);
+ LogManager.getLogManager().readConfiguration(logPropStream);
+ logging = true;
+
+ HashMap connections = (HashMap)Connections.getInstance(context).getConnections();
+ if(!connections.isEmpty()){
+ Entry entry = connections.entrySet().iterator().next();
+ Connection connection = (Connection)entry.getValue();
+ connection.getClient().setTraceEnabled(true);
+ //change menu state.
+ clientConnections.invalidateOptionsMenu();
+ //Connections.getInstance(context).getConnection(clientHandle).getClient().setTraceEnabled(true);
+ }else{
+ Log.i("SampleListener","No connection to enable log in service");
+ }
+ }
+ catch (IOException e) {
+ Log.e("MqttAndroidClient",
+ "Error reading logging parameters", e);
+ }
+
+ }
+
+ /**
+ * Disables logging in the Paho MQTT client
+ */
+ private void disablePahoLogging() {
+ LogManager.getLogManager().reset();
+ logging = false;
+
+ HashMap connections = (HashMap)Connections.getInstance(context).getConnections();
+ if(!connections.isEmpty()){
+ Entry entry = connections.entrySet().iterator().next();
+ Connection connection = (Connection)entry.getValue();
+ connection.getClient().setTraceEnabled(false);
+ //change menu state.
+ clientConnections.invalidateOptionsMenu();
+ }else{
+ Log.i("SampleListener","No connection to disable log in service");
+ }
+ clientConnections.invalidateOptionsMenu();
+ }
+
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/MqttCallbackHandler.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/MqttCallbackHandler.java
new file mode 100644
index 00000000..cde0454b
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/MqttCallbackHandler.java
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service.sample;
+
+import org.eclipse.paho.android.service.sample.R;
+import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.MqttCallback;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import android.content.Context;
+import android.content.Intent;
+import org.eclipse.paho.android.service.sample.Connection.ConnectionStatus;
+
+/**
+ * Handles call backs from the MQTT Client
+ *
+ */
+public class MqttCallbackHandler implements MqttCallback {
+
+ /** {@link Context} for the application used to format and import external strings**/
+ private Context context;
+ /** Client handle to reference the connection that this handler is attached to**/
+ private String clientHandle;
+
+ /**
+ * Creates an MqttCallbackHandler object
+ * @param context The application's context
+ * @param clientHandle The handle to a {@link Connection} object
+ */
+ public MqttCallbackHandler(Context context, String clientHandle)
+ {
+ this.context = context;
+ this.clientHandle = clientHandle;
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.MqttCallback#connectionLost(java.lang.Throwable)
+ */
+ @Override
+ public void connectionLost(Throwable cause) {
+// cause.printStackTrace();
+ if (cause != null) {
+ Connection c = Connections.getInstance(context).getConnection(clientHandle);
+ c.addAction("Connection Lost");
+ c.changeConnectionStatus(ConnectionStatus.DISCONNECTED);
+
+ //format string to use a notification text
+ Object[] args = new Object[2];
+ args[0] = c.getId();
+ args[1] = c.getHostName();
+
+ String message = context.getString(R.string.connection_lost, args);
+
+ //build intent
+ Intent intent = new Intent();
+ intent.setClassName(context, "org.eclipse.paho.android.service.sample.ConnectionDetails");
+ intent.putExtra("handle", clientHandle);
+
+ //notify the user
+ Notify.notifcation(context, message, intent, R.string.notifyTitle_connectionLost);
+ }
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.MqttCallback#messageArrived(java.lang.String, org.eclipse.paho.client.mqttv3.MqttMessage)
+ */
+ @Override
+ public void messageArrived(String topic, MqttMessage message) throws Exception {
+
+ //Get connection object associated with this object
+ Connection c = Connections.getInstance(context).getConnection(clientHandle);
+
+ //create arguments to format message arrived notifcation string
+ String[] args = new String[2];
+ args[0] = new String(message.getPayload());
+ args[1] = topic+";qos:"+message.getQos()+";retained:"+message.isRetained();
+
+ //get the string from strings.xml and format
+ String messageString = context.getString(R.string.messageRecieved, (Object[]) args);
+
+ //create intent to start activity
+ Intent intent = new Intent();
+ intent.setClassName(context, "org.eclipse.paho.android.service.sample.ConnectionDetails");
+ intent.putExtra("handle", clientHandle);
+
+ //format string args
+ Object[] notifyArgs = new String[3];
+ notifyArgs[0] = c.getId();
+ notifyArgs[1] = new String(message.getPayload());
+ notifyArgs[2] = topic;
+
+ //notify the user
+ Notify.notifcation(context, context.getString(R.string.notification, notifyArgs), intent, R.string.notifyTitle);
+
+ //update client history
+ c.addAction(messageString);
+
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.MqttCallback#deliveryComplete(org.eclipse.paho.client.mqttv3.IMqttDeliveryToken)
+ */
+ @Override
+ public void deliveryComplete(IMqttDeliveryToken token) {
+ // Do nothing
+ }
+
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/MqttTraceCallback.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/MqttTraceCallback.java
new file mode 100644
index 00000000..91f1235e
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/MqttTraceCallback.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service.sample;
+
+import org.eclipse.paho.android.service.MqttTraceHandler;
+
+import android.util.Log;
+
+public class MqttTraceCallback implements MqttTraceHandler {
+
+ public void traceDebug(java.lang.String arg0, java.lang.String arg1) {
+ Log.i(arg0, arg1);
+ };
+
+ public void traceError(java.lang.String arg0, java.lang.String arg1) {
+ Log.e(arg0, arg1);
+ };
+
+ public void traceException(java.lang.String arg0, java.lang.String arg1,
+ java.lang.Exception arg2) {
+ Log.e(arg0, arg1, arg2);
+ };
+
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/NewConnection.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/NewConnection.java
new file mode 100644
index 00000000..dbbfb955
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/NewConnection.java
@@ -0,0 +1,274 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service.sample;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import org.eclipse.paho.android.service.sample.R;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.NavUtils;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.Toast;
+
+/**
+ * Handles collection of user information to create a new MQTT Client
+ *
+ */
+public class NewConnection extends Activity {
+
+ /** {@link Bundle} which holds data from activities launched from this activity **/
+ private Bundle result = null;
+
+ /**
+ * @see android.app.Activity#onCreate(android.os.Bundle)
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_new_connection);
+
+ ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1);
+ adapter.addAll(readHosts());
+ AutoCompleteTextView textView = (AutoCompleteTextView) findViewById(R.id.serverURI);
+ textView.setAdapter(adapter);
+
+ //load auto compete options
+
+ }
+
+ /**
+ * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.activity_new_connection, menu);
+ OnMenuItemClickListener listener = new Listener(this);
+ menu.findItem(R.id.connectAction).setOnMenuItemClickListener(listener);
+ menu.findItem(R.id.advanced).setOnMenuItemClickListener(listener);
+
+ return true;
+ }
+
+ /**
+ * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home :
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ /**
+ * @see android.app.Activity#onActivityResult(int, int, android.content.Intent)
+ */
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode,
+ Intent intent) {
+
+ if (resultCode == RESULT_CANCELED) {
+ return;
+ }
+
+ result = intent.getExtras();
+
+ }
+
+ /**
+ * Handles action bar actions
+ *
+ */
+ private class Listener implements OnMenuItemClickListener {
+
+ //used for starting activities
+ private NewConnection newConnection = null;
+
+ public Listener(NewConnection newConnection)
+ {
+ this.newConnection = newConnection;
+ }
+
+ /**
+ * @see android.view.MenuItem.OnMenuItemClickListener#onMenuItemClick(android.view.MenuItem)
+ */
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ {
+ // this will only connect need to package up and sent back
+
+ int id = item.getItemId();
+
+ Intent dataBundle = new Intent();
+
+ switch (id) {
+ case R.id.connectAction :
+ //extract client information
+ String server = ((AutoCompleteTextView) findViewById(R.id.serverURI))
+ .getText().toString();
+ String port = ((EditText) findViewById(R.id.port))
+ .getText().toString();
+ String clientId = ((EditText) findViewById(R.id.clientId))
+ .getText().toString();
+
+ if (server.equals(ActivityConstants.empty) || port.equals(ActivityConstants.empty) || clientId.equals(ActivityConstants.empty))
+ {
+ String notificationText = newConnection.getString(R.string.missingOptions);
+ Notify.toast(newConnection, notificationText, Toast.LENGTH_LONG);
+ return false;
+ }
+
+ boolean cleanSession = ((CheckBox) findViewById(R.id.cleanSessionCheckBox)).isChecked();
+ //persist server
+ persistServerURI(server);
+
+ //put data into a bundle to be passed back to ClientConnections
+ dataBundle.putExtra(ActivityConstants.server, server);
+ dataBundle.putExtra(ActivityConstants.port, port);
+ dataBundle.putExtra(ActivityConstants.clientId, clientId);
+ dataBundle.putExtra(ActivityConstants.action, ActivityConstants.connect);
+ dataBundle.putExtra(ActivityConstants.cleanSession, cleanSession);
+
+ if (result == null) {
+ // create a new bundle and put default advanced options into a bundle
+ result = new Bundle();
+
+ result.putString(ActivityConstants.message,
+ ActivityConstants.empty);
+ result.putString(ActivityConstants.topic, ActivityConstants.empty);
+ result.putInt(ActivityConstants.qos, ActivityConstants.defaultQos);
+ result.putBoolean(ActivityConstants.retained,
+ ActivityConstants.defaultRetained);
+
+ result.putString(ActivityConstants.username,
+ ActivityConstants.empty);
+ result.putString(ActivityConstants.password,
+ ActivityConstants.empty);
+
+ result.putInt(ActivityConstants.timeout,
+ ActivityConstants.defaultTimeOut);
+ result.putInt(ActivityConstants.keepalive,
+ ActivityConstants.defaultKeepAlive);
+ result.putBoolean(ActivityConstants.ssl,
+ ActivityConstants.defaultSsl);
+
+ }
+ //add result bundle to the data being returned to ClientConnections
+ dataBundle.putExtras(result);
+
+ setResult(RESULT_OK, dataBundle);
+ newConnection.finish();
+ break;
+ case R.id.advanced :
+ //start the advanced options activity
+ dataBundle.setClassName(newConnection,
+ "org.eclipse.paho.android.service.sample.Advanced");
+ newConnection.startActivityForResult(dataBundle,
+ ActivityConstants.advancedConnect);
+
+ break;
+ }
+ return false;
+
+ }
+
+ }
+
+ /**
+ * Add a server URI to the persisted file
+ *
+ * @param serverURI the uri to store
+ */
+ private void persistServerURI(String serverURI) {
+ File fileDir = newConnection.getFilesDir();
+ File presited = new File(fileDir, "hosts.txt");
+ BufferedWriter bfw = null;
+ try {
+ bfw = new BufferedWriter(new FileWriter(presited));
+ bfw.write(serverURI);
+ bfw.newLine();
+ }
+ catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ finally {
+ try {
+ if (bfw != null) {
+ bfw.close();
+ }
+ }
+ catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Read persisted hosts
+ * @return The hosts contained in the persisted file
+ */
+ private String[] readHosts() {
+ File fileDir = getFilesDir();
+ File persisted = new File(fileDir, "hosts.txt");
+ if (!persisted.exists()) {
+ return new String[0];
+ }
+ ArrayList hosts = new ArrayList();
+ BufferedReader br = null;
+ try {
+ br = new BufferedReader(new FileReader(persisted));
+ String line = null;
+ line = br.readLine();
+ while (line != null) {
+ hosts.add(line);
+ line = br.readLine();
+ }
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ finally {
+ try {
+ if (br != null) {
+ br.close();
+ }
+ }
+ catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ return hosts.toArray(new String[hosts.size()]);
+
+ }
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/Notify.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/Notify.java
new file mode 100644
index 00000000..67fa778d
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/Notify.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service.sample;
+
+import java.util.Calendar;
+import org.eclipse.paho.android.service.sample.R;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.support.v4.app.NotificationCompat.Builder;
+import android.widget.Toast;
+
+/**
+ * Provides static methods for creating and showing notifications to the user.
+ *
+ */
+public class Notify {
+
+ /** Message ID Counter **/
+ private static int MessageID = 0;
+
+ /**
+ * Displays a notification in the notification area of the UI
+ * @param context Context from which to create the notification
+ * @param messageString The string to display to the user as a message
+ * @param intent The intent which will start the activity when the user clicks the notification
+ * @param notificationTitle The resource reference to the notification title
+ */
+ static void notifcation(Context context, String messageString, Intent intent, int notificationTitle) {
+
+ //Get the notification manage which we will use to display the notification
+ String ns = Context.NOTIFICATION_SERVICE;
+ NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(ns);
+
+ Calendar.getInstance().getTime().toString();
+
+ long when = System.currentTimeMillis();
+
+ //get the notification title from the application's strings.xml file
+ CharSequence contentTitle = context.getString(notificationTitle);
+
+ //the message that will be displayed as the ticker
+ String ticker = contentTitle + " " + messageString;
+
+ //build the pending intent that will start the appropriate activity
+ PendingIntent pendingIntent = PendingIntent.getActivity(context,
+ ActivityConstants.showHistory, intent, 0);
+
+ //build the notification
+ Builder notificationCompat = new Builder(context);
+ notificationCompat.setAutoCancel(true)
+ .setContentTitle(contentTitle)
+ .setContentIntent(pendingIntent)
+ .setContentText(messageString)
+ .setTicker(ticker)
+ .setWhen(when)
+ .setSmallIcon(R.drawable.ic_launcher);
+
+ Notification notification = notificationCompat.build();
+ //display the notification
+ mNotificationManager.notify(MessageID, notification);
+ MessageID++;
+
+ }
+
+ /**
+ * Display a toast notification to the user
+ * @param context Context from which to create a notification
+ * @param text The text the toast should display
+ * @param duration The amount of time for the toast to appear to the user
+ */
+ static void toast(Context context, CharSequence text, int duration) {
+ Toast toast = Toast.makeText(context, text, duration);
+ toast.show();
+ }
+
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/OpenFileDialog.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/OpenFileDialog.java
new file mode 100644
index 00000000..833a71fd
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service.sample/src/org/eclipse/paho/android/service/sample/OpenFileDialog.java
@@ -0,0 +1,240 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service.sample;
+
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+import android.widget.SimpleAdapter;
+import android.widget.Toast;
+
+/**
+ * Add SSL key file selector
+ * @author foxxiang
+ *
+ */
+public class OpenFileDialog {
+ public static String tag = "OpenFileDialog";
+ static final public String sRoot = "/";
+ static final public String sParent = "..";
+ static final public String sFolder = ".";
+ static final public String sEmpty = "";
+ static final private String sOnErrorMsg = "No rights to access!";
+
+ /**
+ * Create a File Selector Dialog windows
+ * @param id Dialog Id
+ * @param context Context that the application is running in
+ * @param title The tile of File Selector Window
+ * @param callback A callback Bundle interface for data transport
+ * @param suffix The file name suffix. E.g. .bks , .pem
+ * @param images The resource id for file icon
+ * @return The Dialog Window
+ */
+ public static Dialog createDialog(int id, Context context, String title, CallbackBundle callback, String suffix, Map images){
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setView(new FileSelectView(context, id, callback, suffix, images));
+ Dialog dialog = builder.create();
+ //dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ dialog.setTitle(title);
+ return dialog;
+ }
+
+ /** The FileSelect View with OnItemClick Listener*/
+ static class FileSelectView extends ListView implements OnItemClickListener{
+
+ private CallbackBundle callback = null;
+ private String path = sRoot;
+ private List
+ *
+ * @param options
+ * a set of connection parameters that override the defaults.
+ * @param userContext
+ * optional object for used to pass context to the callback. Use
+ * null if not required.
+ * @param callback
+ * optional listener that will be notified when the connect
+ * completes. Use null if not required.
+ * @return token used to track and wait for the connect to complete. The
+ * token will be passed to any callback that has been set.
+ * @throws MqttException
+ * for any connected problems, including communication errors
+ */
+
+ @Override
+ public IMqttToken connect(MqttConnectOptions options, Object userContext,
+ IMqttActionListener callback) throws MqttException {
+
+ IMqttToken token = new MqttTokenAndroid(this, userContext,
+ callback);
+
+ connectOptions = options;
+ connectToken = token;
+
+ /*
+ * The actual connection depends on the service, which we start and bind
+ * to here, but which we can't actually use until the serviceConnection
+ * onServiceConnected() method has run (asynchronously), so the
+ * connection itself takes place in the onServiceConnected() method
+ */
+ if (mqttService == null) { // First time - must bind to the service
+ Intent serviceStartIntent = new Intent();
+ serviceStartIntent.setClassName(myContext, SERVICE_NAME);
+ Object service = myContext.startService(serviceStartIntent);
+ if (service == null) {
+ IMqttActionListener listener = token.getActionCallback();
+ if (listener != null) {
+ listener.onFailure(token, new RuntimeException(
+ "cannot start service " + SERVICE_NAME));
+ }
+ }
+
+ // We bind with BIND_SERVICE_FLAG (0), leaving us the manage the lifecycle
+ // until the last time it is stopped by a call to stopService()
+ myContext.startService(serviceStartIntent);
+ myContext.bindService(serviceStartIntent, serviceConnection,
+ Context.BIND_AUTO_CREATE);
+
+ if (!registerReceiver) registerReceiver(this);
+ }
+ else {
+ pool.execute(new Runnable() {
+
+ @Override
+ public void run() {
+ doConnect();
+
+ //Register receiver to show shoulder tap.
+ if (!registerReceiver) registerReceiver(MqttAndroidClient.this);
+ }
+
+ });
+ }
+
+ return token;
+ }
+
+ private void registerReceiver(BroadcastReceiver receiver) {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(MqttServiceConstants.CALLBACK_TO_ACTIVITY);
+ LocalBroadcastManager.getInstance(myContext).registerReceiver(receiver, filter);
+ registerReceiver = true;
+ }
+
+ /**
+ * Actually do the mqtt connect operation
+ */
+ private void doConnect() {
+ if (clientHandle == null) {
+ clientHandle = mqttService.getClient(serverURI, clientId,myContext.getApplicationInfo().packageName,
+ persistence, authFailHandler);
+ }
+ mqttService.setTraceEnabled(traceEnabled);
+ mqttService.setTraceCallbackId(clientHandle);
+
+ String activityToken = storeToken(connectToken);
+ try {
+ mqttService.connect(clientHandle, connectOptions, null,
+ activityToken);
+ }
+ catch (MqttException e) {
+ IMqttActionListener listener = connectToken.getActionCallback();
+ if (listener != null) {
+ listener.onFailure(connectToken, e);
+ }
+ }
+ }
+
+ /**
+ * Disconnects from the server.
+ *
+ * An attempt is made to quiesce the client allowing outstanding work to
+ * complete before disconnecting. It will wait for a maximum of 30 seconds
+ * for work to quiesce before disconnecting. This method must not be called
+ * from inside {@link MqttCallback} methods.
+ *
+ *
+ * @return token used to track and wait for disconnect to complete. The
+ * token will be passed to any callback that has been set.
+ * @throws MqttException
+ * for problems encountered while disconnecting
+ * @see #disconnect(long, Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken disconnect() throws MqttException {
+ IMqttToken token = new MqttTokenAndroid(this, null,
+ (IMqttActionListener) null);
+ String activityToken = storeToken(token);
+ mqttService.disconnect(clientHandle, null, activityToken);
+ return token;
+ }
+
+ /**
+ * Disconnects from the server.
+ *
+ * An attempt is made to quiesce the client allowing outstanding work to
+ * complete before disconnecting. It will wait for a maximum of the
+ * specified quiesce time for work to complete before disconnecting. This
+ * method must not be called from inside {@link MqttCallback} methods.
+ *
+ *
+ * @param quiesceTimeout
+ * the amount of time in milliseconds to allow for existing work
+ * to finish before disconnecting. A value of zero or less means
+ * the client will not quiesce.
+ * @return token used to track and wait for disconnect to complete. The
+ * token will be passed to the callback methods if a callback is
+ * set.
+ * @throws MqttException
+ * for problems encountered while disconnecting
+ * @see #disconnect(long, Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken disconnect(long quiesceTimeout) throws MqttException {
+ IMqttToken token = new MqttTokenAndroid(this, null,
+ (IMqttActionListener) null);
+ String activityToken = storeToken(token);
+ mqttService.disconnect(clientHandle, quiesceTimeout, null,
+ activityToken);
+ return token;
+ }
+
+ /**
+ * Disconnects from the server.
+ *
+ * An attempt is made to quiesce the client allowing outstanding work to
+ * complete before disconnecting. It will wait for a maximum of 30 seconds
+ * for work to quiesce before disconnecting. This method must not be called
+ * from inside {@link MqttCallback} methods.
+ *
+ *
+ * @param userContext
+ * optional object used to pass context to the callback. Use null
+ * if not required.
+ * @param callback
+ * optional listener that will be notified when the disconnect
+ * completes. Use null if not required.
+ * @return token used to track and wait for the disconnect to complete. The
+ * token will be passed to any callback that has been set.
+ * @throws MqttException
+ * for problems encountered while disconnecting
+ * @see #disconnect(long, Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken disconnect(Object userContext,
+ IMqttActionListener callback) throws MqttException {
+ IMqttToken token = new MqttTokenAndroid(this, userContext,
+ callback);
+ String activityToken = storeToken(token);
+ mqttService.disconnect(clientHandle, null, activityToken);
+ return token;
+ }
+
+ /**
+ * Disconnects from the server.
+ *
+ * The client will wait for {@link MqttCallback} methods to complete. It
+ * will then wait for up to the quiesce timeout to allow for work which has
+ * already been initiated to complete. For instance when a QoS 2 message has
+ * started flowing to the server but the QoS 2 flow has not completed.It
+ * prevents new messages being accepted and does not send any messages that
+ * have been accepted but not yet started delivery across the network to the
+ * server. When work has completed or after the quiesce timeout, the client
+ * will disconnect from the server. If the cleanSession flag was set to
+ * false and next time it is also set to false in the connection, the
+ * messages made in QoS 1 or 2 which were not previously delivered will be
+ * delivered this time.
+ *
+ *
+ * This method must not be called from inside {@link MqttCallback} methods.
+ *
+ *
+ * The method returns control before the disconnect completes. Completion
+ * can be tracked by:
+ *
+ *
Waiting on the returned token {@link IMqttToken#waitForCompletion()}
+ * or
+ *
Passing in a callback {@link IMqttActionListener}
+ *
+ *
+ *
+ * @param quiesceTimeout
+ * the amount of time in milliseconds to allow for existing work
+ * to finish before disconnecting. A value of zero or less means
+ * the client will not quiesce.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null
+ * if not required.
+ * @param callback
+ * optional listener that will be notified when the disconnect
+ * completes. Use null if not required.
+ * @return token used to track and wait for the disconnect to complete. The
+ * token will be passed to any callback that has been set.
+ * @throws MqttException
+ * for problems encountered while disconnecting
+ */
+ @Override
+ public IMqttToken disconnect(long quiesceTimeout, Object userContext,
+ IMqttActionListener callback) throws MqttException {
+ IMqttToken token = new MqttTokenAndroid(this, userContext,
+ callback);
+ String activityToken = storeToken(token);
+ mqttService.disconnect(clientHandle, quiesceTimeout, null,
+ activityToken);
+ return token;
+ }
+
+ /**
+ * Publishes a message to a topic on the server.
+ *
+ * A convenience method, which will create a new {@link MqttMessage} object
+ * with a byte array payload and the specified QoS, and then publish it.
+ *
+ *
+ * @param topic
+ * to deliver the message to, for example "finance/stock/ibm".
+ * @param payload
+ * the byte array to use as the payload
+ * @param qos
+ * the Quality of Service to deliver the message at. Valid values
+ * are 0, 1 or 2.
+ * @param retained
+ * whether or not this message should be retained by the server.
+ * @return token used to track and wait for the publish to complete. The
+ * token will be passed to any callback that has been set.
+ * @throws MqttPersistenceException
+ * when a problem occurs storing the message
+ * @throws IllegalArgumentException
+ * if value of QoS is not 0, 1 or 2.
+ * @throws MqttException
+ * for other errors encountered while publishing the message.
+ * For instance, too many messages are being processed.
+ * @see #publish(String, MqttMessage, Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttDeliveryToken publish(String topic, byte[] payload, int qos,
+ boolean retained) throws MqttException, MqttPersistenceException {
+ return publish(topic, payload, qos, retained, null, null);
+ }
+
+ /**
+ * Publishes a message to a topic on the server. Takes an
+ * {@link MqttMessage} message and delivers it to the server at the
+ * requested quality of service.
+ *
+ * @param topic
+ * to deliver the message to, for example "finance/stock/ibm".
+ * @param message
+ * to deliver to the server
+ * @return token used to track and wait for the publish to complete. The
+ * token will be passed to any callback that has been set.
+ * @throws MqttPersistenceException
+ * when a problem occurs storing the message
+ * @throws IllegalArgumentException
+ * if value of QoS is not 0, 1 or 2.
+ * @throws MqttException
+ * for other errors encountered while publishing the message.
+ * For instance client not connected.
+ * @see #publish(String, MqttMessage, Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttDeliveryToken publish(String topic, MqttMessage message)
+ throws MqttException, MqttPersistenceException {
+ return publish(topic, message, null, null);
+ }
+
+ /**
+ * Publishes a message to a topic on the server.
+ *
+ * A convenience method, which will create a new {@link MqttMessage} object
+ * with a byte array payload, the specified QoS and retained, then publish it.
+ *
+ *
+ * @param topic
+ * to deliver the message to, for example "finance/stock/ibm".
+ * @param payload
+ * the byte array to use as the payload
+ * @param qos
+ * the Quality of Service to deliver the message at. Valid values
+ * are 0, 1 or 2.
+ * @param retained
+ * whether or not this message should be retained by the server.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null
+ * if not required.
+ * @param callback
+ * optional listener that will be notified when message delivery
+ * has completed to the requested quality of service
+ * @return token used to track and wait for the publish to complete. The
+ * token will be passed to any callback that has been set.
+ * @throws MqttPersistenceException
+ * when a problem occurs storing the message
+ * @throws IllegalArgumentException
+ * if value of QoS is not 0, 1 or 2.
+ * @throws MqttException
+ * for other errors encountered while publishing the message.
+ * For instance client not connected.
+ * @see #publish(String, MqttMessage, Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttDeliveryToken publish(String topic, byte[] payload, int qos,
+ boolean retained, Object userContext, IMqttActionListener callback)
+ throws MqttException, MqttPersistenceException {
+
+ MqttMessage message = new MqttMessage(payload);
+ message.setQos(qos);
+ message.setRetained(retained);
+ MqttDeliveryTokenAndroid token = new MqttDeliveryTokenAndroid(
+ this, userContext, callback, message);
+ String activityToken = storeToken(token);
+ IMqttDeliveryToken internalToken = mqttService.publish(clientHandle,
+ topic, payload, qos, retained, null, activityToken);
+ token.setDelegate(internalToken);
+ return token;
+ }
+
+ /**
+ * Publishes a message to a topic on the server.
+ *
+ * Once this method has returned cleanly, the message has been accepted for
+ * publication by the client and will be delivered on a background thread.
+ * In the event the connection fails or the client stops, Messages will be
+ * delivered to the requested quality of service once the connection is
+ * re-established to the server on condition that:
+ *
+ *
The connection is re-established with the same clientID
+ *
The original connection was made with (@link
+ * MqttConnectOptions#setCleanSession(boolean)} set to false
+ *
The connection is re-established with (@link
+ * MqttConnectOptions#setCleanSession(boolean)} set to false
+ *
Depending when the failure occurs QoS 0 messages may not be
+ * delivered.
+ *
+ *
+ *
+ *
+ * When building an application, the design of the topic tree should take
+ * into account the following principles of topic name syntax and semantics:
+ *
+ *
+ *
+ *
A topic must be at least one character long.
+ *
Topic names are case sensitive. For example, ACCOUNTS and
+ * Accounts are two different topics.
+ *
Topic names can include the space character. For example,
+ * Accounts
+ * payable is a valid topic.
+ *
A leading "/" creates a distinct topic. For example,
+ * /finance is different from finance. /finance
+ * matches "+/+" and "/+", but not "+".
+ *
Do not include the null character (Unicode \x0000) in any topic.
+ *
+ *
+ *
+ * The following principles apply to the construction and content of a topic
+ * tree:
+ *
+ *
+ *
+ *
The length is limited to 64k but within that there are no limits to
+ * the number of levels in a topic tree.
+ *
There can be any number of root nodes; that is, there can be any
+ * number of topic trees.
+ *
+ *
+ *
+ * The method returns control before the publish completes. Completion can
+ * be tracked by:
+ *
+ *
Setting an {@link IMqttAsyncClient#setCallback(MqttCallback)} where
+ * the {@link MqttCallback#deliveryComplete(IMqttDeliveryToken)} method will
+ * be called.
pu
+ *
Waiting on the returned token {@link MqttToken#waitForCompletion()}
+ * or
+ *
Passing in a callback {@link IMqttActionListener} to this method
+ *
+ *
+ *
+ * @param topic
+ * to deliver the message to, for example "finance/stock/ibm".
+ * @param message
+ * to deliver to the server
+ * @param userContext
+ * optional object used to pass context to the callback. Use null
+ * if not required.
+ * @param callback
+ * optional listener that will be notified when message delivery
+ * has completed to the requested quality of service
+ * @return token used to track and wait for the publish to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttPersistenceException
+ * when a problem occurs storing the message
+ * @throws IllegalArgumentException
+ * if value of QoS is not 0, 1 or 2.
+ * @throws MqttException
+ * for other errors encountered while publishing the message.
+ * For instance, client not connected.
+ * @see MqttMessage
+ */
+ @Override
+ public IMqttDeliveryToken publish(String topic, MqttMessage message,
+ Object userContext, IMqttActionListener callback)
+ throws MqttException, MqttPersistenceException {
+ MqttDeliveryTokenAndroid token = new MqttDeliveryTokenAndroid(
+ this, userContext, callback, message);
+ String activityToken = storeToken(token);
+ IMqttDeliveryToken internalToken = mqttService.publish(clientHandle,
+ topic, message, null, activityToken);
+ token.setDelegate(internalToken);
+ return token;
+ }
+
+ /**
+ * Subscribe to a topic, which may include wildcards.
+ *
+ * @param topic
+ * the topic to subscribe to, which can include wildcards.
+ * @param qos
+ * the maximum quality of service at which to subscribe. Messages
+ * published at a lower quality of service will be received at
+ * the published QoS. Messages published at a higher quality of
+ * service will be received using the QoS specified on the
+ * subscription.
+ * @return token used to track and wait for the subscribe to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttSecurityException
+ * for security related problems
+ * @throws MqttException
+ * for non security related problems
+ *
+ * @see #subscribe(String[], int[], Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken subscribe(String topic, int qos) throws MqttException,
+ MqttSecurityException {
+ return subscribe(topic, qos, null, null);
+ }
+
+ /**
+ * Subscribe to multiple topics, each topic may include wildcards.
+ *
+ *
+ * Provides an optimized way to subscribe to multiple topics compared to
+ * subscribing to each one individually.
+ *
+ *
+ * @param topic
+ * one or more topics to subscribe to, which can include
+ * wildcards
+ * @param qos
+ * the maximum quality of service at which to subscribe. Messages
+ * published at a lower quality of service will be received at
+ * the published QoS. Messages published at a higher quality of
+ * service will be received using the QoS specified on the
+ * subscription.
+ * @return token used to track and wait for the subscription to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttSecurityException
+ * for security related problems
+ * @throws MqttException
+ * for non security related problems
+ *
+ * @see #subscribe(String[], int[], Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken subscribe(String[] topic, int[] qos)
+ throws MqttException, MqttSecurityException {
+ return subscribe(topic, qos, null, null);
+ }
+
+ /**
+ * Subscribe to a topic, which may include wildcards.
+ *
+ * @param topic
+ * the topic to subscribe to, which can include wildcards.
+ * @param qos
+ * the maximum quality of service at which to subscribe. Messages
+ * published at a lower quality of service will be received at
+ * the published QoS. Messages published at a higher quality of
+ * service will be received using the QoS specified on the
+ * subscription.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null
+ * if not required.
+ * @param callback
+ * optional listener that will be notified when subscribe has
+ * completed
+ * @return token used to track and wait for the subscribe to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error when registering the subscription.
+ *
+ * @see #subscribe(String[], int[], Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken subscribe(String topic, int qos, Object userContext,
+ IMqttActionListener callback) throws MqttException {
+ IMqttToken token = new MqttTokenAndroid(this, userContext,
+ callback, new String[]{topic});
+ String activityToken = storeToken(token);
+ mqttService.subscribe(clientHandle, topic, qos, null, activityToken);
+ return token;
+ }
+
+ /**
+ * Subscribes to multiple topics, each topic may include wildcards.
+ *
+ * Provides an optimized way to subscribe to multiple topics compared to
+ * subscribing to each one individually.
+ *
+ *
+ * The {@link #setCallback(MqttCallback)} method should be called before
+ * this method, otherwise any received messages will be discarded.
+ *
+ *
+ * If (@link MqttConnectOptions#setCleanSession(boolean)} was set to true,
+ * when connecting to the server, the subscription remains in place until
+ * either:
+ *
+ *
The client disconnects
+ *
An unsubscribe method is called to unsubscribe the topic
+ *
+ *
+ * If (@link MqttConnectOptions#setCleanSession(boolean)} was set to false,
+ * when connecting to the server, the subscription remains in place
+ * until either:
+ *
+ *
An unsubscribe method is called to unsubscribe the topic
+ *
The next time the client connects with cleanSession set to true
+ *
+ * With cleanSession set to false the MQTT server will store messages
+ * on behalf of the client when the client is not connected. The next time
+ * the client connects with the same client ID the server will
+ * deliver the stored messages to the client.
+ *
+ *
+ *
+ * The "topic filter" string is used when subscription may contain special
+ * characters, which allows you to subscribe to multiple topics at once.
+ *
+ *
+ *
+ *
Topic level separator
+ *
The forward slash (/) is used to separate each level within a topic
+ * tree and provide a hierarchical structure to the topic space. The use of
+ * the topic level separator is significant when the two wildcard characters
+ * are encountered in topics specified by subscribers.
+ *
+ *
Multi-level wildcard
+ *
+ *
+ * The number sign (#) is a wildcard character that matches any number of
+ * levels within a topic. For example, if you subscribe to finance/stock/ibm/#, you receive messages
+ * on these topics:
+ *
+ *
+ * The multi-level wildcard can represent zero or more levels. Therefore,
+ * finance/# can also match the singular finance, where
+ * # represents zero levels. The topic level separator is
+ * meaningless in this context, because there are no levels to separate.
+ *
+ *
+ *
+ * The multi-level wildcard can be specified only on its own or
+ * next to the topic level separator character. Therefore, # and
+ * finance/# are both valid, but finance# is not valid.
+ * The multi-level wildcard must be the last character used within the
+ * topic tree. For example, finance/# is valid but
+ * finance/#/closingprice is not valid.
+ *
+ *
+ *
+ *
Single-level wildcard
+ *
+ *
+ * The plus sign (+) is a wildcard character that matches only one topic
+ * level. For example, finance/stock/+ matches
+ * finance/stock/ibm and finance/stock/xyz, but not
+ * finance/stock/ibm/closingprice. Also, because the single-level
+ * wildcard matches only a single level, finance/+ does not match
+ * finance.
+ *
+ *
+ *
+ * Use the single-level wildcard at any level in the topic tree, and in
+ * conjunction with the multilevel wildcard. Specify the single-level
+ * wildcard next to the topic level separator, except when it is specified
+ * on its own. Therefore, + and finance/+ are both valid,
+ * but finance+ is not valid. The single-level wildcard can
+ * be used at the end of the topic tree or within the topic tree. For
+ * example, finance/+ and finance/+/ibm are both
+ * valid.
+ *
+ *
+ *
+ *
+ *
+ * The method returns control before the subscribe completes. Completion can
+ * be tracked by:
+ *
+ *
Waiting on the supplied token {@link MqttToken#waitForCompletion()}
+ * or
+ *
Passing in a callback {@link IMqttActionListener} to this method
+ *
+ *
+ *
+ * @param topic
+ * one or more topics to subscribe to, which can include
+ * wildcards
+ * @param qos
+ * the maximum quality of service to subscribe each topic
+ * at.Messages published at a lower quality of service will be
+ * received at the published QoS. Messages published at a higher
+ * quality of service will be received using the QoS specified on
+ * the subscription.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null
+ * if not required.
+ * @param callback
+ * optional listener that will be notified when subscribe has
+ * completed
+ * @return token used to track and wait for the subscribe to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error registering the subscription.
+ * @throws IllegalArgumentException
+ * if the two supplied arrays are not the same size.
+ */
+ @Override
+ public IMqttToken subscribe(String[] topic, int[] qos, Object userContext,
+ IMqttActionListener callback) throws MqttException {
+ IMqttToken token = new MqttTokenAndroid(this, userContext,
+ callback, topic);
+ String activityToken = storeToken(token);
+ mqttService.subscribe(clientHandle, topic, qos, null, activityToken);
+ return token;
+ }
+
+ /**
+ * Requests the server unsubscribe the client from a topic.
+ *
+ * @param topic
+ * the topic to unsubscribe from. It must match a topic specified
+ * on an earlier subscribe.
+ * @return token used to track and wait for the unsubscribe to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error unregistering the subscription.
+ *
+ * @see #unsubscribe(String[], Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken unsubscribe(String topic) throws MqttException {
+ return unsubscribe(topic, null, null);
+ }
+
+ /**
+ * Requests the server to unsubscribe the client from one or more topics.
+ *
+ * @param topic
+ * one or more topics to unsubscribe from. Each topic must match
+ * one specified on an earlier subscription.
+ * @return token used to track and wait for the unsubscribe to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error unregistering the subscription.
+ *
+ * @see #unsubscribe(String[], Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken unsubscribe(String[] topic) throws MqttException {
+ return unsubscribe(topic, null, null);
+ }
+
+ /**
+ * Requests the server to unsubscribe the client from a topics.
+ *
+ * @param topic
+ * the topic to unsubscribe from. It must match a topic specified
+ * on an earlier subscribe.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null
+ * if not required.
+ * @param callback
+ * optional listener that will be notified when unsubscribe has
+ * completed
+ * @return token used to track and wait for the unsubscribe to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error unregistering the subscription.
+ *
+ * @see #unsubscribe(String[], Object, IMqttActionListener)
+ */
+ @Override
+ public IMqttToken unsubscribe(String topic, Object userContext,
+ IMqttActionListener callback) throws MqttException {
+ IMqttToken token = new MqttTokenAndroid(this, userContext,
+ callback);
+ String activityToken = storeToken(token);
+ mqttService.unsubscribe(clientHandle, topic, null, activityToken);
+ return token;
+ }
+
+ /**
+ * Requests the server to unsubscribe the client from one or more topics.
+ *
+ * Unsubcribing is the opposite of subscribing. When the server receives the
+ * unsubscribe request it looks to see if it can find a matching
+ * subscription for the client and then removes it. After this point the
+ * server will send no more messages to the client for this subscription.
+ *
+ *
+ * The topic(s) specified on the unsubscribe must match the topic(s)
+ * specified in the original subscribe request for the unsubscribe to
+ * succeed
+ *
+ *
+ * The method returns control before the unsubscribe completes. Completion
+ * can be tracked by:
+ *
+ *
Waiting on the returned token {@link MqttToken#waitForCompletion()}
+ * or
+ *
Passing in a callback {@link IMqttActionListener} to this method
+ *
+ *
+ *
+ * @param topic
+ * one or more topics to unsubscribe from. Each topic must match
+ * one specified on an earlier subscription.
+ * @param userContext
+ * optional object used to pass context to the callback. Use null
+ * if not required.
+ * @param callback
+ * optional listener that will be notified when unsubscribe has
+ * completed
+ * @return token used to track and wait for the unsubscribe to complete. The
+ * token will be passed to callback methods if set.
+ * @throws MqttException
+ * if there was an error unregistering the subscription.
+ */
+ @Override
+ public IMqttToken unsubscribe(String[] topic, Object userContext,
+ IMqttActionListener callback) throws MqttException {
+ IMqttToken token = new MqttTokenAndroid(this, userContext,
+ callback);
+ String activityToken = storeToken(token);
+ mqttService.unsubscribe(clientHandle, topic, null, activityToken);
+ return token;
+ }
+
+ /**
+ * Returns the delivery tokens for any outstanding publish operations.
+ *
+ * If a client has been restarted and there are messages that were in the
+ * process of being delivered when the client stopped, this method returns a
+ * token for each in-flight message to enable the delivery to be tracked.
+ * Alternately the {@link MqttCallback#deliveryComplete(IMqttDeliveryToken)}
+ * callback can be used to track the delivery of outstanding messages.
+ *
+ *
+ * If a client connects with cleanSession true then there will be no
+ * delivery tokens as the cleanSession option deletes all earlier state. For
+ * state to be remembered the client must connect with cleanSession set to
+ * false
+ *
+ *
+ * @return zero or more delivery tokens
+ */
+ @Override
+ public IMqttDeliveryToken[] getPendingDeliveryTokens() {
+ return mqttService.getPendingDeliveryTokens(clientHandle);
+ }
+
+ /**
+ * Sets a callback listener to use for events that happen asynchronously.
+ *
+ * There are a number of events that the listener will be notified about.
+ * These include:
+ *
+ *
A new message has arrived and is ready to be processed
+ *
The connection to the server has been lost
+ *
Delivery of a message to the server has completed
+ *
+ *
+ *
+ * Other events that track the progress of an individual operation such as
+ * connect and subscribe can be tracked using the {@link MqttToken} returned
+ * from each non-blocking method or using setting a
+ * {@link IMqttActionListener} on the non-blocking method.
+ *
+ *
+ * @param callback
+ * which will be invoked for certain asynchronous events
+ *
+ * @see MqttCallback
+ */
+ @Override
+ public void setCallback(MqttCallback callback) {
+ this.callback = callback;
+
+ }
+
+ /**
+ * identify the callback to be invoked when making tracing calls back into
+ * the Activity
+ *
+ * @param traceCallback handler
+ */
+ public void setTraceCallback(MqttTraceHandler traceCallback) {
+ this.traceCallback = traceCallback;
+ // mqttService.setTraceCallbackId(traceCallbackId);
+ }
+
+ /**
+ * turn tracing on and off
+ *
+ * @param traceEnabled
+ * set true to enable trace, otherwise, set
+ * false to disable trace
+ *
+ */
+ public void setTraceEnabled(boolean traceEnabled) {
+ this.traceEnabled = traceEnabled;
+ if (mqttService !=null)
+ mqttService.setTraceEnabled(traceEnabled);
+ }
+
+ /**
+ *
+ * Process incoming Intent objects representing the results of operations
+ * and asynchronous activities such as message received
+ *
+ *
+ * Note: This is only a public method because the Android
+ * APIs require such.
+ * This method should not be explicitly invoked.
+ *
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Bundle data = intent.getExtras();
+
+ String handleFromIntent = data
+ .getString(MqttServiceConstants.CALLBACK_CLIENT_HANDLE);
+
+ if ((handleFromIntent == null)
+ || (!handleFromIntent.equals(clientHandle))) {
+ return;
+ }
+
+ actionExecutor.execute(new ActionReceiveTask(data));
+ }
+
+ private class ActionReceiveTask implements Runnable {
+ private Bundle data;
+
+ public ActionReceiveTask(Bundle data) {
+ this.data = data;
+ }
+
+ @Override
+ public void run() {
+ String action = data.getString(MqttServiceConstants.CALLBACK_ACTION);
+
+ if (MqttServiceConstants.CONNECT_ACTION.equals(action)) {
+ connectAction(data);
+ }
+ else if (MqttServiceConstants.MESSAGE_ARRIVED_ACTION.equals(action)) {
+ messageArrivedAction(data);
+ }
+ else if (MqttServiceConstants.SUBSCRIBE_ACTION.equals(action)) {
+ subscribeAction(data);
+ }
+ else if (MqttServiceConstants.UNSUBSCRIBE_ACTION.equals(action)) {
+ unSubscribeAction(data);
+ }
+ else if (MqttServiceConstants.SEND_ACTION.equals(action)) {
+ sendAction(data);
+ }
+ else if (MqttServiceConstants.MESSAGE_DELIVERED_ACTION.equals(action)) {
+ messageDeliveredAction(data);
+ }
+ else if (MqttServiceConstants.ON_CONNECTION_LOST_ACTION
+ .equals(action)) {
+ connectionLostAction(data);
+ }
+ else if (MqttServiceConstants.DISCONNECT_ACTION.equals(action)) {
+ disconnected(data);
+ }
+ else if (MqttServiceConstants.TRACE_ACTION.equals(action)) {
+ traceAction(data);
+ }else{
+ mqttService.traceError(MqttService.TAG, "Callback action doesn't exist.");
+ }
+ }
+ }
+
+ /**
+ * Acknowledges a message received on the
+ * {@link MqttCallback#messageArrived(String, MqttMessage)}
+ *
+ * @param messageId
+ * the messageId received from the MqttMessage (To access this
+ * field you need to cast {@link MqttMessage} to
+ * {@link ParcelableMqttMessage})
+ * @return whether or not the message was successfully acknowledged
+ */
+ public boolean acknowledgeMessage(String messageId) {
+ if (messageAck == Ack.MANUAL_ACK) {
+ Status status = mqttService.acknowledgeMessageArrival(clientHandle, messageId);
+ return status == Status.OK;
+ }
+ return false;
+
+ }
+
+ /**
+ * Process the results of a connection
+ *
+ * @param data
+ */
+ private void connectAction(Bundle data) {
+ IMqttToken token = connectToken;
+ removeMqttToken(data);
+
+ simpleAction(token, data);
+ }
+
+ /**
+ * Process a notification that we have disconnected
+ *
+ * @param data
+ */
+ private void disconnected(Bundle data) {
+ clientHandle = null; // avoid reuse!
+ IMqttToken token = removeMqttToken(data);
+ if (token != null) {
+ ((MqttTokenAndroid) token).notifyComplete();
+ }
+ if (callback != null) {
+ callback.connectionLost(null);
+ }
+ }
+
+ /**
+ * Process a Connection Lost notification
+ *
+ * @param data
+ */
+ private void connectionLostAction(Bundle data) {
+ if (callback != null) {
+ Exception reason = (Exception) data
+ .getSerializable(MqttServiceConstants.CALLBACK_EXCEPTION);
+ callback.connectionLost(reason);
+ }
+ }
+
+ /**
+ * Common processing for many notifications
+ *
+ * @param token
+ * the token associated with the action being undertake
+ * @param data
+ * the result data
+ */
+ private void simpleAction(IMqttToken token, Bundle data) {
+ if (token != null) {
+ Status status = (Status) data
+ .getSerializable(MqttServiceConstants.CALLBACK_STATUS);
+ if (status == Status.OK) {
+ ((MqttTokenAndroid) token).notifyComplete();
+ }
+ else {
+ Exception exceptionThrown = (Exception) data.getSerializable(MqttServiceConstants.CALLBACK_EXCEPTION);
+ ((MqttTokenAndroid) token).notifyFailure(exceptionThrown);
+ }
+ } else {
+ mqttService.traceError(MqttService.TAG, "simpleAction : token is null");
+ }
+ }
+
+ /**
+ * Process notification of a publish(send) operation
+ *
+ * @param data
+ */
+ private void sendAction(Bundle data) {
+ IMqttToken token = getMqttToken(data); // get, don't remove - will
+ // remove on delivery
+ simpleAction(token, data);
+ }
+
+ /**
+ * Process notification of a subscribe operation
+ *
+ * @param data
+ */
+ private void subscribeAction(Bundle data) {
+ IMqttToken token = removeMqttToken(data);
+ simpleAction(token, data);
+ }
+
+ /**
+ * Process notification of an unsubscribe operation
+ *
+ * @param data
+ */
+ private void unSubscribeAction(Bundle data) {
+ IMqttToken token = removeMqttToken(data);
+ simpleAction(token, data);
+ }
+
+ /**
+ * Process notification of a published message having been delivered
+ *
+ * @param data
+ */
+ private void messageDeliveredAction(Bundle data) {
+ IMqttToken token = removeMqttToken(data);
+ if (token != null) {
+ if (callback != null) {
+ Status status = (Status) data
+ .getSerializable(MqttServiceConstants.CALLBACK_STATUS);
+ if (status == Status.OK && token instanceof IMqttDeliveryToken) {
+ callback.deliveryComplete((IMqttDeliveryToken) token);
+ }
+ }
+ }
+ }
+
+ /**
+ * Process notification of a message's arrival
+ *
+ * @param data
+ */
+ private void messageArrivedAction(Bundle data) {
+ if (callback != null) {
+ String messageId = data
+ .getString(MqttServiceConstants.CALLBACK_MESSAGE_ID);
+ String destinationName = data
+ .getString(MqttServiceConstants.CALLBACK_DESTINATION_NAME);
+
+ ParcelableMqttMessage message = (ParcelableMqttMessage) data
+ .getParcelable(MqttServiceConstants.CALLBACK_MESSAGE_PARCEL);
+ try {
+ if (messageAck == Ack.AUTO_ACK) {
+ callback.messageArrived(destinationName, message);
+ mqttService.acknowledgeMessageArrival(clientHandle, messageId);
+ }
+ else {
+ message.messageId = messageId;
+ callback.messageArrived(destinationName, message);
+ }
+
+ // let the service discard the saved message details
+ }
+ catch (Exception e) {
+ // Swallow the exception
+ }
+ }
+ }
+
+ /**
+ * Process trace action - pass trace data back to the callback
+ *
+ * @param data
+ */
+ private void traceAction(Bundle data) {
+
+ if (traceCallback != null) {
+ String severity = data.getString(MqttServiceConstants.CALLBACK_TRACE_SEVERITY);
+ String message = data.getString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE);
+ String tag = data.getString(MqttServiceConstants.CALLBACK_TRACE_TAG);
+ if (MqttServiceConstants.TRACE_DEBUG.equals(severity))
+ traceCallback.traceDebug(tag, message);
+ else if (MqttServiceConstants.TRACE_ERROR.equals(severity))
+ traceCallback.traceError(tag, message);
+ else
+ {
+ Exception e = (Exception) data.getSerializable(MqttServiceConstants.CALLBACK_EXCEPTION);
+ traceCallback.traceException(tag, message, e);
+ }
+ }
+ }
+
+ /**
+ * @param token
+ * identifying an operation
+ * @return an identifier for the token which can be passed to the Android
+ * Service
+ */
+ private synchronized String storeToken(IMqttToken token) {
+ tokenMap.put(tokenNumber, token);
+ return Integer.toString(tokenNumber++);
+ }
+
+ /**
+ * Get a token identified by a string, and remove it from our map
+ *
+ * @param data
+ * @return the token
+ */
+ private synchronized IMqttToken removeMqttToken(Bundle data) {
+
+ String activityToken = data.getString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN);
+ if (activityToken!=null){
+ int tokenNumber = Integer.parseInt(activityToken);
+ IMqttToken token = tokenMap.get(tokenNumber);
+ tokenMap.delete(tokenNumber);
+ return token;
+ }
+ return null;
+ }
+
+ /**
+ * Get a token identified by a string, and remove it from our map
+ *
+ * @param data
+ * @return the token
+ */
+ private synchronized IMqttToken getMqttToken(Bundle data) {
+ String activityToken = data
+ .getString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN);
+ IMqttToken token = tokenMap.get(Integer.parseInt(activityToken));
+ return token;
+ }
+
+ /**
+ * Get the SSLSocketFactory using SSL key store and password
+ *
+ * A convenience method, which will help user to create a SSLSocketFactory
+ * object
+ *
+ *
+ * @param keyStore
+ * the SSL key store which is generated by some SSL key tool,
+ * such as keytool in Java JDK
+ * @param password
+ * the password of the key store which is set when the key store
+ * is generated
+ * @return SSLSocketFactory used to connect to the server with SSL
+ * authentication
+ * @throws MqttSecurityException
+ * if there was any error when getting the SSLSocketFactory
+ */
+ public SSLSocketFactory getSSLSocketFactory (InputStream keyStore, String password) throws MqttSecurityException {
+ try{
+ SSLContext ctx = null;
+ SSLSocketFactory sslSockFactory=null;
+ KeyStore ts;
+ ts = KeyStore.getInstance("BKS");
+ ts.load(keyStore, password.toCharArray());
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
+ tmf.init(ts);
+ TrustManager[] tm = tmf.getTrustManagers();
+ ctx = SSLContext.getInstance("SSL");
+ ctx.init(null, tm, null);
+
+ sslSockFactory=ctx.getSocketFactory();
+ return sslSockFactory;
+
+ } catch (KeyStoreException e) {
+ throw new MqttSecurityException(e);
+ } catch (CertificateException e) {
+ throw new MqttSecurityException(e);
+ } catch (FileNotFoundException e) {
+ throw new MqttSecurityException(e);
+ } catch (IOException e) {
+ throw new MqttSecurityException(e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new MqttSecurityException(e);
+ } catch (KeyManagementException e) {
+ throw new MqttSecurityException(e);
+ }
+ }
+
+ @Override
+ public void disconnectForcibly() throws MqttException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void disconnectForcibly(long disconnectTimeout) throws MqttException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout)
+ throws MqttException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Unregister receiver which receives intent from MqttService avoids
+ * IntentReceiver leaks.
+ */
+ public void unregisterResources(){
+ if(myContext != null && registerReceiver){
+ synchronized (MqttAndroidClient.this) {
+ LocalBroadcastManager.getInstance(myContext).unregisterReceiver(this);
+ registerReceiver = false;
+ }
+ if(bindedService){
+ try{
+ myContext.unbindService(serviceConnection);
+ bindedService = false;
+ }catch(IllegalArgumentException e){
+ //Ignore unbind issue.
+ }
+ }
+ }
+ }
+
+ /**
+ * Register receiver to receiver intent from MqttService. Call this method
+ * when activity is hidden and become to show again.
+ *
+ * @param context
+ * - Current activity context.
+ */
+ public void registerResources(Context context){
+ if(context != null){
+ this.myContext = context;
+ if(!registerReceiver){
+ registerReceiver(this);
+ }
+ }
+ }
+
+ public void recycleConnection() {
+ if (mqttService != null) {
+ mqttService.recycleConnection();
+ }
+ }
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttAuthenticationFailureHandler.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttAuthenticationFailureHandler.java
new file mode 100644
index 00000000..95a16939
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttAuthenticationFailureHandler.java
@@ -0,0 +1,7 @@
+package org.eclipse.paho.android.service;
+
+public interface MqttAuthenticationFailureHandler {
+ boolean isApplicationAuthenticationFailure(Throwable exception);
+
+ void onApplicationAuthenticationFailure(Throwable exception);
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttConnection.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttConnection.java
new file mode 100644
index 00000000..2775d74a
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttConnection.java
@@ -0,0 +1,1091 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.paho.android.service.MessageStore.StoredMessage;
+import org.eclipse.paho.client.mqttv3.IMqttActionListener;
+import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.IMqttToken;
+import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
+import org.eclipse.paho.client.mqttv3.MqttCallback;
+import org.eclipse.paho.client.mqttv3.MqttClientPersistence;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
+import org.eclipse.paho.client.mqttv3.MqttSecurityException;
+import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence;
+
+import android.app.Service;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.util.Log;
+
+import rx.Observable;
+import rx.Subscriber;
+import rx.functions.Func1;
+
+/**
+ *
+ * MqttConnection holds a MqttAsyncClient {host,port,clientId} instance to perform
+ * MQTT operations to MQTT broker.
+ *
+ *
+ * Most of the major API here is intended to implement the most general forms of
+ * the methods in IMqttAsyncClient, with slight adjustments for the Android
+ * environment
+ * These adjustments usually consist of adding two parameters to each method :-
+ *
+ *
invocationContext - a string passed from the application to identify the
+ * context of the operation (mainly included for support of the javascript API
+ * implementation)
+ *
activityToken - a string passed from the Activity to relate back to a
+ * callback method or other context-specific data
+ *
+ *
+ *
+ * Operations are very much asynchronous, so success and failure are notified by
+ * packing the relevant data into Intent objects which are broadcast back to the
+ * Activity via the MqttService.callbackToActivity() method.
+ *
+ */
+class MqttConnection implements MqttCallback {
+
+ // Strings for Intents etc..
+ private static final String TAG = "MqttConnection";
+ // Error status messages
+ private static final String NOT_CONNECTED = "not connected";
+
+ private static final long RECONNECT_DELAY = 1;
+
+ // fields for the connection definition
+ private String serverURI;
+ public String getServerURI() {
+ return serverURI;
+ }
+
+ public void setServerURI(String serverURI) {
+ this.serverURI = serverURI;
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ private String clientId;
+ private MqttClientPersistence persistence = null;
+ private MqttConnectOptions connectOptions;
+
+ public MqttConnectOptions getConnectOptions() {
+ return connectOptions;
+ }
+
+ public void setConnectOptions(MqttConnectOptions connectOptions) {
+ this.connectOptions = connectOptions;
+ }
+
+ // Client handle, used for callbacks...
+ private String clientHandle;
+
+ public String getClientHandle() {
+ return clientHandle;
+ }
+
+ public void setClientHandle(String clientHandle) {
+ this.clientHandle = clientHandle;
+ }
+
+ //store connect ActivityToken for reconnect
+ private String connectActivityToken = null;
+
+ // our client object - instantiated on connect
+ private MqttAsyncClient myClient = null;
+
+ // our (parent) service object
+ private MqttService service = null;
+
+ private volatile boolean disconnected = true;
+ private boolean cleanSession = true;
+
+ // Indicate this connection is connecting or not.
+ // This variable uses to avoid reconnect multiple times.
+ private volatile boolean isConnecting = false;
+
+ // Saved sent messages and their corresponding Topics, activityTokens and
+ // invocationContexts, so we can handle "deliveryComplete" callbacks
+ // from the mqttClient
+ private Map savedTopics = new HashMap();
+ private Map savedSentMessages = new HashMap();
+ private Map savedActivityTokens = new HashMap();
+ private Map savedInvocationContexts = new HashMap();
+
+ private WakeLock wakelock = null;
+ private String wakeLockTag = null;
+
+ private MqttAuthenticationFailureHandler authFailHandler;
+
+ /**
+ * Constructor - create an MqttConnection to communicate with MQTT server
+ *
+ * @param service
+ * our "parent" service - we make callbacks to it
+ * @param serverURI
+ * the URI of the MQTT server to which we will connect
+ * @param clientId
+ * the name by which we will identify ourselves to the MQTT
+ * server
+ * @param persistence
+ * the persistence class to use to store in-flight message. If
+ * null then the default persistence mechanism is used
+ * @param clientHandle
+ * the "handle" by which the activity will identify us
+ */
+ MqttConnection(MqttService service, String serverURI, String clientId,
+ MqttClientPersistence persistence, String clientHandle,
+ MqttAuthenticationFailureHandler authFailHandler) {
+ this.serverURI = serverURI.toString();
+ this.service = service;
+ this.clientId = clientId;
+ this.persistence = persistence;
+ this.clientHandle = clientHandle;
+ this.authFailHandler = authFailHandler;
+
+ StringBuffer buff = new StringBuffer(this.getClass().getCanonicalName());
+ buff.append(" ");
+ buff.append(clientId);
+ buff.append(" ");
+ buff.append("on host ");
+ buff.append(serverURI);
+ wakeLockTag = buff.toString();
+ }
+
+ // The major API implementation follows
+ /**
+ * Connect to the server specified when we were instantiated
+ *
+ * @param options
+ * timeout, etc
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ public void connect(MqttConnectOptions options, String invocationContext,
+ String activityToken) {
+
+ final String internel_invocationContext = invocationContext;
+ connectOptions = options;
+ connectActivityToken = activityToken;
+
+ if (options != null) {
+ cleanSession = options.isCleanSession();
+ }
+
+ if (connectOptions.isCleanSession()) { // if it's a clean session,
+ // discard old data
+ service.messageStore.clearArrivedMessages(clientHandle);
+ }
+
+ service.traceDebug(TAG, "Connecting {" + serverURI + "} as {" + clientId + "}");
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ activityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
+ invocationContext);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.CONNECT_ACTION);
+
+
+ try {
+ if (persistence == null) {
+ // use internal storage
+ File myDir = service.getDir(TAG, Context.MODE_PRIVATE);
+
+ if (myDir == null) {
+ //Shouldn't happen.
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ "Error! No external and internal storage available");
+ resultBundle.putSerializable(
+ MqttServiceConstants.CALLBACK_EXCEPTION, new MqttPersistenceException());
+ service.callbackToActivity(clientHandle, Status.ERROR,
+ resultBundle);
+ return;
+ }
+
+ // use that to setup MQTT client persistence storage
+ persistence = new MqttDefaultFilePersistence(
+ myDir.getAbsolutePath());
+ }
+
+ IMqttActionListener listener = new MqttConnectionListener(
+ resultBundle) {
+
+ @Override
+ public void onSuccess(IMqttToken asyncActionToken) {
+ doAfterConnectSuccess(resultBundle);
+ service.traceDebug(TAG, "connect success!");
+ }
+
+ @Override
+ public void onFailure(IMqttToken asyncActionToken,
+ Throwable exception) {
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ exception.getLocalizedMessage());
+ resultBundle.putSerializable(
+ MqttServiceConstants.CALLBACK_EXCEPTION, exception);
+ service.traceError(TAG,
+ "connect fail, call connect to reconnect.reason:"
+ + exception.getMessage());
+
+ doAfterConnectFail(resultBundle);
+
+ // if connect fail ,try reconnect.
+ if(service.isOnline()){
+ if(isAuthenticationFailure(exception)) {
+ authFailHandler.onApplicationAuthenticationFailure(exception);
+ } else {
+ connect(connectOptions, internel_invocationContext, connectActivityToken);
+ }
+ }
+
+ }
+ };
+
+ if (myClient != null) {
+ if (isConnecting ) {
+ service.traceDebug(TAG,
+ "myClient != null and the client is connecting. Connect return directly.");
+ service.traceDebug(TAG,"Connect return:isConnecting:"+isConnecting+".disconnected:"+disconnected);
+ return;
+ }else if(!disconnected){
+ service.traceDebug(TAG,"myClient != null and the client is connected and notify!");
+ doAfterConnectSuccess(resultBundle);
+ }
+ else {
+ service.traceDebug(TAG, "myClient != null and the client is not connected");
+ service.traceDebug(TAG,"Do Real connect!");
+ setConnectingState(true);
+ myClient.connect(connectOptions, invocationContext, listener);
+ }
+ }
+
+ // if myClient is null, then create a new connection
+ else {
+ myClient = new MqttAsyncClient(serverURI, clientId,
+ persistence, new AlarmPingSender(service));
+ myClient.setCallback(this);
+
+ service.traceDebug(TAG,"Do Real connect!");
+ setConnectingState(true);
+ myClient.connect(connectOptions, invocationContext, listener);
+ }
+ } catch (Exception e) {
+ handleException(resultBundle, e);
+ }
+ }
+
+ private void doAfterConnectSuccess(final Bundle resultBundle) {
+ //since the device's cpu can go to sleep, acquire a wakelock and drop it later.
+ acquireWakeLock();
+ service.callbackToActivity(clientHandle, Status.OK, resultBundle);
+ deliverBacklog();
+ setConnectingState(false);
+ disconnected = false;
+ releaseWakeLock();
+ }
+
+ private void doAfterConnectFail(final Bundle resultBundle){
+ //
+ acquireWakeLock();
+ disconnected = true;
+ setConnectingState(false);
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ releaseWakeLock();
+ }
+
+ private void handleException(final Bundle resultBundle, Exception e) {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ e.getLocalizedMessage());
+
+ resultBundle.putSerializable(MqttServiceConstants.CALLBACK_EXCEPTION, e);
+
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+
+ /**
+ * Attempt to deliver any outstanding messages we've received but which the
+ * application hasn't acknowledged. If "cleanSession" was specified, we'll
+ * have already purged any such messages from our messageStore.
+ */
+ private void deliverBacklog() {
+ Iterator backlog = service.messageStore
+ .getAllArrivedMessages(clientHandle);
+ while (backlog.hasNext()) {
+ StoredMessage msgArrived = backlog.next();
+ Bundle resultBundle = messageToBundle(msgArrived.getMessageId(),
+ msgArrived.getTopic(), msgArrived.getMessage());
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.MESSAGE_ARRIVED_ACTION);
+ service.callbackToActivity(clientHandle, Status.OK, resultBundle);
+ }
+ }
+
+ /**
+ * Create a bundle containing all relevant data pertaining to a message
+ *
+ * @param messageId
+ * the message's identifier in the messageStore, so that a
+ * callback can be made to remove it once delivered
+ * @param topic
+ * the topic on which the message was delivered
+ * @param message
+ * the message itself
+ * @return the bundle
+ */
+ private Bundle messageToBundle(String messageId, String topic,
+ MqttMessage message) {
+ Bundle result = new Bundle();
+ result.putString(MqttServiceConstants.CALLBACK_MESSAGE_ID, messageId);
+ result.putString(MqttServiceConstants.CALLBACK_DESTINATION_NAME, topic);
+ result.putParcelable(MqttServiceConstants.CALLBACK_MESSAGE_PARCEL,
+ new ParcelableMqttMessage(message));
+ return result;
+ }
+
+ /**
+ * Close connection from the server
+ *
+ */
+ void close() {
+ service.traceDebug(TAG, "close()");
+ try {
+ if (myClient != null) {
+ myClient.close();
+ }
+ } catch (MqttException e) {
+ // Pass a new bundle, let handleException stores error messages.
+ handleException(new Bundle(), e);
+ }
+ }
+
+ /**
+ * Disconnect from the server
+ *
+ * @param quiesceTimeout
+ * in milliseconds
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary string to be passed back to the activity
+ */
+ void disconnect(long quiesceTimeout, String invocationContext,
+ String activityToken) {
+ service.traceDebug(TAG, "disconnect()");
+ disconnected = true;
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ activityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
+ invocationContext);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.DISCONNECT_ACTION);
+ if ((myClient != null) && (myClient.isConnected())) {
+ IMqttActionListener listener = new MqttConnectionListener(
+ resultBundle);
+ try {
+ myClient.disconnect(quiesceTimeout, invocationContext, listener);
+ } catch (Exception e) {
+ handleException(resultBundle, e);
+ }
+ } else {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ NOT_CONNECTED);
+ service.traceError(MqttServiceConstants.DISCONNECT_ACTION,
+ NOT_CONNECTED);
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+
+ if (connectOptions.isCleanSession()) {
+ // assume we'll clear the stored messages at this point
+ service.messageStore.clearArrivedMessages(clientHandle);
+ }
+
+ releaseWakeLock();
+ }
+
+ /**
+ * Disconnect from the server
+ *
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary string to be passed back to the activity
+ */
+ void disconnect(String invocationContext, String activityToken) {
+ service.traceDebug(TAG, "disconnect()");
+ disconnected = true;
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ activityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
+ invocationContext);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.DISCONNECT_ACTION);
+ if ((myClient != null) && (myClient.isConnected())) {
+ IMqttActionListener listener = new MqttConnectionListener(
+ resultBundle);
+ try {
+ myClient.disconnect(invocationContext, listener);
+ } catch (Exception e) {
+ handleException(resultBundle, e);
+ }
+ } else {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ NOT_CONNECTED);
+ service.traceError(MqttServiceConstants.DISCONNECT_ACTION,
+ NOT_CONNECTED);
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+
+ if (connectOptions.isCleanSession()) {
+ // assume we'll clear the stored messages at this point
+ service.messageStore.clearArrivedMessages(clientHandle);
+ }
+ releaseWakeLock();
+ }
+
+ /**
+ * @return true if we are connected to an MQTT server
+ */
+ public boolean isConnected() {
+ if (myClient != null)
+ return myClient.isConnected();
+ return false;
+ }
+
+ /**
+ * Publish a message on a topic
+ *
+ * @param topic
+ * the topic on which to publish - represented as a string, not
+ * an MqttTopic object
+ * @param payload
+ * the content of the message to publish
+ * @param qos
+ * the quality of service requested
+ * @param retained
+ * whether the MQTT server should retain this message
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary string to be passed back to the activity
+ * @return token for tracking the operation
+ */
+ public IMqttDeliveryToken publish(String topic, byte[] payload, int qos,
+ boolean retained, String invocationContext, String activityToken) {
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.SEND_ACTION);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ activityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
+ invocationContext);
+
+ IMqttDeliveryToken sendToken = null;
+
+ if ((myClient != null) && (myClient.isConnected())) {
+ IMqttActionListener listener = new MqttConnectionListener(
+ resultBundle);
+ try {
+ MqttMessage message = new MqttMessage(payload);
+ message.setQos(qos);
+ message.setRetained(retained);
+ sendToken = myClient.publish(topic, payload, qos, retained,
+ invocationContext, listener);
+ storeSendDetails(topic, message, sendToken, invocationContext,
+ activityToken);
+ } catch (Exception e) {
+ handleException(resultBundle, e);
+ }
+ } else {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ NOT_CONNECTED);
+ service.traceError(MqttServiceConstants.SEND_ACTION, NOT_CONNECTED);
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+
+ return sendToken;
+ }
+
+ /**
+ * Publish a message on a topic
+ *
+ * @param topic
+ * the topic on which to publish - represented as a string, not
+ * an MqttTopic object
+ * @param message
+ * the message to publish
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary string to be passed back to the activity
+ * @return token for tracking the operation
+ */
+ public IMqttDeliveryToken publish(String topic, MqttMessage message,
+ String invocationContext, String activityToken) {
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.SEND_ACTION);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ activityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
+ invocationContext);
+
+ IMqttDeliveryToken sendToken = null;
+
+ if ((myClient != null) && (myClient.isConnected())) {
+ IMqttActionListener listener = new MqttConnectionListener(
+ resultBundle);
+ try {
+ sendToken = myClient.publish(topic, message, invocationContext,
+ listener);
+ storeSendDetails(topic, message, sendToken, invocationContext,
+ activityToken);
+ } catch (Exception e) {
+ handleException(resultBundle, e);
+ }
+ } else {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ NOT_CONNECTED);
+ service.traceError(MqttServiceConstants.SEND_ACTION, NOT_CONNECTED);
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+ return sendToken;
+ }
+
+ /**
+ * Subscribe to a topic
+ *
+ * @param topic
+ * a possibly wildcarded topic name
+ * @param qos
+ * requested quality of service for the topic
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ public void subscribe(final String topic, final int qos,
+ String invocationContext, String activityToken) {
+ service.traceDebug(TAG, "subscribe({" + topic + "}," + qos + ",{"
+ + invocationContext + "}, {" + activityToken + "}");
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.SUBSCRIBE_ACTION);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ activityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
+ invocationContext);
+
+ if ((myClient != null) && (myClient.isConnected())) {
+ IMqttActionListener listener = new MqttConnectionListener(
+ resultBundle);
+ try {
+ myClient.subscribe(topic, qos, invocationContext, listener);
+ } catch (Exception e) {
+ handleException(resultBundle, e);
+ }
+ } else {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ NOT_CONNECTED);
+ service.traceError("subscribe", NOT_CONNECTED);
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+ }
+
+ /**
+ * Subscribe to one or more topics
+ *
+ * @param topic
+ * a list of possibly wildcarded topic names
+ * @param qos
+ * requested quality of service for each topic
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ public void subscribe(final String[] topic, final int[] qos,
+ String invocationContext, String activityToken) {
+ service.traceDebug(TAG, "subscribe({" + topic + "}," + qos + ",{"
+ + invocationContext + "}, {" + activityToken + "}");
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.SUBSCRIBE_ACTION);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ activityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
+ invocationContext);
+
+ if ((myClient != null) && (myClient.isConnected())) {
+ IMqttActionListener listener = new MqttConnectionListener(
+ resultBundle);
+ try {
+ myClient.subscribe(topic, qos, invocationContext, listener);
+ } catch (Exception e) {
+ handleException(resultBundle, e);
+ }
+ } else {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ NOT_CONNECTED);
+ service.traceError("subscribe", NOT_CONNECTED);
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+ }
+
+ /**
+ * Unsubscribe from a topic
+ *
+ * @param topic
+ * a possibly wildcarded topic name
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ void unsubscribe(final String topic, String invocationContext,
+ String activityToken) {
+ service.traceDebug(TAG, "unsubscribe({" + topic + "},{"
+ + invocationContext + "}, {" + activityToken + "})");
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.UNSUBSCRIBE_ACTION);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ activityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
+ invocationContext);
+ if ((myClient != null) && (myClient.isConnected())) {
+ IMqttActionListener listener = new MqttConnectionListener(
+ resultBundle);
+ try {
+ myClient.unsubscribe(topic, invocationContext, listener);
+ } catch (Exception e) {
+ handleException(resultBundle, e);
+ }
+ } else {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ NOT_CONNECTED);
+
+ service.traceError("subscribe", NOT_CONNECTED);
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+ }
+
+ /**
+ * Unsubscribe from one or more topics
+ *
+ * @param topic
+ * a list of possibly wildcarded topic names
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ void unsubscribe(final String[] topic, String invocationContext,
+ String activityToken) {
+ service.traceDebug(TAG, "unsubscribe({" + topic + "},{"
+ + invocationContext + "}, {" + activityToken + "})");
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.UNSUBSCRIBE_ACTION);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ activityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
+ invocationContext);
+ if ((myClient != null) && (myClient.isConnected())) {
+ IMqttActionListener listener = new MqttConnectionListener(
+ resultBundle);
+ try {
+ myClient.unsubscribe(topic, invocationContext, listener);
+ } catch (Exception e) {
+ handleException(resultBundle, e);
+ }
+ } else {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ NOT_CONNECTED);
+
+ service.traceError("subscribe", NOT_CONNECTED);
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+ }
+
+ /**
+ * Get tokens for all outstanding deliveries for a client
+ *
+ * @return an array (possibly empty) of tokens
+ */
+ public IMqttDeliveryToken[] getPendingDeliveryTokens() {
+ return myClient.getPendingDeliveryTokens();
+ }
+
+ // Implement MqttCallback
+ /**
+ * Callback for connectionLost
+ *
+ * @param why
+ * the exeception causing the break in communications
+ */
+ @Override
+ public void connectionLost(Throwable why) {
+ service.traceDebug(TAG, "connectionLost(" + why.getMessage() + ")");
+ disconnected = true;
+ try {
+ myClient.disconnect(null, new IMqttActionListener() {
+
+ @Override
+ public void onSuccess(IMqttToken asyncActionToken) {
+ // No action
+ }
+
+ @Override
+ public void onFailure(IMqttToken asyncActionToken,
+ Throwable exception) {
+ // No action
+ }
+ });
+ } catch (Exception e) {
+ // ignore it - we've done our best
+ }
+
+ Bundle resultBundle = new Bundle();
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.ON_CONNECTION_LOST_ACTION);
+ if (why != null) {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ why.getMessage());
+ if (why instanceof MqttException) {
+ resultBundle.putSerializable(
+ MqttServiceConstants.CALLBACK_EXCEPTION, why);
+ }
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_EXCEPTION_STACK,
+ Log.getStackTraceString(why));
+ }
+ service.callbackToActivity(clientHandle, Status.OK, resultBundle);
+ service.traceDebug(TAG,"Reconnect for connection lost");
+ reconnect();
+ // client has lost connection no need for wake lock
+ releaseWakeLock();
+ }
+
+ /**
+ * Callback to indicate a message has been delivered (the exact meaning of
+ * "has been delivered" is dependent on the QOS value)
+ *
+ * @param messageToken
+ * the messge token provided when the message was originally sent
+ */
+ @Override
+ public void deliveryComplete(IMqttDeliveryToken messageToken) {
+
+ service.traceDebug(TAG, "deliveryComplete(" + messageToken + ")");
+
+ MqttMessage message = savedSentMessages.remove(messageToken);
+ if (message != null) { // If I don't know about the message, it's
+ // irrelevant
+ String topic = savedTopics.remove(messageToken);
+ String activityToken = savedActivityTokens.remove(messageToken);
+ String invocationContext = savedInvocationContexts
+ .remove(messageToken);
+
+ Bundle resultBundle = messageToBundle(null, topic, message);
+ if (activityToken != null) {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.SEND_ACTION);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ activityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT,
+ invocationContext);
+
+ service.callbackToActivity(clientHandle, Status.OK,
+ resultBundle);
+ }
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.MESSAGE_DELIVERED_ACTION);
+ service.callbackToActivity(clientHandle, Status.OK, resultBundle);
+ }
+
+ // this notification will have kept the connection alive but send the previously sechudled ping anyway
+ }
+
+ /**
+ * Callback when a message is received
+ *
+ * @param topic
+ * the topic on which the message was received
+ * @param message
+ * the message itself
+ */
+ @Override
+ public void messageArrived(String topic, MqttMessage message)
+ throws Exception {
+
+ service.traceDebug(TAG,
+ "messageArrived(" + topic + ",{" + message.toString() + "})");
+
+ String messageId = service.messageStore.storeArrived(clientHandle,
+ topic, message);
+
+ Bundle resultBundle = messageToBundle(messageId, topic, message);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.MESSAGE_ARRIVED_ACTION);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_MESSAGE_ID,
+ messageId);
+ service.callbackToActivity(clientHandle, Status.OK, resultBundle);
+
+ }
+
+ /**
+ * Store details of sent messages so we can handle "deliveryComplete"
+ * callbacks from the mqttClient
+ *
+ * @param topic
+ * @param msg
+ * @param messageToken
+ * @param invocationContext
+ * @param activityToken
+ */
+ private void storeSendDetails(final String topic, final MqttMessage msg,
+ final IMqttDeliveryToken messageToken,
+ final String invocationContext, final String activityToken) {
+ savedTopics.put(messageToken, topic);
+ savedSentMessages.put(messageToken, msg);
+ savedActivityTokens.put(messageToken, activityToken);
+ savedInvocationContexts.put(messageToken, invocationContext);
+ }
+
+ /**
+ * Acquires a partial wake lock for this client
+ */
+ private void acquireWakeLock() {
+ if (wakelock == null) {
+ PowerManager pm = (PowerManager) service
+ .getSystemService(Service.POWER_SERVICE);
+ wakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ wakeLockTag);
+ }
+ wakelock.acquire();
+
+ }
+
+ /**
+ * Releases the currently held wake lock for this client
+ */
+ private void releaseWakeLock() {
+ if(wakelock != null && wakelock.isHeld()){
+ wakelock.release();
+ }
+ }
+
+ /**
+ * General-purpose IMqttActionListener for the Client context
+ *
+ * Simply handles the basic success/failure cases for operations which don't
+ * return results
+ *
+ */
+ private class MqttConnectionListener implements IMqttActionListener {
+
+ private final Bundle resultBundle;
+
+ private MqttConnectionListener(Bundle resultBundle) {
+ this.resultBundle = resultBundle;
+ }
+
+ @Override
+ public void onSuccess(IMqttToken asyncActionToken) {
+ service.callbackToActivity(clientHandle, Status.OK, resultBundle);
+ }
+
+ @Override
+ public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ exception.getLocalizedMessage());
+
+ resultBundle.putSerializable(
+ MqttServiceConstants.CALLBACK_EXCEPTION, exception);
+
+ service.callbackToActivity(clientHandle, Status.ERROR, resultBundle);
+ }
+ }
+
+ /**
+ * Receive notification that we are offline
+ * if cleanSession is true, we need to regard this as a disconnection
+ */
+ void offline() {
+
+ if (!disconnected && !cleanSession) {
+ Exception e = new Exception("Android offline");
+ connectionLost(e);
+ }
+ }
+
+ /**
+ * Reconnect
+ * Only appropriate if cleanSession is false and we were connected.
+ * Declare as synchronized to avoid multiple calls to this method to send connect
+ * multiple times
+ */
+ synchronized void reconnect() {
+ if (isConnecting) {
+ service.traceDebug(TAG, "The client is connecting. Reconnect return directly.");
+ return ;
+ }
+
+// if(!service.isOnline()){
+// service.traceDebug(TAG,
+// "The network is not reachable. Will not do reconnect");
+// return;
+// }
+
+ if (disconnected && !cleanSession) {
+ // use the activityToke the same with action connect
+ service.traceDebug(TAG,"Do Real Reconnect!");
+ final Bundle resultBundle = new Bundle();
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN,
+ connectActivityToken);
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_INVOCATION_CONTEXT, null);
+ resultBundle.putString(MqttServiceConstants.CALLBACK_ACTION,
+ MqttServiceConstants.CONNECT_ACTION);
+
+ try {
+
+ IMqttActionListener listener = new MqttConnectionListener(resultBundle) {
+ @Override
+ public void onSuccess(IMqttToken asyncActionToken) {
+ // since the device's cpu can go to sleep, acquire a
+ // wakelock and drop it later.
+ service.traceDebug(TAG,"Reconnect Success!");
+ service.traceDebug(TAG,"DeliverBacklog when reconnect.");
+ doAfterConnectSuccess(resultBundle);
+ }
+
+ @Override
+ public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
+ resultBundle.putString(
+ MqttServiceConstants.CALLBACK_ERROR_MESSAGE,
+ exception.getLocalizedMessage());
+ resultBundle.putSerializable(
+ MqttServiceConstants.CALLBACK_EXCEPTION,
+ exception);
+ service.callbackToActivity(clientHandle, Status.ERROR,
+ resultBundle);
+
+ doAfterConnectFail(resultBundle);
+
+ //reconnect fail , try reconnect . check network in reconnect function;
+ service.traceDebug(TAG,"Reconnect Fail, wait for network connection");
+
+ waitUntilOnline(RECONNECT_DELAY);
+
+ if (isAuthenticationFailure(exception)) {
+ authFailHandler.onApplicationAuthenticationFailure(exception);
+ } else {
+ reconnect();
+ }
+ }
+ };
+
+ myClient.connect(connectOptions, null, listener);
+ setConnectingState(true);
+ } catch (MqttException e) {
+ service.traceError(TAG, "Cannot reconnect to remote server." + e.getMessage());
+ setConnectingState(false);
+ handleException(resultBundle, e);
+ } catch (Exception e){
+ /* TODO: Added Due to: https://github.com/eclipse/paho.mqtt.android/issues/101
+ For some reason in a small number of cases, myClient is null here and so
+ a NullPointer Exception is thrown. This is a workaround to pass the exception
+ up to the application. myClient should not be null so more investigation is
+ required.
+ */
+ service.traceError(TAG, "Cannot reconnect to remote server." + e.getMessage());
+ setConnectingState(false);
+ MqttException newEx = new MqttException(MqttException.REASON_CODE_UNEXPECTED_ERROR, e.getCause());
+ handleException(resultBundle, newEx);
+ }
+ } else {
+ service.traceDebug(TAG, "not reconnect because connection state = " + disconnected + "; clean session = " + cleanSession);
+ }
+ }
+
+ /**
+ *
+ * @param isConnecting
+ */
+ synchronized void setConnectingState(boolean isConnecting){
+ this.isConnecting = isConnecting;
+ }
+
+ private boolean isAuthenticationFailure(Throwable exception) {
+ return (authFailHandler != null) &&
+ authFailHandler.isApplicationAuthenticationFailure(exception);
+ }
+
+ public void recycleConnection() {
+ if (myClient != null) {
+ myClient.recycleConnection();
+ }
+ }
+
+ private void waitUntilOnline(long delay) {
+ Observable.interval(delay, TimeUnit.SECONDS)
+ .filter(new Func1() {
+ @Override
+ public Boolean call(Long aLong) {
+ return service.isOnline();
+ }
+ })
+ .take(1)
+ .toBlocking()
+ .single();
+ }
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttDeliveryTokenAndroid.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttDeliveryTokenAndroid.java
new file mode 100644
index 00000000..12ae877b
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttDeliveryTokenAndroid.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service;
+
+import org.eclipse.paho.client.mqttv3.IMqttActionListener;
+import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+/**
+ *
+ * Implementation of the IMqttDeliveryToken interface for use from within the
+ * MqttAndroidClient implementation
+ */
+class MqttDeliveryTokenAndroid extends MqttTokenAndroid
+ implements IMqttDeliveryToken {
+
+ // The message which is being tracked by this token
+ private MqttMessage message;
+
+ MqttDeliveryTokenAndroid(MqttAndroidClient client,
+ Object userContext, IMqttActionListener listener, MqttMessage message) {
+ super(client, userContext, listener);
+ this.message = message;
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttDeliveryToken#getMessage()
+ */
+ @Override
+ public MqttMessage getMessage() throws MqttException {
+ return message;
+ }
+
+ void setMessage(MqttMessage message) {
+ this.message = message;
+ }
+
+ void notifyDelivery(MqttMessage delivered) {
+ message = delivered;
+ super.notifyComplete();
+ }
+
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttService.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttService.java
new file mode 100644
index 00000000..639f1097
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttService.java
@@ -0,0 +1,878 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * James Sutton - isOnline Null Pointer (bug 473775)
+ */
+package org.eclipse.paho.android.service;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.MqttClientPersistence;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
+import org.eclipse.paho.client.mqttv3.MqttSecurityException;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+
+/**
+ *
+ * The android service which interfaces with an MQTT client implementation
+ *
+ *
+ * The main API of MqttService is intended to pretty much mirror the
+ * IMqttAsyncClient with appropriate adjustments for the Android environment.
+ * These adjustments usually consist of adding two parameters to each method :-
+ *
+ *
invocationContext - a string passed from the application to identify the
+ * context of the operation (mainly included for support of the javascript API
+ * implementation)
+ *
activityToken - a string passed from the Activity to relate back to a
+ * callback method or other context-specific data
+ *
+ *
+ *
+ * To support multiple client connections, the bulk of the MQTT work is
+ * delegated to MqttConnection objects. These are identified by "client
+ * handle" strings, which is how the Activity, and the higher-level APIs refer
+ * to them.
+ *
+ *
+ * Activities using this service are expected to start it and bind to it using
+ * the BIND_AUTO_CREATE flag. The life cycle of this service is based on this
+ * approach.
+ *
+ *
+ * Operations are highly asynchronous - in most cases results are returned to
+ * the Activity by broadcasting one (or occasionally more) appropriate Intents,
+ * which the Activity is expected to register a listener for.
+ * The Intents have an Action of
+ * {@link MqttServiceConstants#CALLBACK_TO_ACTIVITY
+ * MqttServiceConstants.CALLBACK_TO_ACTIVITY} which allows the Activity to
+ * register a listener with an appropriate IntentFilter.
+ * Further data is provided by "Extra Data" in the Intent, as follows :-
+ *
The identifier for the message in the message
+ * store, used by the Activity to acknowledge the arrival of the message, so
+ * that the service may remove it from the store
The new message encapsulated in Android
+ * Parcelable format as a {@link ParcelableMqttMessage}
+ *
The Message Arrived event
+ *
+ *
+ *
+ */
+public class MqttService extends Service implements MqttTraceHandler {
+
+ // Identifier for Intents, log messages, etc..
+ static final String TAG = "MqttService";
+
+ // callback id for making trace callbacks to the Activity
+ // needs to be set by the activity as appropriate
+ private String traceCallbackId;
+ // state of tracing
+ private boolean traceEnabled = false;
+
+ // somewhere to persist received messages until we're sure
+ // that they've reached the application
+ MessageStore messageStore;
+
+ // An intent receiver to deal with changes in network connectivity
+ private NetworkConnectionIntentReceiver networkConnectionMonitor;
+
+ //a receiver to recognise when the user changes the "background data" preference
+ // and a flag to track that preference
+ // Only really relevant below android version ICE_CREAM_SANDWICH - see
+ // android docs
+ private BackgroundDataPreferenceReceiver backgroundDataPreferenceMonitor;
+ private volatile boolean backgroundDataEnabled = true;
+
+ // a way to pass ourself back to the activity
+ private MqttServiceBinder mqttServiceBinder;
+
+ // mapping from client handle strings to actual client connections.
+ private Map connections = new ConcurrentHashMap();
+ // private LogToFile logger;
+
+ public MqttService() {
+ super();
+ }
+
+ /**
+ * pass data back to the Activity, by building a suitable Intent object and
+ * broadcasting it
+ *
+ * @param clientHandle
+ * source of the data
+ * @param status
+ * OK or Error
+ * @param dataBundle
+ * the data to be passed
+ */
+ void callbackToActivity(String clientHandle, Status status,
+ Bundle dataBundle) {
+ // Don't call traceDebug, as it will try to callbackToActivity leading
+ // to recursion.
+ Intent callbackIntent = new Intent(
+ MqttServiceConstants.CALLBACK_TO_ACTIVITY);
+ if (clientHandle != null) {
+ callbackIntent.putExtra(
+ MqttServiceConstants.CALLBACK_CLIENT_HANDLE, clientHandle);
+ }
+ callbackIntent.putExtra(MqttServiceConstants.CALLBACK_STATUS, status);
+ if (dataBundle != null) {
+ callbackIntent.putExtras(dataBundle);
+ }
+ LocalBroadcastManager.getInstance(this).sendBroadcast(callbackIntent);
+ }
+
+ // The major API implementation follows :-
+
+ /*
+ * Get an MqttConnection object to represent a connection to a server
+ *
+ * @param serverURI specifies the protocol, host name and port to be used to connect to an MQTT server
+ * @param clientId specifies the name by which this connection should be identified to the server
+ * @param contextId specifies the app conext info to make a difference between apps
+ * @return a string to be used by the Activity as a "handle" for this
+ * MqttConnection
+ */
+ public String getClient(String serverURI, String clientId, String contextId,
+ MqttClientPersistence persistence, MqttAuthenticationFailureHandler authFailHandler) {
+ String clientHandle = serverURI + ":" + clientId+":"+contextId;
+ if (!connections.containsKey(clientHandle)) {
+ MqttConnection client = new MqttConnection(this, serverURI,
+ clientId, persistence, clientHandle, authFailHandler);
+ connections.put(clientHandle, client);
+ }
+ return clientHandle;
+ }
+
+ /**
+ * Connect to the MQTT server specified by a particular client
+ *
+ * @param clientHandle
+ * identifies the MqttConnection to use
+ * @param connectOptions
+ * the MQTT connection options to be used
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ * @throws MqttSecurityException
+ * @throws MqttException
+ */
+ public void connect(String clientHandle, MqttConnectOptions connectOptions,
+ String invocationContext, String activityToken)
+ throws MqttSecurityException, MqttException {
+ MqttConnection client = getConnection(clientHandle);
+ client.connect(connectOptions, invocationContext, activityToken);
+
+ }
+
+ /**
+ * Request all clients to reconnect if appropriate
+ */
+ void reconnect() {
+ traceDebug(TAG, "Reconnect to server, client size=" + connections.size());
+ for (MqttConnection client : connections.values()) {
+ traceDebug("Reconnect Client:",
+ client.getClientId() + '/' + client.getServerURI());
+ if(this.isOnline()){
+ client.reconnect();
+ }
+ }
+ }
+
+ /**
+ * Close connection from a particular client
+ *
+ * @param clientHandle
+ * identifies the MqttConnection to use
+ */
+ public void close(String clientHandle) {
+ MqttConnection client = getConnection(clientHandle);
+ client.close();
+ }
+
+ /**
+ * Disconnect from the server
+ *
+ * @param clientHandle
+ * identifies the MqttConnection to use
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ public void disconnect(String clientHandle, String invocationContext,
+ String activityToken) {
+ MqttConnection client = getConnection(clientHandle);
+ client.disconnect(invocationContext, activityToken);
+ connections.remove(clientHandle);
+
+
+ // the activity has finished using us, so we can stop the service
+ // the activities are bound with BIND_AUTO_CREATE, so the service will
+ // remain around until the last activity disconnects
+ stopSelf();
+ }
+
+ /**
+ * Disconnect from the server
+ *
+ * @param clientHandle
+ * identifies the MqttConnection to use
+ * @param quiesceTimeout
+ * in milliseconds
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ public void disconnect(String clientHandle, long quiesceTimeout,
+ String invocationContext, String activityToken) {
+ MqttConnection client = getConnection(clientHandle);
+ client.disconnect(quiesceTimeout, invocationContext, activityToken);
+ connections.remove(clientHandle);
+
+ // the activity has finished using us, so we can stop the service
+ // the activities are bound with BIND_AUTO_CREATE, so the service will
+ // remain around until the last activity disconnects
+ stopSelf();
+ }
+
+ /**
+ * Get the status of a specific client
+ *
+ * @param clientHandle
+ * identifies the MqttConnection to use
+ * @return true if the specified client is connected to an MQTT server
+ */
+ public boolean isConnected(String clientHandle) {
+ MqttConnection client = getConnection(clientHandle);
+ return client.isConnected();
+ }
+
+ /**
+ * Publish a message to a topic
+ *
+ * @param clientHandle
+ * identifies the MqttConnection to use
+ * @param topic
+ * the topic to which to publish
+ * @param payload
+ * the content of the message to publish
+ * @param qos
+ * the quality of service requested
+ * @param retained
+ * whether the MQTT server should retain this message
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ * @throws MqttPersistenceException
+ * @throws MqttException
+ * @return token for tracking the operation
+ */
+ public IMqttDeliveryToken publish(String clientHandle, String topic,
+ byte[] payload, int qos, boolean retained,
+ String invocationContext, String activityToken)
+ throws MqttPersistenceException, MqttException {
+ MqttConnection client = getConnection(clientHandle);
+ return client.publish(topic, payload, qos, retained, invocationContext,
+ activityToken);
+ }
+
+ /**
+ * Publish a message to a topic
+ *
+ * @param clientHandle
+ * identifies the MqttConnection to use
+ * @param topic
+ * the topic to which to publish
+ * @param message
+ * the message to publish
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ * @throws MqttPersistenceException
+ * @throws MqttException
+ * @return token for tracking the operation
+ */
+ public IMqttDeliveryToken publish(String clientHandle, String topic,
+ MqttMessage message, String invocationContext, String activityToken)
+ throws MqttPersistenceException, MqttException {
+ MqttConnection client = getConnection(clientHandle);
+ return client.publish(topic, message, invocationContext, activityToken);
+ }
+
+ /**
+ * Subscribe to a topic
+ *
+ * @param clientHandle
+ * identifies the MqttConnection to use
+ * @param topic
+ * a possibly wildcarded topic name
+ * @param qos
+ * requested quality of service for the topic
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ public void subscribe(String clientHandle, String topic, int qos,
+ String invocationContext, String activityToken) {
+ MqttConnection client = getConnection(clientHandle);
+ client.subscribe(topic, qos, invocationContext, activityToken);
+ }
+
+ /**
+ * Subscribe to one or more topics
+ *
+ * @param clientHandle
+ * identifies the MqttConnection to use
+ * @param topic
+ * a list of possibly wildcarded topic names
+ * @param qos
+ * requested quality of service for each topic
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ public void subscribe(String clientHandle, String[] topic, int[] qos,
+ String invocationContext, String activityToken) {
+ MqttConnection client = getConnection(clientHandle);
+ client.subscribe(topic, qos, invocationContext, activityToken);
+ }
+
+ /**
+ * Unsubscribe from a topic
+ *
+ * @param clientHandle
+ * identifies the MqttConnection
+ * @param topic
+ * a possibly wildcarded topic name
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ public void unsubscribe(String clientHandle, final String topic,
+ String invocationContext, String activityToken) {
+ MqttConnection client = getConnection(clientHandle);
+ client.unsubscribe(topic, invocationContext, activityToken);
+ }
+
+ /**
+ * Unsubscribe from one or more topics
+ *
+ * @param clientHandle
+ * identifies the MqttConnection
+ * @param topic
+ * a list of possibly wildcarded topic names
+ * @param invocationContext
+ * arbitrary data to be passed back to the application
+ * @param activityToken
+ * arbitrary identifier to be passed back to the Activity
+ */
+ public void unsubscribe(String clientHandle, final String[] topic,
+ String invocationContext, String activityToken) {
+ MqttConnection client = getConnection(clientHandle);
+ client.unsubscribe(topic, invocationContext, activityToken);
+ }
+
+ /**
+ * Get tokens for all outstanding deliveries for a client
+ *
+ * @param clientHandle
+ * identifies the MqttConnection
+ * @return an array (possibly empty) of tokens
+ */
+ public IMqttDeliveryToken[] getPendingDeliveryTokens(String clientHandle) {
+ MqttConnection client = getConnection(clientHandle);
+ return client.getPendingDeliveryTokens();
+ }
+
+ /**
+ * Get the MqttConnection identified by this client handle
+ *
+ * @param clientHandle identifies the MqttConnection
+ * @return the MqttConnection identified by this handle
+ */
+ private MqttConnection getConnection(String clientHandle) {
+ MqttConnection client = connections.get(clientHandle);
+ if (client == null) {
+ throw new IllegalArgumentException("Invalid ClientHandle");
+ }
+ return client;
+ }
+
+ /**
+ * Called by the Activity when a message has been passed back to the
+ * application
+ *
+ * @param clientHandle identifier for the client which received the message
+ * @param id identifier for the MQTT message
+ */
+ public Status acknowledgeMessageArrival(String clientHandle, String id) {
+ if (messageStore.discardArrived(clientHandle, id)) {
+ return Status.OK;
+ }
+ else {
+ return Status.ERROR;
+ }
+ }
+
+ // Extend Service
+
+ /**
+ * @see android.app.Service#onCreate()
+ */
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ /*
+ * create new logger sebaiknya menggunakan thread yang berbeda supaya
+ * tidak mengakibatkan ANR
+ */
+ // logger = new LogToFile(this);
+
+ // create a binder that will let the Activity UI send
+ // commands to the Service
+ mqttServiceBinder = new MqttServiceBinder(this);
+
+ // create somewhere to buffer received messages until
+ // we know that they have been passed to the application
+ messageStore = new DatabaseMessageStore(this, this);
+ }
+
+
+
+ /**
+ * @see android.app.Service#onDestroy()
+ */
+ @Override
+ public void onDestroy() {
+ // disconnect immediately
+ for (MqttConnection client : connections.values()) {
+ client.disconnect(null, null);
+ }
+
+ // clear down
+ if (mqttServiceBinder != null) {
+ mqttServiceBinder = null;
+ }
+
+ unregisterBroadcastReceivers();
+
+ if (this.messageStore !=null )
+ this.messageStore.close();
+
+ super.onDestroy();
+ }
+
+ /**
+ * @see android.app.Service#onBind(Intent)
+ */
+ @Override
+ public IBinder onBind(Intent intent) {
+ // What we pass back to the Activity on binding -
+ // a reference to ourself, and the activityToken
+ // we were given when started
+ String activityToken = intent
+ .getStringExtra(MqttServiceConstants.CALLBACK_ACTIVITY_TOKEN);
+ mqttServiceBinder.setActivityToken(activityToken);
+ return mqttServiceBinder;
+ }
+
+ /**
+ * @see android.app.Service#onStartCommand(Intent,int,int)
+ */
+ @Override
+ public int onStartCommand(final Intent intent, int flags, final int startId) {
+ // run till explicitly stopped, restart when
+ // process restarted
+ registerBroadcastReceivers();
+
+ return START_STICKY;
+ }
+
+ /**
+ * Identify the callbackId to be passed when making tracing calls back into
+ * the Activity
+ *
+ * @param traceCallbackId identifier to the callback into the Activity
+ */
+ public void setTraceCallbackId(String traceCallbackId) {
+ this.traceCallbackId = traceCallbackId;
+ }
+
+ /**
+ * Turn tracing on and off
+ *
+ * @param traceEnabled set true to turn on tracing, false to turn off tracing
+ */
+ public void setTraceEnabled(boolean traceEnabled) {
+ this.traceEnabled = traceEnabled;
+ }
+
+ /**
+ * Check whether trace is on or off.
+ *
+ * @return the state of trace
+ */
+ public boolean isTraceEnabled(){
+ return this.traceEnabled;
+ }
+
+ /**
+ * Trace debugging information
+ *
+ * @param tag
+ * identifier for the source of the trace
+ * @param message
+ * the text to be traced
+ */
+ @Override
+ public void traceDebug(String tag, String message) {
+ traceCallback(MqttServiceConstants.TRACE_DEBUG, tag, message);
+ // logger.write(tag, message);
+ }
+
+ /**
+ * Trace error information
+ *
+ * @param tag
+ * identifier for the source of the trace
+ * @param message
+ * the text to be traced
+ */
+ @Override
+ public void traceError(String tag, String message) {
+ traceCallback(MqttServiceConstants.TRACE_ERROR, tag, message);
+ }
+
+ private void traceCallback(String severity, String tag, String message) {
+ if ((traceCallbackId != null) && (traceEnabled)) {
+ Bundle dataBundle = new Bundle();
+ dataBundle.putString(MqttServiceConstants.CALLBACK_ACTION, MqttServiceConstants.TRACE_ACTION);
+ dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_SEVERITY, severity);
+ dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_TAG, tag);
+ //dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_ID, traceCallbackId);
+ dataBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, message);
+ callbackToActivity(traceCallbackId, Status.ERROR, dataBundle);
+ }
+ }
+
+ /**
+ * trace exceptions
+ *
+ * @param tag
+ * identifier for the source of the trace
+ * @param message
+ * the text to be traced
+ * @param e
+ * the exception
+ */
+ @Override
+ public void traceException(String tag, String message, Exception e) {
+ if (traceCallbackId != null) {
+ Bundle dataBundle = new Bundle();
+ dataBundle.putString(MqttServiceConstants.CALLBACK_ACTION, MqttServiceConstants.TRACE_ACTION);
+ dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_SEVERITY, MqttServiceConstants.TRACE_EXCEPTION);
+ dataBundle.putString(MqttServiceConstants.CALLBACK_ERROR_MESSAGE, message);
+ dataBundle.putSerializable(MqttServiceConstants.CALLBACK_EXCEPTION, e); //TODO: Check
+ dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_TAG, tag);
+ //dataBundle.putString(MqttServiceConstants.CALLBACK_TRACE_ID, traceCallbackId);
+ callbackToActivity(traceCallbackId, Status.ERROR, dataBundle);
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private void registerBroadcastReceivers() {
+ if (networkConnectionMonitor == null) {
+ networkConnectionMonitor = new NetworkConnectionIntentReceiver();
+ registerReceiver(networkConnectionMonitor, new IntentFilter(
+ ConnectivityManager.CONNECTIVITY_ACTION));
+ }
+
+ if (Build.VERSION.SDK_INT < 14 /**Build.VERSION_CODES.ICE_CREAM_SANDWICH**/) {
+ // Support the old system for background data preferences
+ ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
+ backgroundDataEnabled = cm.getBackgroundDataSetting();
+ if (backgroundDataPreferenceMonitor == null) {
+ backgroundDataPreferenceMonitor = new BackgroundDataPreferenceReceiver();
+ registerReceiver(
+ backgroundDataPreferenceMonitor,
+ new IntentFilter(
+ ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED));
+ }
+ }
+ }
+
+ private void unregisterBroadcastReceivers(){
+ if(networkConnectionMonitor != null){
+ unregisterReceiver(networkConnectionMonitor);
+ networkConnectionMonitor = null;
+ }
+
+ if (Build.VERSION.SDK_INT < 14 /**Build.VERSION_CODES.ICE_CREAM_SANDWICH**/) {
+ if(backgroundDataPreferenceMonitor != null){
+ unregisterReceiver(backgroundDataPreferenceMonitor);
+ }
+ }
+ }
+
+ /*
+ * Called in response to a change in network connection - after losing a
+ * connection to the server, this allows us to wait until we have a usable
+ * data connection again
+ */
+ private class NetworkConnectionIntentReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ traceDebug(TAG, "Internal network status receive.");
+ // we protect against the phone switching off
+ // by requesting a wake lock - we request the minimum possible wake
+ // lock - just enough to keep the CPU running until we've finished
+ PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
+ WakeLock wl = pm
+ .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MQTT");
+ wl.acquire();
+ traceDebug(TAG,"Reconnect for Network recovery.");
+ if (isOnline()) {
+ traceDebug(TAG,"Online,reconnect.");
+ // we have an internet connection - have another try at
+ // connecting
+ reconnect();
+ } else {
+ notifyClientsOffline();
+ }
+
+ wl.release();
+ }
+ }
+
+ /**
+ * @return whether the android service can be regarded as online
+ */
+ public boolean isOnline() {
+ ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = cm.getActiveNetworkInfo();
+ if (networkInfo != null
+ && networkInfo.isAvailable()
+ && networkInfo.isConnected()
+ && backgroundDataEnabled) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Notify clients we're offline
+ */
+ public void notifyClientsOffline() {
+ for (MqttConnection connection : connections.values()) {
+ connection.offline();
+ }
+ }
+
+ public void recycleConnection() {
+ for (MqttConnection connection : connections.values()) {
+ connection.recycleConnection();
+ }
+ }
+
+
+ /**
+ * Detect changes of the Allow Background Data setting - only used below
+ * ICE_CREAM_SANDWICH
+ */
+ private class BackgroundDataPreferenceReceiver extends BroadcastReceiver {
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
+ traceDebug(TAG,"Reconnect since BroadcastReceiver.");
+ if (cm.getBackgroundDataSetting()) {
+ if (!backgroundDataEnabled) {
+ backgroundDataEnabled = true;
+ // we have the Internet connection - have another try at
+ // connecting
+ reconnect();
+ }
+ } else {
+ backgroundDataEnabled = false;
+ notifyClientsOffline();
+ }
+ }
+ }
+
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttServiceBinder.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttServiceBinder.java
new file mode 100644
index 00000000..6dbf3fd1
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttServiceBinder.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service;
+
+import android.os.Binder;
+
+/**
+ * What the Service passes to the Activity on binding:-
+ *
+ *
a reference to the Service
+ *
the activityToken provided when the Service was started
+ *
+ *
+ */
+class MqttServiceBinder extends Binder {
+
+ private MqttService mqttService;
+ private String activityToken;
+
+ MqttServiceBinder(MqttService mqttService) {
+ this.mqttService = mqttService;
+ }
+
+ /**
+ * @return a reference to the Service
+ */
+ public MqttService getService() {
+ return mqttService;
+ }
+
+ void setActivityToken(String activityToken) {
+ this.activityToken = activityToken;
+ }
+
+ /**
+ * @return the activityToken provided when the Service was started
+ */
+ public String getActivityToken() {
+ return activityToken;
+ }
+
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttServiceConstants.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttServiceConstants.java
new file mode 100644
index 00000000..f903135b
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttServiceConstants.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service;
+
+/**
+ * Various strings used to identify operations or data in the Android MQTT
+ * service, mainly used in Intents passed between Activities and the Service.
+ */
+interface MqttServiceConstants {
+
+ /*
+ * Version information
+ */
+
+ static final String VERSION = "v0";
+
+ /*
+ * Attributes of messages
Used for the column names in the database
+ */
+ static final String DUPLICATE = "duplicate";
+ static final String RETAINED = "retained";
+ static final String QOS = "qos";
+ static final String PAYLOAD = "payload";
+ static final String DESTINATION_NAME = "destinationName";
+ static final String CLIENT_HANDLE = "clientHandle";
+ static final String MESSAGE_ID = "messageId";
+
+ /* Tags for actions passed between the Activity and the Service */
+ static final String SEND_ACTION = "send";
+ static final String UNSUBSCRIBE_ACTION = "unsubscribe";
+ static final String SUBSCRIBE_ACTION = "subscribe";
+ static final String DISCONNECT_ACTION = "disconnect";
+ static final String CONNECT_ACTION = "connect";
+ static final String MESSAGE_ARRIVED_ACTION = "messageArrived";
+ static final String MESSAGE_DELIVERED_ACTION = "messageDelivered";
+ static final String ON_CONNECTION_LOST_ACTION = "onConnectionLost";
+ static final String TRACE_ACTION = "trace";
+
+ /* Identifies an Intent which calls back to the Activity */
+ static final String CALLBACK_TO_ACTIVITY = MqttService.TAG
+ + ".callbackToActivity"+"."+VERSION;
+
+ /* Identifiers for extra data on Intents broadcast to the Activity */
+ static final String CALLBACK_ACTION = MqttService.TAG + ".callbackAction";
+ static final String CALLBACK_STATUS = MqttService.TAG + ".callbackStatus";
+ static final String CALLBACK_CLIENT_HANDLE = MqttService.TAG + "."
+ + CLIENT_HANDLE;
+ static final String CALLBACK_ERROR_MESSAGE = MqttService.TAG
+ + ".errorMessage";
+ static final String CALLBACK_EXCEPTION_STACK = MqttService.TAG
+ + ".exceptionStack";
+ static final String CALLBACK_INVOCATION_CONTEXT = MqttService.TAG + "."
+ + "invocationContext";
+ static final String CALLBACK_ACTIVITY_TOKEN = MqttService.TAG + "."
+ + "activityToken";
+ static final String CALLBACK_DESTINATION_NAME = MqttService.TAG + '.'
+ + DESTINATION_NAME;
+ static final String CALLBACK_MESSAGE_ID = MqttService.TAG + '.'
+ + MESSAGE_ID;
+ static final String CALLBACK_MESSAGE_PARCEL = MqttService.TAG + ".PARCEL";
+ static final String CALLBACK_TRACE_SEVERITY = MqttService.TAG
+ + ".traceSeverity";
+ static final String CALLBACK_TRACE_TAG = MqttService.TAG + ".traceTag";
+ static final String CALLBACK_TRACE_ID = MqttService.TAG + ".traceId";
+ static final String CALLBACK_ERROR_NUMBER = MqttService.TAG
+ + ".ERROR_NUMBER";
+
+ static final String CALLBACK_EXCEPTION = MqttService.TAG + ".exception";
+
+ //Intent prefix for Ping sender.
+ static final String PING_SENDER = MqttService.TAG + ".pingSender.";
+
+ //Constant for wakelock
+ static final String PING_WAKELOCK = MqttService.TAG + ".client.";
+ static final String WAKELOCK_NETWORK_INTENT = MqttService.TAG + "";
+
+ //Trace severity levels
+ static final String TRACE_ERROR = "error";
+ static final String TRACE_DEBUG = "debug";
+ static final String TRACE_EXCEPTION = "exception";
+
+
+ //exception code for non MqttExceptions
+ static final int NON_MQTT_EXCEPTION = -1;
+
+}
\ No newline at end of file
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttTokenAndroid.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttTokenAndroid.java
new file mode 100644
index 00000000..839cb101
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttTokenAndroid.java
@@ -0,0 +1,252 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service;
+
+import org.eclipse.paho.client.mqttv3.IMqttActionListener;
+import org.eclipse.paho.client.mqttv3.IMqttAsyncClient;
+import org.eclipse.paho.client.mqttv3.IMqttToken;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttSecurityException;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttWireMessage;
+
+/**
+ *
+ * Implementation of the IMqttToken interface for use from within the
+ * MqttAndroidClient implementation
+ */
+
+class MqttTokenAndroid implements IMqttToken {
+
+ private IMqttActionListener listener;
+
+ private volatile boolean isComplete;
+
+ private volatile MqttException lastException;
+
+ private Object waitObject = new Object();
+
+ private MqttAndroidClient client;
+
+ private Object userContext;
+
+ private String[] topics;
+
+ private IMqttToken delegate; // specifically for getMessageId
+
+ private MqttException pendingException;
+
+ /**
+ * Standard constructor
+ *
+ * @param client used to pass MqttAndroidClient object
+ * @param userContext used to pass context
+ * @param listener optional listener that will be notified when the action completes. Use null if not required.
+ */
+ MqttTokenAndroid(MqttAndroidClient client,
+ Object userContext, IMqttActionListener listener) {
+ this(client, userContext, listener, (String[]) null);
+ }
+
+ /**
+ * Constructor for use with subscribe operations
+ *
+ * @param client used to pass MqttAndroidClient object
+ * @param userContext used to pass context
+ * @param listener optional listener that will be notified when the action completes. Use null if not required.
+ * @param topics topics to subscribe to, which can include wildcards.
+ */
+ MqttTokenAndroid(MqttAndroidClient client,
+ Object userContext, IMqttActionListener listener, String[] topics) {
+ this.client = client;
+ this.userContext = userContext;
+ this.listener = listener;
+ this.topics = topics;
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#waitForCompletion()
+ */
+ @Override
+ public void waitForCompletion() throws MqttException, MqttSecurityException {
+ synchronized (waitObject) {
+ try {
+ waitObject.wait();
+ }
+ catch (InterruptedException e) {
+ // do nothing
+ }
+ }
+ if (pendingException != null) {
+ throw pendingException;
+ }
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#waitForCompletion(long)
+ */
+ @Override
+ public void waitForCompletion(long timeout) throws MqttException,
+ MqttSecurityException {
+ synchronized (waitObject) {
+ try {
+ waitObject.wait(timeout);
+ }
+ catch (InterruptedException e) {
+ // do nothing
+ }
+ if (!isComplete) {
+ throw new MqttException(MqttException.REASON_CODE_CLIENT_TIMEOUT);
+ }
+ if (pendingException != null) {
+ throw pendingException;
+ }
+ }
+ }
+
+ /**
+ * notify successful completion of the operation
+ */
+ void notifyComplete() {
+ synchronized (waitObject) {
+ isComplete = true;
+ waitObject.notifyAll();
+ if (listener != null) {
+ listener.onSuccess(this);
+ }
+ }
+ }
+
+ /**
+ * notify unsuccessful completion of the operation
+ */
+ void notifyFailure(Throwable exception) {
+ synchronized (waitObject) {
+ isComplete = true;
+ if (exception instanceof MqttException) {
+ pendingException = (MqttException) exception;
+ }
+ else {
+ pendingException = new MqttException(exception);
+ }
+ waitObject.notifyAll();
+ if (exception instanceof MqttException) {
+ lastException = (MqttException) exception;
+ }
+ if (listener != null) {
+ listener.onFailure(this, exception);
+ }
+ }
+
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#isComplete()
+ */
+ @Override
+ public boolean isComplete() {
+ return isComplete;
+ }
+
+ void setComplete(boolean complete) {
+ isComplete = complete;
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#getException()
+ */
+ @Override
+ public MqttException getException() {
+ return lastException;
+ }
+
+ void setException(MqttException exception) {
+ lastException = exception;
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#getClient()
+ */
+ @Override
+ public IMqttAsyncClient getClient() {
+ return client;
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#setActionCallback(IMqttActionListener)
+ */
+ @Override
+ public void setActionCallback(IMqttActionListener listener) {
+ this.listener = listener;
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#getActionCallback()
+ */
+ @Override
+ public IMqttActionListener getActionCallback() {
+ return listener;
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#getTopics()
+ */
+ @Override
+ public String[] getTopics() {
+ return topics;
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#setUserContext(Object)
+ */
+ @Override
+ public void setUserContext(Object userContext) {
+ this.userContext = userContext;
+
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#getUserContext()
+ */
+ @Override
+ public Object getUserContext() {
+ return userContext;
+ }
+
+ void setDelegate(IMqttToken delegate) {
+ this.delegate = delegate;
+ }
+
+ /**
+ * @see org.eclipse.paho.client.mqttv3.IMqttToken#getMessageId()
+ */
+ @Override
+ public int getMessageId() {
+ return (delegate != null) ? delegate.getMessageId() : 0;
+ }
+
+ @Override
+ public MqttWireMessage getResponse() {
+ return delegate.getResponse();
+ }
+
+ @Override
+ public boolean getSessionPresent() {
+ return delegate.getSessionPresent();
+ }
+
+ @Override
+ public int[] getGrantedQos() {
+ return delegate.getGrantedQos();
+ }
+
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttTraceHandler.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttTraceHandler.java
new file mode 100644
index 00000000..eb38dc51
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/MqttTraceHandler.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service;
+
+/**
+ * Interface for simple trace handling, pass the trace message to trace
+ * callback.
+ *
+ */
+
+public interface MqttTraceHandler {
+
+ /**
+ * Trace debugging information
+ *
+ * @param tag
+ * identifier for the source of the trace
+ * @param message
+ * the text to be traced
+ */
+ public abstract void traceDebug(String source, String message);
+
+ /**
+ * Trace error information
+ *
+ * @param tag
+ * identifier for the source of the trace
+ * @param message
+ * the text to be traced
+ */
+ public abstract void traceError(String source, String message);
+
+ /**
+ * trace exceptions
+ *
+ * @param tag
+ * identifier for the source of the trace
+ * @param message
+ * the text to be traced
+ * @param e
+ * the exception
+ */
+ public abstract void traceException(String source, String message,
+ Exception e);
+
+}
\ No newline at end of file
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/ParcelableMqttMessage.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/ParcelableMqttMessage.java
new file mode 100644
index 00000000..8ae610fc
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/ParcelableMqttMessage.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service;
+
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ *
+ * A way to flow MqttMessages via Bundles/Intents
+ *
+ *
+ *
+ * An application will probably use this only when receiving a message from a
+ * Service in a Bundle - the necessary code will be something like this :-
+ *
+ *
+ *
+ * private void messageArrivedAction(Bundle data) {
+ * ParcelableMqttMessage message = (ParcelableMqttMessage) data
+ * .getParcelable(MqttServiceConstants.CALLBACK_MESSAGE_PARCEL);
+ * Use the normal {@link MqttMessage} methods on the the message object.
+ * }
+ *
+ *
+ *
+ *
+ *
+ *
+ * It is unlikely that an application will directly use the methods which are
+ * specific to this class.
+ *
+ */
+
+// Relies on knowledge of MqttMessage internals, unfortunately
+class ParcelableMqttMessage extends MqttMessage implements Parcelable {
+
+ String messageId = null;
+
+ ParcelableMqttMessage(MqttMessage original) {
+ super(original.getPayload());
+ setQos(original.getQos());
+ setRetained(original.isRetained());
+ setDuplicate(original.isDuplicate());
+ }
+
+ ParcelableMqttMessage(Parcel parcel) {
+ super(parcel.createByteArray());
+ setQos(parcel.readInt());
+ boolean[] flags = parcel.createBooleanArray();
+ setRetained(flags[0]);
+ setDuplicate(flags[1]);
+ messageId = parcel.readString();
+ }
+
+ /**
+ * @return the messageId
+ */
+ public String getMessageId() {
+ return messageId;
+ }
+
+ /**
+ * Describes the contents of this object
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Writes the contents of this object to a parcel
+ *
+ * @param parcel
+ * The parcel to write the data to.
+ * @param flags
+ * this parameter is ignored
+ */
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeByteArray(getPayload());
+ parcel.writeInt(getQos());
+ parcel.writeBooleanArray(new boolean[]{isRetained(), isDuplicate()});
+ parcel.writeString(messageId);
+ }
+
+ /**
+ * A creator which creates the message object from a parcel
+ */
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+
+ /**
+ * Creates a message from the parcel object
+ */
+ @Override
+ public ParcelableMqttMessage createFromParcel(Parcel parcel) {
+ return new ParcelableMqttMessage(parcel);
+ }
+
+ /**
+ * creates an array of type {@link ParcelableMqttMessage}[]
+ *
+ */
+ @Override
+ public ParcelableMqttMessage[] newArray(int size) {
+ return new ParcelableMqttMessage[size];
+ }
+ };
+}
\ No newline at end of file
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/Status.java b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/Status.java
new file mode 100644
index 00000000..dadde368
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/Status.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 1999, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+package org.eclipse.paho.android.service;
+
+/**
+ * Enumeration representing the success or failure of an operation
+ */
+enum Status {
+ /**
+ * Indicates that the operation succeeded
+ */
+ OK,
+
+ /**
+ * Indicates that the operation failed
+ */
+ ERROR,
+
+ /**
+ * Indicates that the operation's result may be returned asynchronously
+ */
+ NO_RESULT
+}
diff --git a/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/package.html b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/package.html
new file mode 100644
index 00000000..68f03ccd
--- /dev/null
+++ b/org.eclipse.paho.android.service/org.eclipse.paho.android.service/src/org/eclipse/paho/android/service/package.html
@@ -0,0 +1,40 @@
+
+ Contains a set of classes which allow an Android App to communicate
+ with an MQTT server using an Android Service.
+
+ The Android environment requires that a Service is used to support
+ such things as a long-lasting network connection. This
+ package provides the classes need to implement such a Service to
+ interact with MQTT, together with an implementation of the
+ IMqttAsyncClient interface which uses the Service to implement the
+ interface operations.
+
+
The Service returns results via the Android Intent mechanism.
+ The MqttAndroidClient class shows how the interaction is
+ managed
+
+ Note: An App which uses this service must include the
+ appropriate Service tag in its manifest - e.g.
+
+ ‹!-- Mqtt Service --›
+ ‹service android:name="com.ibm.android.service.MqttService" /›
+
+
+
Note regarding network connectivity
+
+The service does not attempt to track network state and automatically reconnect to MQTT servers as connectivity is lost and regained
+While this is clearly possible, determining appropriate behaviour presents certain challenges.
+Any maintainer adding support for such functionality should look to providing a BroadcastReceiver for
+ConnectivityManager.CONNECTIVITY_ACTION probably as part of the MqttService object.
+Changes in network connectivity would then need to be notified to the relevant MqttConnection objects.
+
+
+The behaviour in response to changes in network connectivity would need to be considered in some detail - for example :-
+
+
Should the behaviour differ for CleanSession=true vs CleanSession=false?
+Remember that CleanSession=false will preserve subscriptions and messages with QOS > 0 should not be lost.
+This would not be true for CleanSession=true;
+
What is the meaning of the isConnected() in this context?
+
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER
+THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE,
+REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE
+OF THIS AGREEMENT.
+
+
1. DEFINITIONS
+
+
"Contribution" means:
+
+
a)
+in the case of the initial Contributor, the initial code and documentation
+distributed under this Agreement, and
+b) in the case of each subsequent Contributor:
+
+
i)
+changes to the Program, and
+
+
ii)
+additions to the Program;
+
+
where
+such changes and/or additions to the Program originate from and are distributed
+by that particular Contributor. A Contribution 'originates' from a Contributor
+if it was added to the Program by such Contributor itself or anyone acting on
+such Contributor's behalf. Contributions do not include additions to the
+Program which: (i) are separate modules of software distributed in conjunction
+with the Program under their own license agreement, and (ii) are not derivative
+works of the Program.
+
+
"Contributor" means any person or
+entity that distributes the Program.
+
+
"Licensed Patents " mean patent
+claims licensable by a Contributor which are necessarily infringed by the use
+or sale of its Contribution alone or when combined with the Program.
+
+
"Program" means the Contributions
+distributed in accordance with this Agreement.
+
+
"Recipient" means anyone who
+receives the Program under this Agreement, including all Contributors.
+
+
2. GRANT OF RIGHTS
+
+
a)
+Subject to the terms of this Agreement, each Contributor hereby grants Recipient
+a non-exclusive, worldwide, royalty-free copyright license toreproduce, prepare derivative works of, publicly
+display, publicly perform, distribute and sublicense the Contribution of such
+Contributor, if any, and such derivative works, in source code and object code
+form.
+
+
b)
+Subject to the terms of this Agreement, each Contributor hereby grants
+Recipient a non-exclusive, worldwide,royalty-free
+patent license under Licensed Patents to make, use, sell, offer to sell, import
+and otherwise transfer the Contribution of such Contributor, if any, in source
+code and object code form. This patent license shall apply to the combination
+of the Contribution and the Program if, at the time the Contribution is added
+by the Contributor, such addition of the Contribution causes such combination
+to be covered by the Licensed Patents. The patent license shall not apply to
+any other combinations which include the Contribution. No hardware per se is
+licensed hereunder.
+
+
c)
+Recipient understands that although each Contributor grants the licenses to its
+Contributions set forth herein, no assurances are provided by any Contributor
+that the Program does not infringe the patent or other intellectual property
+rights of any other entity. Each Contributor disclaims any liability to Recipient
+for claims brought by any other entity based on infringement of intellectual
+property rights or otherwise. As a condition to exercising the rights and
+licenses granted hereunder, each Recipient hereby assumes sole responsibility
+to secure any other intellectual property rights needed, if any. For example,
+if a third party patent license is required to allow Recipient to distribute
+the Program, it is Recipient's responsibility to acquire that license before
+distributing the Program.
+
+
d)
+Each Contributor represents that to its knowledge it has sufficient copyright
+rights in its Contribution, if any, to grant the copyright license set forth in
+this Agreement.
+
+
3. REQUIREMENTS
+
+
A Contributor may choose to distribute the
+Program in object code form under its own license agreement, provided that:
+
+
+
a)
+it complies with the terms and conditions of this Agreement; and
+
+
b)
+its license agreement:
+
+
i)
+effectively disclaims on behalf of all Contributors all warranties and
+conditions, express and implied, including warranties or conditions of title
+and non-infringement, and implied warranties or conditions of merchantability
+and fitness for a particular purpose;
+
+
ii)
+effectively excludes on behalf of all Contributors all liability for damages,
+including direct, indirect, special, incidental and consequential damages, such
+as lost profits;
+
+
iii)
+states that any provisions which differ from this Agreement are offered by that
+Contributor alone and not by any other party; and
+
+
iv)
+states that source code for the Program is available from such Contributor, and
+informs licensees how to obtain it in a reasonable manner on or through a
+medium customarily used for software exchange.
+
+
When the Program is made available in source
+code form:
+
+
a)
+it must be made available under this Agreement; and
+
+
b) a
+copy of this Agreement must be included with each copy of the Program.
+
+
Contributors may not remove or alter any
+copyright notices contained within the Program.
+
+
Each Contributor must identify itself as the
+originator of its Contribution, if any, in a manner that reasonably allows
+subsequent Recipients to identify the originator of the Contribution.
+
+
4. COMMERCIAL DISTRIBUTION
+
+
Commercial distributors of software may
+accept certain responsibilities with respect to end users, business partners
+and the like. While this license is intended to facilitate the commercial use
+of the Program, the Contributor who includes the Program in a commercial
+product offering should do so in a manner which does not create potential
+liability for other Contributors. Therefore, if a Contributor includes the
+Program in a commercial product offering, such Contributor ("Commercial
+Contributor") hereby agrees to defend and indemnify every other
+Contributor ("Indemnified Contributor") against any losses, damages and
+costs (collectively "Losses") arising from claims, lawsuits and other
+legal actions brought by a third party against the Indemnified Contributor to
+the extent caused by the acts or omissions of such Commercial Contributor in
+connection with its distribution of the Program in a commercial product
+offering. The obligations in this section do not apply to any claims or Losses
+relating to any actual or alleged intellectual property infringement. In order
+to qualify, an Indemnified Contributor must: a) promptly notify the Commercial
+Contributor in writing of such claim, and b) allow the Commercial Contributor
+to control, and cooperate with the Commercial Contributor in, the defense and
+any related settlement negotiations. The Indemnified Contributor may participate
+in any such claim at its own expense.
+
+
For example, a Contributor might include the
+Program in a commercial product offering, Product X. That Contributor is then a
+Commercial Contributor. If that Commercial Contributor then makes performance
+claims, or offers warranties related to Product X, those performance claims and
+warranties are such Commercial Contributor's responsibility alone. Under this
+section, the Commercial Contributor would have to defend claims against the
+other Contributors related to those performance claims and warranties, and if a
+court requires any other Contributor to pay any damages as a result, the
+Commercial Contributor must pay those damages.
+
+
5. NO WARRANTY
+
+
EXCEPT AS EXPRESSLY SET FORTH IN THIS
+AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING,
+WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
+responsible for determining the appropriateness of using and distributing the
+Program and assumes all risks associated with its exercise of rights under this
+Agreement , including but not limited to the risks and costs of program errors,
+compliance with applicable laws, damage to or loss of data, programs or
+equipment, and unavailability or interruption of operations.
+
+
6. DISCLAIMER OF LIABILITY
+
+
EXCEPT AS EXPRESSLY SET FORTH IN THIS
+AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF
+THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGES.
+
+
7. GENERAL
+
+
If any provision of this Agreement is invalid
+or unenforceable under applicable law, it shall not affect the validity or
+enforceability of the remainder of the terms of this Agreement, and without
+further action by the parties hereto, such provision shall be reformed to the
+minimum extent necessary to make such provision valid and enforceable.
+
+
If Recipient institutes patent litigation
+against any entity (including a cross-claim or counterclaim in a lawsuit)
+alleging that the Program itself (excluding combinations of the Program with
+other software or hardware) infringes such Recipient's patent(s), then such
+Recipient's rights granted under Section 2(b) shall terminate as of the date
+such litigation is filed.
+
+
All Recipient's rights under this Agreement
+shall terminate if it fails to comply with any of the material terms or
+conditions of this Agreement and does not cure such failure in a reasonable
+period of time after becoming aware of such noncompliance. If all Recipient's
+rights under this Agreement terminate, Recipient agrees to cease use and
+distribution of the Program as soon as reasonably practicable. However,
+Recipient's obligations under this Agreement and any licenses granted by
+Recipient relating to the Program shall continue and survive.
+
+
Everyone is permitted to copy and distribute
+copies of this Agreement, but in order to avoid inconsistency the Agreement is
+copyrighted and may only be modified in the following manner. The Agreement
+Steward reserves the right to publish new versions (including revisions) of
+this Agreement from time to time. No one other than the Agreement Steward has
+the right to modify this Agreement. The Eclipse Foundation is the initial
+Agreement Steward. The Eclipse Foundation may assign the responsibility to
+serve as the Agreement Steward to a suitable separate entity. Each new version
+of the Agreement will be given a distinguishing version number. The Program
+(including Contributions) may always be distributed subject to the version of
+the Agreement under which it was received. In addition, after a new version of
+the Agreement is published, Contributor may elect to distribute the Program
+(including its Contributions) under the new version. Except as expressly stated
+in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to
+the intellectual property of any Contributor under this Agreement, whether
+expressly, by implication, estoppel or otherwise. All rights in the Program not
+expressly granted under this Agreement are reserved.
+
+
This Agreement is governed by the laws of the
+State of New York and the intellectual property laws of the United States of
+America. No party to this Agreement will bring a legal action under this
+Agreement more than one year after the cause of action arose. Each party waives
+its rights to a jury trial in any resulting litigation.
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/org.eclipse.paho.client.eclipse.feature/feature.properties b/org.eclipse.paho.client.eclipse.feature/feature.properties
new file mode 100644
index 00000000..c75b84b1
--- /dev/null
+++ b/org.eclipse.paho.client.eclipse.feature/feature.properties
@@ -0,0 +1,155 @@
+###############################################################################
+# Copyright (c) 2009, 2012 IBM Corp.
+# 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
+###############################################################################
+
+featureName=Eclipse Paho MQTT Client Tester View Feature
+providerName=Eclipse.org - Paho
+
+# description property - text of the "Feature Descrption"
+description=\
+Eclipse Paho MQTT Client Exerciser plugin\n
+################ end of description property ##################################
+
+# "copyright" property - text of the "Feature Update Copyright"
+copyright=\
+Copyright 2009, 2012 IBM Corp. \n\
+All rights reserved. This program and the accompanying materials\n\
+are made available under the terms of the Eclipse Public License v1.0\n\
+which accompanies this distribution, and is available at\n\
+http://www.eclipse.org/legal/epl-v10.html\n
+################ end of copyright property ####################################
+
+# "licenseURL" property - URL of the "Feature License"
+# do not translate value - just change to point to a locale-specific HTML page
+licenseURL=license.html
+
+# "license" property - text of the "Feature Update License"
+# should be plain text version of license agreement pointed to be "licenseURL"
+license=\
+Eclipse Foundation Software User Agreement\n\
+April 14, 2010\n\
+\n\
+Usage Of Content\n\
+\n\
+THE ECLIPSE FOUNDATION MAKES AVAILABLE SOFTWARE, DOCUMENTATION, INFORMATION AND/OR\n\
+OTHER MATERIALS FOR OPEN SOURCE PROJECTS (COLLECTIVELY "CONTENT").\n\
+USE OF THE CONTENT IS GOVERNED BY THE TERMS AND CONDITIONS OF THIS\n\
+AGREEMENT AND/OR THE TERMS AND CONDITIONS OF LICENSE AGREEMENTS OR\n\
+NOTICES INDICATED OR REFERENCED BELOW. BY USING THE CONTENT, YOU\n\
+AGREE THAT YOUR USE OF THE CONTENT IS GOVERNED BY THIS AGREEMENT\n\
+AND/OR THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS\n\
+OR NOTICES INDICATED OR REFERENCED BELOW. IF YOU DO NOT AGREE TO THE\n\
+TERMS AND CONDITIONS OF THIS AGREEMENT AND THE TERMS AND CONDITIONS\n\
+OF ANY APPLICABLE LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED\n\
+BELOW, THEN YOU MAY NOT USE THE CONTENT.\n\
+\n\
+Applicable Licenses\n\
+\n\
+Unless otherwise indicated, all Content made available by the\n\
+Eclipse Foundation is provided to you under the terms and conditions of\n\
+the Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is\n\
+provided with this Content and is also available at http://www.eclipse.org/legal/epl-v10.html.\n\
+For purposes of the EPL, "Program" will mean the Content.\n\
+\n\
+Content includes, but is not limited to, source code, object code,\n\
+documentation and other files maintained in the Eclipse Foundation source code\n\
+repository ("Repository") in software modules ("Modules") and made available\n\
+as downloadable archives ("Downloads").\n\
+\n\
+ - Content may be structured and packaged into modules to facilitate delivering,\n\
+ extending, and upgrading the Content. Typical modules may include plug-ins ("Plug-ins"),\n\
+ plug-in fragments ("Fragments"), and features ("Features").\n\
+ - Each Plug-in or Fragment may be packaged as a sub-directory or JAR (Java(TM) ARchive)\n\
+ in a directory named "plugins".\n\
+ - A Feature is a bundle of one or more Plug-ins and/or Fragments and associated material.\n\
+ Each Feature may be packaged as a sub-directory in a directory named "features".\n\
+ Within a Feature, files named "feature.xml" may contain a list of the names and version\n\
+ numbers of the Plug-ins and/or Fragments associated with that Feature.\n\
+ - Features may also include other Features ("Included Features"). Within a Feature, files\n\
+ named "feature.xml" may contain a list of the names and version numbers of Included Features.\n\
+\n\
+The terms and conditions governing Plug-ins and Fragments should be\n\
+contained in files named "about.html" ("Abouts"). The terms and\n\
+conditions governing Features and Included Features should be contained\n\
+in files named "license.html" ("Feature Licenses"). Abouts and Feature\n\
+Licenses may be located in any directory of a Download or Module\n\
+including, but not limited to the following locations:\n\
+\n\
+ - The top-level (root) directory\n\
+ - Plug-in and Fragment directories\n\
+ - Inside Plug-ins and Fragments packaged as JARs\n\
+ - Sub-directories of the directory named "src" of certain Plug-ins\n\
+ - Feature directories\n\
+\n\
+Note: if a Feature made available by the Eclipse Foundation is installed using the\n\
+Provisioning Technology (as defined below), you must agree to a license ("Feature \n\
+Update License") during the installation process. If the Feature contains\n\
+Included Features, the Feature Update License should either provide you\n\
+with the terms and conditions governing the Included Features or inform\n\
+you where you can locate them. Feature Update Licenses may be found in\n\
+the "license" property of files named "feature.properties" found within a Feature.\n\
+Such Abouts, Feature Licenses, and Feature Update Licenses contain the\n\
+terms and conditions (or references to such terms and conditions) that\n\
+govern your use of the associated Content in that directory.\n\
+\n\
+THE ABOUTS, FEATURE LICENSES, AND FEATURE UPDATE LICENSES MAY REFER\n\
+TO THE EPL OR OTHER LICENSE AGREEMENTS, NOTICES OR TERMS AND CONDITIONS.\n\
+SOME OF THESE OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT ARE NOT LIMITED TO):\n\
+\n\
+ - Common Public License Version 1.0 (available at http://www.eclipse.org/legal/cpl-v10.html)\n\
+ - Apache Software License 1.1 (available at http://www.apache.org/licenses/LICENSE)\n\
+ - Apache Software License 2.0 (available at http://www.apache.org/licenses/LICENSE-2.0)\n\
+ - Metro Link Public License 1.00 (available at http://www.opengroup.org/openmotif/supporters/metrolink/license.html)\n\
+ - Mozilla Public License Version 1.1 (available at http://www.mozilla.org/MPL/MPL-1.1.html)\n\
+\n\
+IT IS YOUR OBLIGATION TO READ AND ACCEPT ALL SUCH TERMS AND CONDITIONS PRIOR\n\
+TO USE OF THE CONTENT. If no About, Feature License, or Feature Update License\n\
+is provided, please contact the Eclipse Foundation to determine what terms and conditions\n\
+govern that particular Content.\n\
+\n\
+\n\Use of Provisioning Technology\n\
+\n\
+The Eclipse Foundation makes available provisioning software, examples of which include,\n\
+but are not limited to, p2 and the Eclipse Update Manager ("Provisioning Technology") for\n\
+the purpose of allowing users to install software, documentation, information and/or\n\
+other materials (collectively "Installable Software"). This capability is provided with\n\
+the intent of allowing such users to install, extend and update Eclipse-based products.\n\
+Information about packaging Installable Software is available at\n\
+http://eclipse.org/equinox/p2/repository_packaging.html ("Specification").\n\
+\n\
+You may use Provisioning Technology to allow other parties to install Installable Software.\n\
+You shall be responsible for enabling the applicable license agreements relating to the\n\
+Installable Software to be presented to, and accepted by, the users of the Provisioning Technology\n\
+in accordance with the Specification. By using Provisioning Technology in such a manner and\n\
+making it available in accordance with the Specification, you further acknowledge your\n\
+agreement to, and the acquisition of all necessary rights to permit the following:\n\
+\n\
+ 1. A series of actions may occur ("Provisioning Process") in which a user may execute\n\
+ the Provisioning Technology on a machine ("Target Machine") with the intent of installing,\n\
+ extending or updating the functionality of an Eclipse-based product.\n\
+ 2. During the Provisioning Process, the Provisioning Technology may cause third party\n\
+ Installable Software or a portion thereof to be accessed and copied to the Target Machine.\n\
+ 3. Pursuant to the Specification, you will provide to the user the terms and conditions that\n\
+ govern the use of the Installable Software ("Installable Software Agreement") and such\n\
+ Installable Software Agreement shall be accessed from the Target Machine in accordance\n\
+ with the Specification. Such Installable Software Agreement must inform the user of the\n\
+ terms and conditions that govern the Installable Software and must solicit acceptance by\n\
+ the end user in the manner prescribed in such Installable Software Agreement. Upon such\n\
+ indication of agreement by the user, the provisioning Technology will complete installation\n\
+ of the Installable Software.\n\
+\n\
+Cryptography\n\
+\n\
+Content may contain encryption software. The country in which you are\n\
+currently may have restrictions on the import, possession, and use,\n\
+and/or re-export to another country, of encryption software. BEFORE\n\
+using any encryption software, please check the country's laws,\n\
+regulations and policies concerning the import, possession, or use, and\n\
+re-export of encryption software, to see if this is permitted.\n\
+\n\
+Java and all Java-based trademarks are trademarks of Oracle Corporation in the United States, other countries, or both.\n
+########### end of license property ##########################################
diff --git a/org.eclipse.paho.client.eclipse.feature/feature.xml b/org.eclipse.paho.client.eclipse.feature/feature.xml
new file mode 100644
index 00000000..c2048c5b
--- /dev/null
+++ b/org.eclipse.paho.client.eclipse.feature/feature.xml
@@ -0,0 +1,34 @@
+
+
+
+
+ %description
+
+
+
+ %copyright
+
+
+
+ %license
+
+
+
+
+
+
+
diff --git a/org.eclipse.paho.client.eclipse.feature/license.html b/org.eclipse.paho.client.eclipse.feature/license.html
new file mode 100644
index 00000000..f19c483b
--- /dev/null
+++ b/org.eclipse.paho.client.eclipse.feature/license.html
@@ -0,0 +1,108 @@
+
+
+
+
+
+Eclipse Foundation Software User Agreement
+
+
+
+
Eclipse Foundation Software User Agreement
+
February 1, 2011
+
+
Usage Of Content
+
+
THE ECLIPSE FOUNDATION MAKES AVAILABLE SOFTWARE, DOCUMENTATION, INFORMATION AND/OR OTHER MATERIALS FOR OPEN SOURCE PROJECTS
+ (COLLECTIVELY "CONTENT"). USE OF THE CONTENT IS GOVERNED BY THE TERMS AND CONDITIONS OF THIS AGREEMENT AND/OR THE TERMS AND
+ CONDITIONS OF LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW. BY USING THE CONTENT, YOU AGREE THAT YOUR USE
+ OF THE CONTENT IS GOVERNED BY THIS AGREEMENT AND/OR THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR
+ NOTICES INDICATED OR REFERENCED BELOW. IF YOU DO NOT AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT AND THE TERMS AND
+ CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW, THEN YOU MAY NOT USE THE CONTENT.
+
+
Applicable Licenses
+
+
Unless otherwise indicated, all Content made available by the Eclipse Foundation is provided to you under the terms and conditions of the Eclipse Public License Version 1.0
+ ("EPL"). A copy of the EPL is provided with this Content and is also available at http://www.eclipse.org/legal/epl-v10.html.
+ For purposes of the EPL, "Program" will mean the Content.
+
+
Content includes, but is not limited to, source code, object code, documentation and other files maintained in the Eclipse Foundation source code
+ repository ("Repository") in software modules ("Modules") and made available as downloadable archives ("Downloads").
+
+
+
Content may be structured and packaged into modules to facilitate delivering, extending, and upgrading the Content. Typical modules may include plug-ins ("Plug-ins"), plug-in fragments ("Fragments"), and features ("Features").
+
Each Plug-in or Fragment may be packaged as a sub-directory or JAR (Java™ ARchive) in a directory named "plugins".
+
A Feature is a bundle of one or more Plug-ins and/or Fragments and associated material. Each Feature may be packaged as a sub-directory in a directory named "features". Within a Feature, files named "feature.xml" may contain a list of the names and version numbers of the Plug-ins
+ and/or Fragments associated with that Feature.
+
Features may also include other Features ("Included Features"). Within a Feature, files named "feature.xml" may contain a list of the names and version numbers of Included Features.
+
+
+
The terms and conditions governing Plug-ins and Fragments should be contained in files named "about.html" ("Abouts"). The terms and conditions governing Features and
+Included Features should be contained in files named "license.html" ("Feature Licenses"). Abouts and Feature Licenses may be located in any directory of a Download or Module
+including, but not limited to the following locations:
+
+
+
The top-level (root) directory
+
Plug-in and Fragment directories
+
Inside Plug-ins and Fragments packaged as JARs
+
Sub-directories of the directory named "src" of certain Plug-ins
+
Feature directories
+
+
+
Note: if a Feature made available by the Eclipse Foundation is installed using the Provisioning Technology (as defined below), you must agree to a license ("Feature Update License") during the
+installation process. If the Feature contains Included Features, the Feature Update License should either provide you with the terms and conditions governing the Included Features or
+inform you where you can locate them. Feature Update Licenses may be found in the "license" property of files named "feature.properties" found within a Feature.
+Such Abouts, Feature Licenses, and Feature Update Licenses contain the terms and conditions (or references to such terms and conditions) that govern your use of the associated Content in
+that directory.
+
+
THE ABOUTS, FEATURE LICENSES, AND FEATURE UPDATE LICENSES MAY REFER TO THE EPL OR OTHER LICENSE AGREEMENTS, NOTICES OR TERMS AND CONDITIONS. SOME OF THESE
+OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT ARE NOT LIMITED TO):
IT IS YOUR OBLIGATION TO READ AND ACCEPT ALL SUCH TERMS AND CONDITIONS PRIOR TO USE OF THE CONTENT. If no About, Feature License, or Feature Update License is provided, please
+contact the Eclipse Foundation to determine what terms and conditions govern that particular Content.
+
+
+
Use of Provisioning Technology
+
+
The Eclipse Foundation makes available provisioning software, examples of which include, but are not limited to, p2 and the Eclipse
+ Update Manager ("Provisioning Technology") for the purpose of allowing users to install software, documentation, information and/or
+ other materials (collectively "Installable Software"). This capability is provided with the intent of allowing such users to
+ install, extend and update Eclipse-based products. Information about packaging Installable Software is available at http://eclipse.org/equinox/p2/repository_packaging.html
+ ("Specification").
+
+
You may use Provisioning Technology to allow other parties to install Installable Software. You shall be responsible for enabling the
+ applicable license agreements relating to the Installable Software to be presented to, and accepted by, the users of the Provisioning Technology
+ in accordance with the Specification. By using Provisioning Technology in such a manner and making it available in accordance with the
+ Specification, you further acknowledge your agreement to, and the acquisition of all necessary rights to permit the following:
+
+
+
A series of actions may occur ("Provisioning Process") in which a user may execute the Provisioning Technology
+ on a machine ("Target Machine") with the intent of installing, extending or updating the functionality of an Eclipse-based
+ product.
+
During the Provisioning Process, the Provisioning Technology may cause third party Installable Software or a portion thereof to be
+ accessed and copied to the Target Machine.
+
Pursuant to the Specification, you will provide to the user the terms and conditions that govern the use of the Installable
+ Software ("Installable Software Agreement") and such Installable Software Agreement shall be accessed from the Target
+ Machine in accordance with the Specification. Such Installable Software Agreement must inform the user of the terms and conditions that govern
+ the Installable Software and must solicit acceptance by the end user in the manner prescribed in such Installable Software Agreement. Upon such
+ indication of agreement by the user, the provisioning Technology will complete installation of the Installable Software.
+
+
+
Cryptography
+
+
Content may contain encryption software. The country in which you are currently may have restrictions on the import, possession, and use, and/or re-export to
+ another country, of encryption software. BEFORE using any encryption software, please check the country's laws, regulations and policies concerning the import,
+ possession, or use, and re-export of encryption software, to see if this is permitted.
+
+
Java and all Java-based trademarks are trademarks of Oracle Corporation in the United States, other countries, or both.
+
+
diff --git a/org.eclipse.paho.client.eclipse.feature/pom.xml b/org.eclipse.paho.client.eclipse.feature/pom.xml
new file mode 100644
index 00000000..985963a7
--- /dev/null
+++ b/org.eclipse.paho.client.eclipse.feature/pom.xml
@@ -0,0 +1,21 @@
+
+ 4.0.0
+
+
+ org.eclipse.paho
+ java-parent
+ 1.0.2
+
+
+ org.eclipse.paho.client.eclipse.feature
+ eclipse-feature
+
+
+
+
+ org.eclipse.tycho
+ tycho-maven-plugin
+
+
+
+
\ No newline at end of file
diff --git a/org.eclipse.paho.client.eclipse.view/.gitignore b/org.eclipse.paho.client.eclipse.view/.gitignore
new file mode 100644
index 00000000..e4715cf3
--- /dev/null
+++ b/org.eclipse.paho.client.eclipse.view/.gitignore
@@ -0,0 +1,2 @@
+.settings/*
+bin/*
diff --git a/org.eclipse.paho.client.eclipse.view/META-INF/MANIFEST.MF b/org.eclipse.paho.client.eclipse.view/META-INF/MANIFEST.MF
new file mode 100644
index 00000000..0ee1d6b2
--- /dev/null
+++ b/org.eclipse.paho.client.eclipse.view/META-INF/MANIFEST.MF
@@ -0,0 +1,13 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %bundle.name
+Bundle-SymbolicName: org.eclipse.paho.client.eclipse.view;singleton:=true
+Bundle-Version: 1.0.2
+Bundle-Activator: org.eclipse.paho.client.eclipse.view.Activator
+Bundle-Vendor: %bundle.provider
+Bundle-Localization: plugin
+Bundle-ActivationPolicy: lazy
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime
+Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+Import-Package: org.eclipse.paho.client.mqttv3
diff --git a/org.eclipse.paho.client.eclipse.view/README.md b/org.eclipse.paho.client.eclipse.view/README.md
new file mode 100644
index 00000000..50a31a34
--- /dev/null
+++ b/org.eclipse.paho.client.eclipse.view/README.md
@@ -0,0 +1,102 @@
+**Project** Eclipse Paho MQTT Tester View
+
+**Version** 1.0.3
+
+**Date** 24-04-2013
+
+**Author** Eurotech Inc.
+
+
+# Overview
+
+The Eclipse Paho MQTT Tester View is an Eclipse plugin that provides a user interface for performing MQTT-based messaging tasks within Eclipse.
+
+
+# Installation
+
+(TODO this section is not yet valid)
+
+The plug-in can be installed using the included update site:
+
+ update_site/update.site.zip
+
+1) Unzip the file onto your local machine.
+
+2) In Eclipse, browse to Help -> Install New Software.
+
+3) Add a new local repository by clicking on the "Add..." button and browsing to the directory that you unzipped above (should contain a site.xml file). Make sure to name the repository and then click "Ok". You should now be able to select the repository in the "Work with:" drop down menu and the plug-in should appear in the list.
+
+4) Select the plug-in:
+
+ Paho Client Eclipse View Feature
+
+5) Click "Next" and follow the remaining instructions for installing the plug-in.
+
+# Usage
+
+The view can be accessed by navigating to Window -> Show View -> Other..., then expand the "M2M" folder and select "MQTT Tester View". This will open the interface for the MQTT view. The interface consists of three tabs: Connection, Publish, and Subscribe.
+
+**Connection Tab**
+
+This tab is used to connect the MQTT client to a broker. A connection must be established in order to publish and subscribe in the remaining tabs. Here is a brief description of the fields:
+
+ * Broker Address: (Required) The IP address or URL of the broker
+ * Broker Port: (Required) The port number of the broker
+ * Client ID: (Required) A unique identifier to connect with.
+ * Username: A username, if required by the broker.
+ * Password: A password, if required by the broker.
+ * Keep Alive: (Required) The number of seconds between keep alive pings sent to the broker.
+ * Clean Start: Whether or not to maintain subscriptions across disconnects.
+ * LWT Enable: Whether to enable Last Will and Testament (LWT).
+ * LWT Topic: The topic that the broker will publish the LWT on.
+ * LWT Message: The message that the broker will publish for the LWT.
+ * LWT QoS: The quality of service that the LWT will be published on.
+ * LWT Retain: Whether to retain the LWT message.
+
+**Publish Tab**
+
+Used to publish messages to the broker. A message may be a string or a File. Here is a brief description of the fields:
+
+ * Topic: The topic to publish on.
+ * QoS: The quality of service to publish on.
+ * Retain: Whether to retain the message on the broker.
+ * Payload: The payload to publish (if publishing a string).
+ * File: The file to publish (if publishing a file).
+
+**Subscribe Tab**
+
+Used to subscribe and unsubscribe on topics. Once the client is subscribed to a topic, all messages received will be displayed in the log below. Here is a brief description of the fields:
+
+ * Topic: The topic to subscribe on (can include wildcards + and #)
+ * QoS: The quality of service to subscribe on.
+
+
+# Building
+
+The project requires the Plug-in Development Environment (PDE) in order to build. Information about installing the PDE tools can be found at:
+
+ [Eclipse PDE](www.eclipse.org/pde)
+
+The source for the project itself can be found at:
+
+ [Eclipse Git](http://git.eclipse.org/c/paho/org.eclipse.paho.mqtt.esf.git/)
+
+Once the project is imported into Eclipse, it can be build by right clicking on the project and navigating to Export -> Plug-in Development -> Deployable plug-ins and fragments.
+
+
+## Copyright
+
+Copyright (c) 2012 Eurotech Inc. All rights reserved.
+
+
+## License
+
+This project is released under the Eclipse Public License (EPL) version 1.0
+
+
+## Additional Resources
+
+* [Eclipse Paho project](http://www.eclipse.org/paho/)
+
+* [MQTT Community](http://www.mqtt.org)
+
diff --git a/org.eclipse.paho.client.eclipse.view/build.properties b/org.eclipse.paho.client.eclipse.view/build.properties
new file mode 100644
index 00000000..3d1c5d91
--- /dev/null
+++ b/org.eclipse.paho.client.eclipse.view/build.properties
@@ -0,0 +1,13 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ plugin.xml,\
+ icons/,\
+ README.md,\
+ plugin.properties
+additional.bundles = org.eclipse.swt,\
+ org.eclipse.ui,\
+ org.eclipse.core.runtime,\
+ org.eclipse.core.resources,\
+ org.eclipse.paho.client.mqttv3
diff --git a/org.eclipse.paho.client.eclipse.view/icons/mqtt_view.png b/org.eclipse.paho.client.eclipse.view/icons/mqtt_view.png
new file mode 100644
index 00000000..a66fac53
Binary files /dev/null and b/org.eclipse.paho.client.eclipse.view/icons/mqtt_view.png differ
diff --git a/org.eclipse.paho.client.eclipse.view/icons/paho.png b/org.eclipse.paho.client.eclipse.view/icons/paho.png
new file mode 100644
index 00000000..344f1f15
Binary files /dev/null and b/org.eclipse.paho.client.eclipse.view/icons/paho.png differ
diff --git a/org.eclipse.paho.client.eclipse.view/plugin.properties b/org.eclipse.paho.client.eclipse.view/plugin.properties
new file mode 100644
index 00000000..5403b530
--- /dev/null
+++ b/org.eclipse.paho.client.eclipse.view/plugin.properties
@@ -0,0 +1,10 @@
+############################################################################
+# Copyright (c) 2014 IBM Corp.
+# 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
+#
+############################################################################
+bundle.name=Paho MQTT Exerciser
+bundle.provider=Eclipse.org - Paho
diff --git a/org.eclipse.paho.client.eclipse.view/plugin.xml b/org.eclipse.paho.client.eclipse.view/plugin.xml
new file mode 100644
index 00000000..64df8b2e
--- /dev/null
+++ b/org.eclipse.paho.client.eclipse.view/plugin.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/org.eclipse.paho.client.eclipse.view/pom.xml b/org.eclipse.paho.client.eclipse.view/pom.xml
new file mode 100644
index 00000000..7179ae95
--- /dev/null
+++ b/org.eclipse.paho.client.eclipse.view/pom.xml
@@ -0,0 +1,60 @@
+
+
+ 4.0.0
+
+ org.eclipse.paho
+ java-parent
+ 1.0.2
+
+
+ org.eclipse.paho.client.eclipse.view
+ eclipse-plugin
+
+
+
+
+ org.eclipse.tycho
+ tycho-maven-plugin
+
+
+
+ org.eclipse.tycho
+ tycho-compiler-plugin
+ ${tycho.version}
+
+ ${project.build.sourceEncoding}
+ ${java.version}
+ ${java.version}
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 2.4.2
+
+
+ default-copy-resources
+ process-resources
+
+ copy-resources
+
+
+ true
+ ${project.build.outputDirectory}
+
+
+ ../
+
+ about.html
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/Activator.java b/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/Activator.java
new file mode 100644
index 00000000..5c8a08c4
--- /dev/null
+++ b/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/Activator.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2012 Eurotech Inc. All rights reserved.
+ *
+ * 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
+ *
+ * Contributors:
+ * Chad Kienle
+ */
+
+package org.eclipse.paho.client.eclipse.view;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class Activator extends AbstractUIPlugin {
+
+ // The plug-in ID
+ public static final String PLUGIN_ID = "org.eclipse.paho.client.eclipse.view";
+
+ // The shared instance
+ private static Activator plugin;
+
+ /**
+ * The constructor
+ */
+ public Activator() {
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
+ */
+ public void start(BundleContext context) throws Exception {
+ super.start(context);
+ plugin = this;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
+ */
+ public void stop(BundleContext context) throws Exception {
+ plugin = null;
+ super.stop(context);
+ }
+
+ /**
+ * Returns the shared instance
+ *
+ * @return the shared instance
+ */
+ public static Activator getDefault() {
+ return plugin;
+ }
+
+ /**
+ * Returns an image descriptor for the image file at the given
+ * plug-in relative path
+ *
+ * @param path the path
+ * @return the image descriptor
+ */
+ public static ImageDescriptor getImageDescriptor(String path) {
+ return imageDescriptorFromPlugin(PLUGIN_ID, path);
+ }
+}
diff --git a/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/ClientConstants.java b/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/ClientConstants.java
new file mode 100644
index 00000000..0da7f4fd
--- /dev/null
+++ b/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/ClientConstants.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2012 Eurotech Inc. All rights reserved.
+ *
+ * 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
+ *
+ * Contributors:
+ * Chad Kienle
+ */
+
+package org.eclipse.paho.client.eclipse.view;
+
+/**
+ * Constants for widget sizing defaults
+ */
+public class ClientConstants {
+
+ protected static final int BUTTON_WIDTH = 120;
+ protected static final int BUTTON_HEIGHT = 25;
+}
diff --git a/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/Messages.java b/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/Messages.java
new file mode 100644
index 00000000..1a7fe224
--- /dev/null
+++ b/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/Messages.java
@@ -0,0 +1,95 @@
+package org.eclipse.paho.client.eclipse.view;
+
+import org.eclipse.osgi.util.NLS;
+
+public class Messages extends NLS {
+ private static final String BUNDLE_NAME = "org.eclipse.paho.client.eclipse.view.messages"; //$NON-NLS-1$
+ public static String MqttClientView_10;
+ public static String MqttClientView_100;
+ public static String MqttClientView_101;
+ public static String MqttClientView_102;
+ public static String MqttClientView_103;
+ public static String MqttClientView_104;
+ public static String MqttClientView_105;
+ public static String MqttClientView_106;
+ public static String MqttClientView_11;
+ public static String MqttClientView_12;
+ public static String MqttClientView_110;
+ public static String MqttClientView_111;
+ public static String MqttClientView_15;
+ public static String MqttClientView_16;
+ public static String MqttClientView_17;
+ public static String MqttClientView_19;
+ public static String MqttClientView_20;
+ public static String MqttClientView_21;
+ public static String MqttClientView_22;
+ public static String MqttClientView_23;
+ public static String MqttClientView_24;
+ public static String MqttClientView_25;
+ public static String MqttClientView_26;
+ public static String MqttClientView_28;
+ public static String MqttClientView_29;
+ public static String MqttClientView_30;
+ public static String MqttClientView_32;
+ public static String MqttClientView_34;
+ public static String MqttClientView_36;
+ public static String MqttClientView_38;
+ public static String MqttClientView_39;
+ public static String MqttClientView_41;
+ public static String MqttClientView_42;
+ public static String MqttClientView_43;
+ public static String MqttClientView_45;
+ public static String MqttClientView_47;
+ public static String MqttClientView_48;
+ public static String MqttClientView_49;
+ public static String MqttClientView_50;
+ public static String MqttClientView_51;
+ public static String MqttClientView_52;
+ public static String MqttClientView_54;
+ public static String MqttClientView_56;
+ public static String MqttClientView_6;
+ public static String MqttClientView_62;
+ public static String MqttClientView_63;
+ public static String MqttClientView_64;
+ public static String MqttClientView_65;
+ public static String MqttClientView_66;
+ public static String MqttClientView_67;
+ public static String MqttClientView_68;
+ public static String MqttClientView_69;
+ public static String MqttClientView_7;
+ public static String MqttClientView_70;
+ public static String MqttClientView_71;
+ public static String MqttClientView_72;
+ public static String MqttClientView_73;
+ public static String MqttClientView_74;
+ public static String MqttClientView_75;
+ public static String MqttClientView_76;
+ public static String MqttClientView_77;
+ public static String MqttClientView_78;
+ public static String MqttClientView_79;
+ public static String MqttClientView_8;
+ public static String MqttClientView_80;
+ public static String MqttClientView_81;
+ public static String MqttClientView_82;
+ public static String MqttClientView_83;
+ public static String MqttClientView_87;
+ public static String MqttClientView_88;
+ public static String MqttClientView_89;
+ public static String MqttClientView_9;
+ public static String MqttClientView_90;
+ public static String MqttClientView_91;
+ public static String MqttClientView_92;
+ public static String MqttClientView_93;
+ public static String MqttClientView_94;
+ public static String MqttClientView_95;
+ public static String MqttClientView_96;
+ public static String MqttClientView_97;
+ public static String MqttClientView_99;
+ static {
+ // initialize resource bundle
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
diff --git a/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/MqttClientView.java b/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/MqttClientView.java
new file mode 100644
index 00000000..c96e27be
--- /dev/null
+++ b/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/MqttClientView.java
@@ -0,0 +1,862 @@
+/*
+ * Copyright (c) 2012 Eurotech Inc. All rights reserved.
+ *
+ * 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
+ *
+ * Contributors:
+ * Chad Kienle
+ */
+
+package org.eclipse.paho.client.eclipse.view;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.MqttCallback;
+import org.eclipse.paho.client.mqttv3.MqttClient;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
+import org.eclipse.paho.client.mqttv3.MqttTopic;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.TabFolder;
+import org.eclipse.swt.widgets.TabItem;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.part.ViewPart;
+
+/**
+ * An MQTT client view. The top half provides three tabs for Connecting to an MQTT broker, publishing messages,
+ * and subscribing on topics. The bottom half of the view contains a log of all MQTT connections, publications, and
+ * subscription events.
+ */
+public class MqttClientView extends ViewPart implements MqttCallback {
+
+ // The display and parent composite
+ private Display display;
+ private Composite parent;
+
+ // MQTT connection parameters with defaults
+ private static MqttClient mqttClient = null;
+ private static MqttConnectOptions connOpts = null;
+ private static String connectAddress = ""; //$NON-NLS-1$
+ private static int connectPort = 1883;
+ private static String clientId = ""; //$NON-NLS-1$
+ private static short keepAlive = 30;
+ private static boolean cleanStart = true;
+ private static boolean firstConnect = true;
+ private static Boolean connected = new Boolean(false);
+ private static String tmpMsg = null;
+ private static String willTopic = ""; //$NON-NLS-1$
+ private static String willMessage = ""; //$NON-NLS-1$
+ private static int willQos = 0;
+ private static boolean willRetain = false;
+ private static String username = ""; //$NON-NLS-1$
+ private static String password = ""; //$NON-NLS-1$
+
+ // Current publish parameters with defaults
+ private String publishTopic = null;
+ private int publishQos = 0;
+ private boolean retain = false;
+ private byte [] payload = null;
+ private boolean useWill = false;
+
+ // Current subscribe topic and QoS with defaults
+ private String subscribeTopic = null;
+ private int subscribeQos = 0;
+
+ // The text boxes to store the values of the MQTT parameters for connecting/publishing/subscribing
+ private Text subscribeTopicValue;
+ private Text publishTopicValue;
+ private Text publishPayloadValue;
+ private Text publishFileName;
+ private Text brokerAddressValue;
+ private Text brokerPortValue;
+ private Text clientIdValue;
+ private Text keepAliveValue;
+ private Text messageLog;
+ private Text willTopicValue;
+ private Text willMessageValue;
+ private Text usernameValue;
+ private Text passwordValue;
+
+ // The buttons needed to store the state of MQTT parameters for connecting/publishing/subscribing
+ private Button willCheckBox;
+ private Button willRetainCheckBox;
+ private Button cleanStartCheckBox;
+ private Button connectButton;
+ private Button disconnectButton;
+ private Button publishRetainCheckBox;
+
+ // The Groups associate with each tab
+ private Group connectionGroup;
+ private Group publishGroup;
+ private Group subscribeGroup;
+
+ // Quality of Service drop down selection menus
+ private Combo subscribeQosDrop;
+ private Combo publishQosDrop;
+ private Combo willQosDrop;
+
+ /**
+ * Constructor
+ */
+ public MqttClientView() {
+ super();
+ }
+
+ /**
+ * Sets the focus
+ */
+ public void setFocus() {
+ }
+
+ /**
+ * Create the control
+ */
+ public void createPartControl(Composite parent) {
+ // Connection Group
+ this.parent = parent;
+ display = parent.getDisplay();
+ FillLayout masterLayout = new FillLayout();
+ parent.setLayout(masterLayout);
+
+ // Create composite with rows
+ SashForm sashform = new SashForm(parent, SWT.VERTICAL);
+
+ final TabFolder tabFolder = new TabFolder(sashform, SWT.V_SCROLL | SWT.H_SCROLL);
+
+ // Create each tab and set its text, tool tip text, image, and control
+ // Connection tab
+ TabItem one = new TabItem(tabFolder, SWT.NONE);
+ one.setText(Messages.MqttClientView_6);
+ one.setToolTipText(Messages.MqttClientView_7);
+ one.setControl(getConnectionControl(tabFolder));
+
+ // Publish tab
+ TabItem two = new TabItem(tabFolder, SWT.NONE);
+ two.setText(Messages.MqttClientView_8);
+ two.setToolTipText(Messages.MqttClientView_9);
+ two.setControl(getPublishControl(tabFolder));
+
+ // Subscribe tab
+ TabItem three = new TabItem(tabFolder, SWT.NONE);
+ three.setText(Messages.MqttClientView_10);
+ three.setToolTipText(Messages.MqttClientView_11);
+ three.setControl(getSubscribeControl(tabFolder));
+
+ messageLog = new Text(sashform, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
+ messageLog.setFont(new Font(display, new FontData("Courier New", 10, SWT.NORMAL))); //$NON-NLS-1$
+
+ sashform.setWeights(new int[]{2,1});
+ }
+
+ // Listener for LWT enable/disable events
+ private SelectionListener willListener = new SelectionListener() {
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+ public void widgetSelected(SelectionEvent e) {
+ useWill = willCheckBox.getSelection();
+ willTopicValue.setEnabled(useWill);
+ willMessageValue.setEnabled(useWill);
+ willQosDrop.setEnabled(useWill);
+ willRetainCheckBox.setEnabled(useWill);
+ }
+ };
+
+ // Listener for widget selection events
+ private SelectionListener selectListener = new SelectionListener() {
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+ public void widgetSelected(SelectionEvent e) {
+ cleanStart = cleanStartCheckBox.getSelection();
+ updateInfo();
+ }
+ };
+
+ // Listener for connect events
+ private SelectionListener connectListener = new SelectionListener() {
+ public void widgetDefaultSelected(SelectionEvent e) {}
+ public void widgetSelected(SelectionEvent e) {
+ if(e.getSource() == connectButton) {
+ connect();
+ } else if(e.getSource() == disconnectButton) {
+ disconnect();
+ }
+ }
+ };
+
+ // Listener for publish (text) events
+ private SelectionListener publishListener = new SelectionListener() {
+ public void widgetDefaultSelected(SelectionEvent e) {}
+ public void widgetSelected(SelectionEvent e) {
+ publishTopic = publishTopicValue.getText();
+ publishQos = Integer.parseInt(publishQosDrop.getText());
+ retain = publishRetainCheckBox.getSelection();
+ payload = publishPayloadValue.getText().getBytes();
+ publish();
+ }
+ };
+
+ // Listener for publish (file) events
+ private SelectionListener publishFileListener = new SelectionListener() {
+ public void widgetDefaultSelected(SelectionEvent e) {}
+ public void widgetSelected(SelectionEvent e) {
+ publishTopic = publishTopicValue.getText();
+ publishQos = Integer.parseInt(publishQosDrop.getText());
+ retain = publishRetainCheckBox.getSelection();
+ String filename = publishFileName.getText();
+ try {
+ payload = getBytesFromFile(new File(filename));
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ }
+ publish();
+ }
+ };
+
+ // Listener for subscribe events
+ private SelectionListener subscribeListener = new SelectionListener() {
+ public void widgetDefaultSelected(SelectionEvent e) {}
+ public void widgetSelected(SelectionEvent e) {
+ subscribeTopic = subscribeTopicValue.getText();
+ subscribeQos = Integer.parseInt(subscribeQosDrop.getText());
+ subscribe();
+ publishTopicValue.setText(subscribeTopic);
+ }
+ };
+
+ // Listener for unsubscribe events
+ private SelectionListener unsubscribeListener = new SelectionListener() {
+ public void widgetDefaultSelected(SelectionEvent e) {}
+ public void widgetSelected(SelectionEvent e) {
+ subscribeTopic = subscribeTopicValue.getText();
+ unsubscribe();
+ }
+ };
+
+ /**
+ * A parameter's value has changed, update info
+ */
+ public void valueChanged(Text text) {
+ updateInfo();
+ }
+
+ /**
+ * Broker address value has changed, update info
+ */
+ private void addressValueChanged(Text text) {
+ updateInfo();
+ firstConnect = true;
+ }
+
+ /**
+ * Updates the connection information
+ */
+ private void updateInfo() {
+ connectAddress = brokerAddressValue.getText();
+ willTopic = willTopicValue.getText();
+ willMessage = willMessageValue.getText();
+ clientId = clientIdValue.getText();
+ willQos = Integer.parseInt(willQosDrop.getText());
+ willRetain = willRetainCheckBox.getSelection();
+ username = usernameValue.getText();
+ password = passwordValue.getText();
+ try {
+ connectPort = Integer.parseInt(brokerPortValue.getText());
+ } catch (NumberFormatException e) {
+ }
+ try {
+ keepAlive = Short.parseShort(keepAliveValue.getText());
+ } catch (NumberFormatException e) {
+ }
+ }
+
+ /**
+ * Connects to the broker
+ */
+ private void connect() {
+ // Check if the client is currently connected
+ if (!connected) {
+ // Update connection information
+ updateInfo();
+ // Build connection string
+ String connectString = "tcp://" + connectAddress + ":" + connectPort; //$NON-NLS-1$ //$NON-NLS-2$
+ if(clientId == null || clientId.length() < 1) {
+ out(getDate() + Messages.MqttClientView_15 + connectString + Messages.MqttClientView_16);
+ return;
+ }
+ // Instantiate client
+ try {
+ if (firstConnect) {
+ mqttClient = new MqttClient(connectString, clientId);
+ mqttClient.setCallback(this);
+ firstConnect = false;
+ }
+ } catch (MqttException e) {
+ out(getDate() + Messages.MqttClientView_17 + e.getMessage());
+ e.printStackTrace();
+ }
+
+ // Set connection options
+ connOpts = new MqttConnectOptions();
+ connOpts.setCleanSession(cleanStart);
+ connOpts.setConnectionTimeout(30);
+ connOpts.setKeepAliveInterval(keepAlive);
+ if (username.length() > 0 && password.length() > 0) {
+ connOpts.setPassword(password.toCharArray());
+ connOpts.setUserName(username);
+ }
+ if (useWill) {
+ if(willTopic == null || willTopic.equals("")) { //$NON-NLS-1$
+ out(Messages.MqttClientView_19);
+ return;
+ }
+ connOpts.setWill(mqttClient.getTopic(willTopic), willMessage.getBytes(), willQos, willRetain);
+ }
+ // Attempt to connect
+ try {
+ out(getDate() + Messages.MqttClientView_20 + connectString);
+ mqttClient.connect(connOpts);
+
+ connected = true;
+ out(getDate() + Messages.MqttClientView_21 + clientId);
+ } catch (MqttException e) {
+ out(getDate() + Messages.MqttClientView_22 + e.getMessage());
+ e.printStackTrace();
+ }
+ } else {
+ out(Messages.MqttClientView_23);
+ }
+ }
+
+ /**
+ * Disconnects from the broker
+ */
+ private void disconnect() {
+ try {
+ if(connected) {
+ mqttClient.disconnect();
+ out(getDate() + Messages.MqttClientView_24);
+ } else {
+ out(getDate() + Messages.MqttClientView_25);
+ }
+ } catch (MqttException e) {
+ out(getDate() + Messages.MqttClientView_26 + e.getMessage());
+ }
+ connected = false;
+ }
+
+ /**
+ * Publishes a message
+ */
+ private void publish() {
+
+ if(mqttClient != null) {
+ if(publishTopic == null || publishTopic.equals("")) { //$NON-NLS-1$
+ out(Messages.MqttClientView_28);
+ return;
+ }
+ try {
+ MqttTopic topic = mqttClient.getTopic(publishTopic);
+ topic.publish(payload, publishQos, retain);
+ out(getDate() + Messages.MqttClientView_29);
+ out(Messages.MqttClientView_30 + publishTopic + "\"\n" + //$NON-NLS-2$
+ Messages.MqttClientView_32 + publishQos + "\n" + //$NON-NLS-2$
+ Messages.MqttClientView_34 + retain + "\n" + //$NON-NLS-2$
+ Messages.MqttClientView_36 + new String(payload) + "\""); //$NON-NLS-2$
+ } catch (MqttPersistenceException e) {
+ e.printStackTrace();
+ } catch (NullPointerException e) {
+ e.printStackTrace();
+ } catch (MqttException e) {
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ out(Messages.MqttClientView_12);
+ }
+ }
+ }
+
+ /**
+ * Subscribes on a topic
+ */
+ private void subscribe() {
+ if(mqttClient != null) {
+ try {
+ String [] topicArray = {subscribeTopic};
+ int [] qosArray = {subscribeQos};
+ mqttClient.subscribe(topicArray, qosArray);
+ out(getDate() + Messages.MqttClientView_38);
+ out(Messages.MqttClientView_39 + subscribeTopic + "\"\n" + //$NON-NLS-2$
+ Messages.MqttClientView_41 + subscribeQos);
+ } catch (MqttPersistenceException e) {
+ e.printStackTrace();
+ } catch (NullPointerException e) {
+ e.printStackTrace();
+ } catch (MqttException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Unsubscribes from a topic
+ */
+ private void unsubscribe() {
+ if(mqttClient != null) {
+ try {
+ String [] topicArray = {subscribeTopic};
+ mqttClient.unsubscribe(topicArray);
+ out(getDate() + Messages.MqttClientView_42);
+ out(Messages.MqttClientView_43 + subscribeTopic + "\""); //$NON-NLS-2$
+ } catch (MqttPersistenceException e) {
+ e.printStackTrace();
+ } catch (NullPointerException e) {
+ e.printStackTrace();
+ } catch (MqttException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Logs a message that the broker connection has been lost and attempts to reconnect.
+ */
+ public void connectionLost(Throwable cause) {
+ connected = false;
+ syncOut(getDate() + Messages.MqttClientView_45);
+ String connectString = connectAddress + ":" + connectPort; //$NON-NLS-1$
+ syncOut(getDate() + Messages.MqttClientView_47 + connectString);
+ try {
+ mqttClient.connect(connOpts);
+ } catch (Exception e) {
+ syncOut(getDate() + Messages.MqttClientView_48);
+ syncOut(getDate() + Messages.MqttClientView_49);
+ }
+ connected = true;
+ syncOut(getDate() + Messages.MqttClientView_50 + clientId);
+ }
+
+ @Override
+ /**
+ * Logs a message that has arrived from the broker
+ */
+ public void messageArrived(String topic, MqttMessage message)
+ throws Exception {
+ syncOut(getDate() + Messages.MqttClientView_51);
+ syncOut(Messages.MqttClientView_52 + topic + "\""); //$NON-NLS-2$
+ syncOut(Messages.MqttClientView_54 + new String(message.getPayload()) + "\""); //$NON-NLS-2$
+ }
+
+ /**
+ * Logs that a publish has completed (an acknowledgement has been received from the broker
+ */
+ public void deliveryComplete(MqttDeliveryToken token) {
+ syncOut(getDate() + Messages.MqttClientView_56);
+ }
+
+ /**
+ * Synchronously writes a message to the log.
+ */
+ private void syncOut(String msg) {
+ tmpMsg = msg;
+ parent.getDisplay().syncExec(new Runnable() {
+ public void run(){
+ out(tmpMsg);
+ }
+ });
+ }
+
+ /**
+ * Returns a hex string representation of the byte array
+ */
+ public static String getHexString(byte[] b) {
+ String result = ""; //$NON-NLS-1$
+ for (int i=0; i < b.length; i++) {
+ result += Integer.toString( ( b[i] & 0xff ) + 0x100, 16).substring( 1 );
+ }
+ return result;
+ }
+
+ /**
+ * Writes a message to the log
+ */
+ private void out(String message) {
+ messageLog.append(message + "\n"); //$NON-NLS-1$
+ }
+
+ /**
+ * Return the current date as a formatted string.
+ */
+ private String getDate() {
+ System.currentTimeMillis();
+ SimpleDateFormat formatter = new SimpleDateFormat ("yyyy/MM/dd hh:mm:ss.SS"); //$NON-NLS-1$
+ Date currentTime_1 = new Date();
+ String dateString = formatter.format(currentTime_1);
+ return "[" + dateString + "] "; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /**
+ * Creates the connection composite and populates it with all the widgets needed to establish an MQTT connection
+ */
+ private Control getConnectionControl(TabFolder tabFolder) {
+ Composite composite = new Composite(tabFolder, SWT.NONE);
+ composite.setLayout(new FillLayout(SWT.VERTICAL));
+
+ // Connection group
+ connectionGroup = new Group(composite, SWT.NONE);
+ connectionGroup.setLayout(new GridLayout(2, false));
+ connectionGroup.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_FILL));
+ connectionGroup.setText(Messages.MqttClientView_62);
+
+ // Broker address
+ Label brokerAddressLabel = new Label(connectionGroup, SWT.NULL);
+ brokerAddressLabel.setText(Messages.MqttClientView_63);
+ brokerAddressValue = new Text(connectionGroup, SWT.SINGLE | SWT.BORDER);
+ brokerAddressValue.setLayoutData(new GridData(120,13));
+ brokerAddressValue.setText(connectAddress);
+ brokerAddressValue.setToolTipText(Messages.MqttClientView_64);
+
+ // Broker port
+ Label brokerPortLabel = new Label(connectionGroup, SWT.NULL);
+ brokerPortLabel.setText( Messages.MqttClientView_65);
+ brokerPortValue = new Text(connectionGroup, SWT.SINGLE | SWT.BORDER);
+ brokerPortValue.setLayoutData(new GridData(30,13));
+ brokerPortValue.setText(Integer.toString(connectPort));
+ brokerPortValue.setToolTipText(Messages.MqttClientView_66);
+
+ // Client ID
+ Label clientIdLabel = new Label(connectionGroup, SWT.NULL);
+ clientIdLabel.setText( Messages.MqttClientView_67);
+ clientIdValue = new Text(connectionGroup, SWT.SINGLE | SWT.BORDER);
+ clientIdValue.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_FILL));
+ clientIdValue.setText(clientId);
+ clientIdValue.setToolTipText(Messages.MqttClientView_68);
+
+ // Username
+ Label usernameLabel = new Label(connectionGroup, SWT.NULL);
+ usernameLabel.setText( Messages.MqttClientView_69);
+ usernameValue = new Text(connectionGroup, SWT.SINGLE | SWT.BORDER);
+ usernameValue.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_FILL));
+ usernameValue.setText(username);
+ usernameValue.setToolTipText(Messages.MqttClientView_70);
+
+ // Password
+ Label passwordLabel = new Label(connectionGroup, SWT.NULL);
+ passwordLabel.setText( Messages.MqttClientView_71);
+ passwordValue = new Text(connectionGroup, SWT.SINGLE | SWT.BORDER);
+ passwordValue.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_FILL));
+ passwordValue.setText(password);
+ passwordValue.setToolTipText(Messages.MqttClientView_72);
+
+ // Keep alive value in seconds
+ Label keepAliveLabel = new Label(connectionGroup, SWT.NULL);
+ keepAliveLabel.setText( Messages.MqttClientView_73);
+ keepAliveValue = new Text(connectionGroup, SWT.SINGLE | SWT.BORDER);
+ keepAliveValue.setLayoutData(new GridData(30,13));
+ keepAliveValue.setText(Short.toString(keepAlive));
+ keepAliveValue.setToolTipText(Messages.MqttClientView_74);
+
+ // Clean start
+ // we should use text referring to "Clean Session" for consistency
+ Label cleanStartLabel = new Label(connectionGroup, SWT.NULL);
+ cleanStartLabel.setText(Messages.MqttClientView_75);
+ cleanStartCheckBox = new Button(connectionGroup, SWT.CHECK);
+ cleanStartCheckBox.setSelection(cleanStart);
+ cleanStartCheckBox.setToolTipText(Messages.MqttClientView_76);
+
+ // LWT checkbox
+ Label useWillLabel = new Label(connectionGroup, SWT.NULL);
+ useWillLabel.setText(Messages.MqttClientView_77);
+ willCheckBox = new Button(connectionGroup, SWT.CHECK);
+ willCheckBox.setSelection(false);
+ willCheckBox.setToolTipText(Messages.MqttClientView_78);
+
+ // LWT topic
+ Label willTopicLabel = new Label(connectionGroup, SWT.NULL);
+ willTopicLabel.setText(Messages.MqttClientView_79);
+ willTopicValue = new Text(connectionGroup, SWT.SINGLE | SWT.BORDER);
+ willTopicValue.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL));
+ willTopicValue.setText(willTopic);
+ willTopicValue.setEnabled(useWill);
+ willTopicValue.setToolTipText(Messages.MqttClientView_80);
+
+ // LWT message
+ Label willMessageLabel = new Label(connectionGroup, SWT.NULL);
+ willMessageLabel.setText(Messages.MqttClientView_81);
+ willMessageValue = new Text(connectionGroup, SWT.SINGLE | SWT.BORDER);
+ willMessageValue.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL));
+ willMessageValue.setText(willMessage);
+ willMessageValue.setEnabled(useWill);
+ willMessageValue.setToolTipText(Messages.MqttClientView_82);
+
+ // LWT quality of service
+ Label willQosLabel = new Label(connectionGroup, SWT.NULL);
+ willQosLabel.setText(Messages.MqttClientView_83);
+ willQosDrop = new Combo(connectionGroup, SWT.DROP_DOWN | SWT.BORDER);
+ willQosDrop.add("0"); //$NON-NLS-1$
+ willQosDrop.add("1"); //$NON-NLS-1$
+ willQosDrop.add("2"); //$NON-NLS-1$
+ willQosDrop.select(0);
+
+ // LWT retained flag
+ Label willRetainedLabel = new Label(connectionGroup, SWT.NULL);
+ willRetainedLabel.setText(Messages.MqttClientView_87);
+ willRetainCheckBox = new Button(connectionGroup, SWT.CHECK);
+ willRetainCheckBox.setSelection(false);
+ willRetainCheckBox.setToolTipText(Messages.MqttClientView_88);
+
+ // Connect button
+ connectButton = new Button(connectionGroup, SWT.PUSH);
+ connectButton.setLayoutData(new GridData(ClientConstants.BUTTON_WIDTH, ClientConstants.BUTTON_HEIGHT));
+ connectButton.setText(Messages.MqttClientView_89);
+ connectButton.setToolTipText(Messages.MqttClientView_90);
+
+ // Disconnect button
+ disconnectButton = new Button(connectionGroup, SWT.PUSH);
+ disconnectButton.setLayoutData(new GridData(ClientConstants.BUTTON_WIDTH, ClientConstants.BUTTON_HEIGHT));
+ disconnectButton.setText(Messages.MqttClientView_91);
+ disconnectButton.setToolTipText(Messages.MqttClientView_92);
+
+ // A modify listener for modifications to textual parameters
+ ModifyListener listener = new ModifyListener() {
+ public void modifyText(ModifyEvent e) {
+ valueChanged((Text) e.widget);
+ }
+ };
+
+ // A modify listener for modification to the broker address
+ ModifyListener addressListener = new ModifyListener() {
+ public void modifyText(ModifyEvent e) {
+ addressValueChanged((Text) e.widget);
+ }
+ };
+
+ // Set listeners
+ brokerAddressValue.addModifyListener(addressListener);
+ brokerPortValue.addModifyListener(addressListener);
+ clientIdValue.addModifyListener(listener);
+ usernameValue.addModifyListener(listener);
+ passwordValue.addModifyListener(listener);
+ keepAliveValue.addModifyListener(listener);
+ cleanStartCheckBox.addSelectionListener(selectListener);
+ connectButton.addSelectionListener(connectListener);
+ disconnectButton.addSelectionListener(connectListener);
+ willCheckBox.addSelectionListener(willListener);
+ willTopicValue.addModifyListener(listener);
+ willMessageValue.addModifyListener(listener);
+
+ return composite;
+ }
+
+ /**
+ * Creates the publish composite and populates it with all the widgets needed to make an MQTT publish.
+ * TODO: add tooltips for input fields and controls
+ */
+ private Control getPublishControl(TabFolder tabFolder) {
+ @SuppressWarnings("unused")
+ Label tmpNullLabel;
+
+ Composite composite = new Composite(tabFolder, SWT.NONE);
+ composite.setLayout(new FillLayout(SWT.VERTICAL));
+
+ // Publish Group
+ publishGroup = new Group(composite, SWT.NONE);
+ publishGroup.setLayout(new GridLayout(3, false));
+ publishGroup.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_FILL));
+ publishGroup.setText(Messages.MqttClientView_93);
+
+ // Publish topic
+ Label publishTopicLabel = new Label(publishGroup, SWT.NULL);
+ publishTopicLabel.setText(Messages.MqttClientView_94);
+ publishTopicValue = new Text(publishGroup, SWT.SINGLE | SWT.BORDER);
+ publishTopicValue.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL));
+ publishTopicValue.setSize(110, 1);
+
+ tmpNullLabel = new Label(publishGroup, SWT.NULL);
+
+ // Publish quality of service
+ Label publishQosLabel = new Label(publishGroup, SWT.NULL);
+ publishQosLabel.setText(Messages.MqttClientView_95);
+ publishQosDrop = new Combo(publishGroup, SWT.DROP_DOWN | SWT.BORDER);
+ publishQosDrop.add("0"); //$NON-NLS-1$
+ publishQosDrop.add("1"); //$NON-NLS-1$
+ publishQosDrop.add("2"); //$NON-NLS-1$
+ publishQosDrop.select(0);
+
+ tmpNullLabel = new Label(publishGroup, SWT.NULL);
+
+ // Retained flag
+ Label publishRetainedLabel = new Label(publishGroup, SWT.NULL);
+ publishRetainedLabel.setText(Messages.MqttClientView_96);
+ publishRetainCheckBox = new Button(publishGroup, SWT.CHECK);
+ publishRetainCheckBox.setSelection(false);
+ publishRetainCheckBox.setToolTipText(Messages.MqttClientView_97);
+
+ tmpNullLabel = new Label(publishGroup, SWT.NULL);
+
+ // Publish payload (text)
+ Label publishPayloadLabel = new Label(publishGroup, SWT.NULL);
+ publishPayloadLabel.setText(Messages.MqttClientView_99);
+ publishPayloadValue = new Text(publishGroup, SWT.SINGLE | SWT.BORDER);
+ publishPayloadValue.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL));
+ publishPayloadValue.setSize(110, 1);
+
+ tmpNullLabel = new Label(publishGroup, SWT.NULL);
+ tmpNullLabel = new Label(publishGroup, SWT.NULL);
+
+ Button publishPayloadButton = new Button(publishGroup, SWT.PUSH);
+ publishPayloadButton.setLayoutData(new GridData(ClientConstants.BUTTON_WIDTH, ClientConstants.BUTTON_HEIGHT));
+ publishPayloadButton.setText(Messages.MqttClientView_100);
+
+ tmpNullLabel = new Label(publishGroup, SWT.NULL);
+
+ // Publish payload (file)
+ Label publishFileLabel = new Label(publishGroup, SWT.NULL);
+ publishFileLabel.setText(Messages.MqttClientView_101);
+ publishFileName = new Text(publishGroup, SWT.SINGLE | SWT.BORDER);
+ publishFileName.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL));
+ publishFileName.setSize(100, 1);
+
+ // Browse button for selecting a filename
+ Button browseButton = new Button(publishGroup, SWT.PUSH);
+ browseButton.setText(Messages.MqttClientView_102);
+
+ tmpNullLabel = new Label(publishGroup, SWT.NULL);
+
+ // Publish file button
+ Button publishFileButton = new Button(publishGroup, SWT.PUSH);
+ publishFileButton.setLayoutData(new GridData(ClientConstants.BUTTON_WIDTH, ClientConstants.BUTTON_HEIGHT));
+ publishFileButton.setText(Messages.MqttClientView_103);
+
+ // Set selection listeners
+ publishPayloadButton.addSelectionListener(publishListener);
+ publishFileButton.addSelectionListener(publishFileListener);
+ browseButton.addSelectionListener(new SelectionAdapter() {
+ public void widgetSelected(SelectionEvent e) {
+ FileDialog dialog = new FileDialog(new Shell(), SWT.NULL);
+ String path = dialog.open();
+ if (path != null) {
+ File file = new File(path);
+ if (file.isFile())
+ displayFiles(new String[] { file.toString()});
+ else
+ displayFiles(file.list());
+
+ }
+ }
+ });
+
+ return composite;
+ }
+
+ private void displayFiles(String[] files) {
+ for (int i = 0; files != null && i < files.length; i++) {
+ publishFileName.setText(files[i]);
+ publishFileName.setEditable(true);
+ }
+ }
+
+ /**
+ * Creates the subscribe composite and populates it with all the widgets needed to make an MQTT subscription.
+ * TODO: add tooltips for input fields and controls
+ */
+ private Control getSubscribeControl(TabFolder tabFolder) {
+ Composite composite = new Composite(tabFolder, SWT.NONE);
+ composite.setLayout(new FillLayout(SWT.VERTICAL));
+
+ // Subscribe Group
+ subscribeGroup = new Group(composite, SWT.NONE);
+ subscribeGroup.setLayout(new GridLayout(2, false));
+ subscribeGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL));
+ subscribeGroup.setText(Messages.MqttClientView_104);
+
+ // Subscribe topic
+ Label subscribeTopicLabel = new Label(subscribeGroup, SWT.NULL);
+ subscribeTopicLabel.setText(Messages.MqttClientView_105);
+ subscribeTopicValue = new Text(subscribeGroup, SWT.SINGLE | SWT.BORDER);
+ subscribeTopicValue.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL));
+ subscribeTopicValue.setSize(80, 1);
+
+ // Subscribe quality of service
+ Label subscribeQosLabel = new Label(subscribeGroup, SWT.NULL);
+ subscribeQosLabel.setText(Messages.MqttClientView_106);
+ subscribeQosDrop = new Combo(subscribeGroup, SWT.DROP_DOWN | SWT.BORDER);
+ subscribeQosDrop.add("0"); //$NON-NLS-1$
+ subscribeQosDrop.add("1"); //$NON-NLS-1$
+ subscribeQosDrop.add("2"); //$NON-NLS-1$
+ subscribeQosDrop.select(0);
+
+ // Subscribe button
+ Button subscribeButton = new Button(subscribeGroup, SWT.PUSH);
+ subscribeButton.setLayoutData(new GridData(ClientConstants.BUTTON_WIDTH, ClientConstants.BUTTON_HEIGHT));
+ subscribeButton.setText(Messages.MqttClientView_110);
+
+ // Unsubscribe button
+ Button unsubscribeButton = new Button(subscribeGroup, SWT.PUSH);
+ unsubscribeButton.setLayoutData(new GridData(ClientConstants.BUTTON_WIDTH, ClientConstants.BUTTON_HEIGHT));
+ unsubscribeButton.setText(Messages.MqttClientView_111);
+
+ // Set selection listeners
+ subscribeButton.addSelectionListener(subscribeListener);
+ unsubscribeButton.addSelectionListener(unsubscribeListener);
+
+ return composite;
+ }
+
+ /**
+ * Return the bytes from a file
+ */
+ public static byte[] getBytesFromFile(File file) throws IOException {
+ InputStream is = new FileInputStream(file);
+ long length = file.length();
+ byte[] bytes = new byte[(int)length];
+
+ // Read in file
+ int offset = 0;
+ int numRead = 0;
+ while ((offset < bytes.length) && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) {
+ offset += numRead;
+ }
+
+ // Close stream
+ is.close();
+
+ return bytes;
+ }
+
+ @Override
+ public void deliveryComplete(IMqttDeliveryToken token) {
+ // TODO Auto-generated method stub
+
+ }
+}
diff --git a/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/messages.properties b/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/messages.properties
new file mode 100644
index 00000000..33ae6641
--- /dev/null
+++ b/org.eclipse.paho.client.eclipse.view/src/org/eclipse/paho/client/eclipse/view/messages.properties
@@ -0,0 +1,81 @@
+MqttClientView_10=Subscribe
+MqttClientView_100=Publish Payload
+MqttClientView_101=File:
+MqttClientView_102=Browse
+MqttClientView_103=Publish File
+MqttClientView_104=Subscribe
+MqttClientView_105=Topic:
+MqttClientView_106=QoS:
+MqttClientView_11=Subscribe and unsubscribe to topics
+MqttClientView_110=Subscribe
+MqttClientView_111=Unsubscribe
+MqttClientView_12=Error publishing: Please enter a valid topic to publish on.
+MqttClientView_15=Error connecting to
+MqttClientView_16=, please enter a valid client ID.
+MqttClientView_17=Failed to connect to broker:
+MqttClientView_19=Error connecting: Please enter a LWT topic.
+MqttClientView_20=Attempting to connect to broker:
+MqttClientView_21=CONNECTED - Client ID:
+MqttClientView_22=Failed to connect to broker:
+MqttClientView_23=Error connecting: Client is currently connected.
+MqttClientView_24=DISCONNECTED
+MqttClientView_25=Error disconnecting: Client was not connected.
+MqttClientView_26=Error disconnecting:
+MqttClientView_28=Error publishing: Please enter a topic to publish on.
+MqttClientView_29=PUBLISH
+MqttClientView_30=\ Topic: "
+MqttClientView_32=\ QoS:
+MqttClientView_34=\ Retain:
+MqttClientView_36=\ Payload: "
+MqttClientView_38=SUBSCRIBE
+MqttClientView_39=\ Topic: "
+MqttClientView_41=\ QoS:
+MqttClientView_42=UNSUBSCRIBE
+MqttClientView_43=\ Topic: "
+MqttClientView_45=CONNECTION LOST\!
+MqttClientView_47=Attempting to reconnect to broker:
+MqttClientView_48=Failed to reconnect.
+MqttClientView_49=DISCONNECTED
+MqttClientView_50=CONNECTED - Client ID:
+MqttClientView_51=PUBLICATION ARRIVED
+MqttClientView_52=\ Topic: "
+MqttClientView_54=\ Payload: "
+MqttClientView_56=PUBLISH COMPLETE
+MqttClientView_6=Connection
+MqttClientView_62=Connection
+MqttClientView_63=Broker address:
+MqttClientView_64=IP address or URL of the broker
+MqttClientView_65=Broker port:
+MqttClientView_66=Port that the broker is listening on
+MqttClientView_67=Client ID:
+MqttClientView_68=Client ID to connect with (must be unique)
+MqttClientView_69=Username:
+MqttClientView_7=Connect to a broker
+MqttClientView_70=Username to connect with (optional)
+MqttClientView_71=Password:
+MqttClientView_72=Password to connect with (optional)
+MqttClientView_73=Keep Alive:
+MqttClientView_74=Number of seconds between keep alive pings with the broker
+MqttClientView_75=Clean Session:
+MqttClientView_76=Select to enable a clean session
+MqttClientView_77=LWT Enable:
+MqttClientView_78=Select to enable the Last Will and Testament (LWT)
+MqttClientView_79=LWT Topic:
+MqttClientView_8=Publish
+MqttClientView_80=Topic that the LWT message is published on
+MqttClientView_81=LWT Message:
+MqttClientView_82=LWT message to publish
+MqttClientView_83=LWT QoS:
+MqttClientView_87=LWT Retain:
+MqttClientView_88=Select to retain the last LWT message
+MqttClientView_89=Connect
+MqttClientView_9=Publish messages
+MqttClientView_90=Press to connect to the broker
+MqttClientView_91=Disconnect
+MqttClientView_92=Press to disconnect from the broker
+MqttClientView_93=Publish
+MqttClientView_94=Topic:
+MqttClientView_95=QoS:
+MqttClientView_96=Retain:
+MqttClientView_97=Select to mark the published message as retained
+MqttClientView_99=Payload:
diff --git a/org.eclipse.paho.client.mqttv3.internal.traceformat/.classpath b/org.eclipse.paho.client.mqttv3.internal.traceformat/.classpath
index 9086a8bf..f5547665 100644
--- a/org.eclipse.paho.client.mqttv3.internal.traceformat/.classpath
+++ b/org.eclipse.paho.client.mqttv3.internal.traceformat/.classpath
@@ -1,7 +1,7 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/org.eclipse.paho.client.mqttv3.internal.traceformat/.project b/org.eclipse.paho.client.mqttv3.internal.traceformat/.project
index 66f78c09..0c7c516c 100644
--- a/org.eclipse.paho.client.mqttv3.internal.traceformat/.project
+++ b/org.eclipse.paho.client.mqttv3.internal.traceformat/.project
@@ -1,17 +1,17 @@
-
-
- org.eclipse.paho.client.mqttv3.internal.traceformat
-
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
-
- org.eclipse.jdt.core.javanature
-
-
+
+
+ org.eclipse.paho.client.mqttv3.internal.traceformat
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/org.eclipse.paho.client.mqttv3.internal.traceformat/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.paho.client.mqttv3.internal.traceformat/.settings/org.eclipse.jdt.core.prefs
index 3ae8bd7a..7341ab16 100644
--- a/org.eclipse.paho.client.mqttv3.internal.traceformat/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.paho.client.mqttv3.internal.traceformat/.settings/org.eclipse.jdt.core.prefs
@@ -1,12 +1,11 @@
-#Sun Mar 25 21:47:46 BST 2012
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.compliance=1.4
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=warning
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=disabled
-org.eclipse.jdt.core.compiler.source=1.3
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=warning
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.7
diff --git a/org.eclipse.paho.client.mqttv3.internal.traceformat/build.xml b/org.eclipse.paho.client.mqttv3.internal.traceformat/build.xml
new file mode 100644
index 00000000..fc10a076
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.internal.traceformat/build.xml
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/org.eclipse.paho.client.mqttv3.internal.traceformat/src/org/eclipse/paho/client/mqttv3/internal/traceformat/TracePointExtractor.java b/org.eclipse.paho.client.mqttv3.internal.traceformat/src/main/java/org/eclipse/paho/client/mqttv3/internal/logBuilder/LogMessageExtractor.java
similarity index 56%
rename from org.eclipse.paho.client.mqttv3.internal.traceformat/src/org/eclipse/paho/client/mqttv3/internal/traceformat/TracePointExtractor.java
rename to org.eclipse.paho.client.mqttv3.internal.traceformat/src/main/java/org/eclipse/paho/client/mqttv3/internal/logBuilder/LogMessageExtractor.java
index 448e7bcd..138561c7 100644
--- a/org.eclipse.paho.client.mqttv3.internal.traceformat/src/org/eclipse/paho/client/mqttv3/internal/traceformat/TracePointExtractor.java
+++ b/org.eclipse.paho.client.mqttv3.internal.traceformat/src/main/java/org/eclipse/paho/client/mqttv3/internal/logBuilder/LogMessageExtractor.java
@@ -1,119 +1,155 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal.traceformat;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.PrintStream;
-import java.util.HashMap;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class TracePointExtractor {
-
- public static void main(String[] args) {
- if (args == null) {
- args = new String[] {};
- }
- if (args.length != 0 && args.length != 2 && args.length != 4) {
- usageAndExit();
- }
- String dir = ".";
- String file = "./trace.properties";
-
- for (int i=0;i0) {
+ rc = rc1;;
+ }
+ } else {
+ if (f.isDirectory()) {
+ File[] files = f.listFiles();
+ for (int i=0;i0) {
+ rc = rc1;;
+ }
+ }
+ }
+ }
+ return rc;
+ }
+
+ public short parseFile(File f) throws Exception {
+ String filename = f.getAbsolutePath();
+ String classname = filename.substring(this.basedir.length()+1).replaceAll("/",".");
+ classname = classname.substring(0,classname.length()-5);
+ BufferedReader in = new BufferedReader(new FileReader(f));
+ String line;
+ int lineNo = 1;
+ short rc = 0;
+
+ while ( (line = in.readLine()) != null) {
+ Matcher m = pattern.matcher(line);
+ if (m.matches()) {
+ String number = m.group(1);
+ if (this.points.containsKey(number)) {
+ System.out.println("Duplicate Trace Point: "+number);
+ System.out.println(" "+this.points.get(number));
+ System.out.println(" "+classname+":"+lineNo);
+ rc=1;
+ }
+ // The original extractor put out 4 values for each trace point
+// out.println(number+".class="+classname);
+// out.println(number+".line="+lineNo);
+// out.println(number+".value="+m.group(2));
+ this.points.put(number, classname+":"+lineNo);
+ out.println(number+"="+m.group(2));
+ }
+ lineNo++;
+ }
+ in.close();
+
+ return rc;
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3.internal.traceformat/src/org/eclipse/paho/client/mqttv3/internal/traceformat/TestTrace.java b/org.eclipse.paho.client.mqttv3.internal.traceformat/src/org/eclipse/paho/client/mqttv3/internal/traceformat/TestTrace.java
deleted file mode 100644
index f7d4060e..00000000
--- a/org.eclipse.paho.client.mqttv3.internal.traceformat/src/org/eclipse/paho/client/mqttv3/internal/traceformat/TestTrace.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal.traceformat;
-
-import org.eclipse.paho.client.mqttv3.MqttClient;
-import org.eclipse.paho.client.mqttv3.MqttMessage;
-
-public class TestTrace {
- public static void main(String[] args) {
- try {
- MqttClient client = new MqttClient("tcp://localhost:1883","foobar");
- client.connect();
- client.subscribe("foo");
- for (int i=0;i<5;i++) {
- client.getTopic("bar").publish(new MqttMessage("hi".getBytes()));
- }
- client.disconnect();
- } catch(Exception e) {
- e.printStackTrace();
- }
- /*
- System.out.println(System.getProperty("user.dir"));
- long start = System.currentTimeMillis();
- Trace trace = Trace.getTrace("foo");
- for (int i=0;i<100;i++) {
- trace.trace(Trace.FINE,123);
- trace.trace(Trace.FINE,456,new Object[] {"a",new Date(),System.class});
- trace.traceEntry(Trace.FINE,678);
- trace.traceExit(Trace.FINE,789);
- trace.trace(Trace.FINE,123);
- trace.trace(Trace.FINE,123);
- //trace.traceCatch(Trace.FINE,345, new Exception());
- //trace.trace(Trace.FINE,543,null,new Exception());
- }
- long stop = System.currentTimeMillis();
- System.out.println(stop-start);
- */
- try {
- TracePointExtractor.main(new String[] {"-d","/home/nol/workspace/org.eclipse.paho.client.mqttv3/mqtt_client_v3/","-o","/home/nol/workspace/org.eclipse.paho.client.mqttv3/trace.properties"});
- TraceFormatter f = new TraceFormatter("mqtt-0.trc","/tmp/trace.html");
- f.format();
- } catch(Exception e) {
- e.printStackTrace();
- }
- }
-}
diff --git a/org.eclipse.paho.client.mqttv3.internal.traceformat/src/org/eclipse/paho/client/mqttv3/internal/traceformat/TraceFormatter.java b/org.eclipse.paho.client.mqttv3.internal.traceformat/src/org/eclipse/paho/client/mqttv3/internal/traceformat/TraceFormatter.java
deleted file mode 100644
index 6180f980..00000000
--- a/org.eclipse.paho.client.mqttv3.internal.traceformat/src/org/eclipse/paho/client/mqttv3/internal/traceformat/TraceFormatter.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal.traceformat;
-
-import java.io.DataInputStream;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.text.MessageFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Properties;
-
-import org.eclipse.paho.client.mqttv3.internal.trace.TracePoint;
-
-public class TraceFormatter {
-
- private DataInputStream in;
- private PrintWriter out;
- private Properties traceProperties;
- private String traceFile;
- private String outputFile;
-
- public static void main(String[] args) {
- if (args == null) {
- args = new String[] {};
- }
- if (args.length != 0 && args.length != 2 && args.length != 4) {
- usageAndExit();
- }
- String traceFile = "mqtt-0.trc";
- String outputFile = "mqtt-0.trc.html";
- for (int i=0;i");
- out.println("org.eclipse.paho.client.mqttv3 trace");
- out.println("");
- out.println("");
- out.println("");
- out.println("
+ *
+ * This bug is caused by the {@link org.eclipse.paho.client.mqttv3.internal.CommsCallback#messageArrived}, the while
+ * condition will never be false when the {@code messageQueue} is full and not {@code quiescing}, even when the callback
+ * thread is trying to stop.
+ */
+public class Bug443142Test {
+ private static final Logger log = Logger.getLogger(Bug443142Test.class.getName());
+ private static URI serverURI;
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ try {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, Bug443142Test.class, methodName);
+ serverURI = TestProperties.getServerURI();
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ throw exception;
+ }
+ }
+
+ @Test
+ public void testBug443142() throws Exception {
+ CountDownLatch stopLatch = new CountDownLatch(1);
+ MqttClient client1 = new MqttClient(serverURI.toString(), "foo");
+ client1.connect();
+ MqttClient client2 = new MqttClient(serverURI.toString(), "bar");
+ client2.setCallback(new MyMqttCallback(stopLatch));
+ client2.connect();
+ client2.subscribe("bar");
+
+ // publish messages until the queue is full > 10
+ for (int i = 0; i < 16; i++) {
+ MqttMessage message = new MqttMessage(("foo-" + i).getBytes());
+ client1.publish("bar", message);
+ log.info("client1 publish: " + message);
+ }
+
+ // wait until the exception is thrown
+ stopLatch.await();
+
+ // wait some time let client2 to shutdown because of the exception thrown from the callback
+ Thread.sleep(5000);
+
+ // client2 should be disconnected
+ Assert.assertTrue("client1 should connected", client1.isConnected());
+ Assert.assertFalse("client2 should disconnected", client2.isConnected());
+
+ // close client1
+ client1.disconnect();
+ client1.close();
+ Assert.assertFalse("client1 should disconnected", client1.isConnected());
+ }
+
+ private static class MyMqttCallback implements MqttCallback {
+ private final CountDownLatch stopLatch;
+
+ public MyMqttCallback(CountDownLatch stopLatch) {
+ this.stopLatch = stopLatch;
+ }
+
+ @Override
+ public void connectionLost(Throwable cause) {
+ }
+
+ @Override
+ public void messageArrived(String topic, MqttMessage message) throws Exception {
+ System.out.println(new String(message.getPayload()));
+ Thread.sleep(5000);
+ stopLatch.countDown();
+ throw new RuntimeException("deadlock");
+
+ }
+
+ @Override
+ public void deliveryComplete(IMqttDeliveryToken token) {
+ }
+ }
+
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/ConformantTest.java.notrun b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/ConformantTest.java.notrun
new file mode 100644
index 00000000..55cc276a
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/ConformantTest.java.notrun
@@ -0,0 +1,535 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Patrick Leong - export Ian Craggs's Python client_test.py to Java.
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test;
+
+import java.net.URI;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.eclipse.paho.client.mqttv3.IMqttClient;
+import org.eclipse.paho.client.mqttv3.MqttClient;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
+import org.eclipse.paho.client.mqttv3.test.client.MqttClientFactoryPaho;
+import org.eclipse.paho.client.mqttv3.test.client.MqttClientPaho;
+import org.eclipse.paho.client.mqttv3.test.logging.LoggingUtilities;
+import org.eclipse.paho.client.mqttv3.test.properties.TestProperties;
+import org.eclipse.paho.client.mqttv3.test.utilities.MqttV3Receiver;
+import org.eclipse.paho.client.mqttv3.test.utilities.MqttV3Receiver.ReceivedMessage;
+import org.eclipse.paho.client.mqttv3.test.utilities.Utility;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Conformant test for MQTT 3.1.1 protocol.
+ */
+public class ConformantTest {
+
+ static final Class> cclass = ConformantTest.class;
+ private static final String className = cclass.getName();
+ private static final Logger log = Logger.getLogger(className);
+ private static String[] topics = { "TopicA", "TopicA/B", "Topic/C",
+ "TopicA/C", "/TopicA" };
+ private static String[] wildTopics = { "TopicA/+", "+/C", "#", "/#", "/+",
+ "+/+", "TopicA/#" };
+ private static String nosubscribe_topic = "nosubscribe";
+
+ private static URI serverURI;
+ private static MqttClientFactoryPaho clientFactory;
+ private static String[] clientid = { "javaclientid", "javaclientid2" };
+ private IMqttClient client = null, bClient = null;
+
+ /**
+ * @throws Exception
+ */
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ try {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+
+ serverURI = TestProperties.getServerURI();
+ clientFactory = new MqttClientFactoryPaho();
+ clientFactory.open();
+
+ cleanUpAllStatus();
+ } catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ throw exception;
+ }
+ }
+
+ /**
+ * @throws Exception
+ */
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+
+ try {
+ if (clientFactory != null) {
+ clientFactory.close();
+ clientFactory.disconnect();
+ }
+ } catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ }
+ }
+
+ @Before
+ public void init() throws Exception {
+ client = clientFactory.createMqttClient(serverURI, clientid[0]);
+ client.setProtocolVersion(MqttProtocolVersion.V3_1_1);
+ bClient = clientFactory.createMqttClient(serverURI, clientid[1]);
+ bClient.setProtocolVersion(MqttProtocolVersion.V3_1_1);
+ }
+
+ @After
+ public void clean() throws MqttException {
+ if (client != null) {
+ if (client.isConnected()) {
+ client.disconnect();
+ }
+ client.close();
+ }
+ if (bClient != null) {
+ if (bClient.isConnected()) {
+ bClient.disconnect();
+ }
+ bClient.close();
+ }
+ }
+
+ /**
+ * Clean up all status, retained messages
+ *
+ * @throws Exception
+ */
+ public static void cleanUpAllStatus() throws Exception {
+ // Clean all clients status
+ for (int i = 0; i < clientid.length; i++) {
+ IMqttClient client = clientFactory.createMqttClient(serverURI,
+ clientid[i]);
+ client.setProtocolVersion(MqttProtocolVersion.V3_1_1);
+ MqttConnectOptions options = new MqttConnectOptions();
+ options.setCleanSession(true);
+ client.connect(options);
+ Thread.sleep(100);
+ client.disconnect();
+ Thread.sleep(100);
+ }
+
+ // Clean all retain subscriptions.
+ IMqttClient client = clientFactory.createMqttClient(serverURI,
+ clientid[0]);
+ client.setProtocolVersion(MqttProtocolVersion.V3_1_1);
+ MqttV3Receiver receiver = new MqttV3Receiver(client,
+ LoggingUtilities.getPrintStream());
+ client.setCallback(receiver);
+ client.connect();
+ client.subscribe("#", 0);
+ Thread.sleep(3000);
+ List messageList = receiver
+ .getReceivedMessagesInCopy();
+ for (ReceivedMessage receivedMessage : messageList) {
+ if (receivedMessage.message.isRetained()) {
+ log.info("Deleting retained message for topic.");
+ client.publish(receivedMessage.topic, new byte[0], 0, true);
+ }
+ }
+ client.disconnect();
+ client.close();
+ Thread.sleep(100);
+ }
+
+ /**
+ * @throws Exception
+ */
+ @Test
+ public void basicTest() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+
+ String clientId = "basicTest";
+ String[] messages = { "qos 0", "qos 1", "qos 2" };
+ int[] qoses = { 0, 1, 2 };
+
+ log.info("Assigning callback...");
+ MqttV3Receiver receiver = new MqttV3Receiver(client,
+ LoggingUtilities.getPrintStream());
+ client.setCallback(receiver);
+
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:"
+ + clientId);
+ client.connect();
+
+ log.info("Subscribing to..." + topics[0]);
+
+ client.subscribe(topics[0], 2);
+
+ log.info("Publishing to..." + topics[0]);
+ for (int i = 0; i < 3; i++) {
+ client.publish(topics[0], messages[i].getBytes(), qoses[i], false);
+ }
+ Thread.sleep(2000);
+
+ // Verify "TopicA/B"
+ Assert.assertEquals(3, receiver.receivedMessageCount());
+ }
+
+ @Test
+ public void retained_message_test() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+
+ String[] messages = { "qos 0", "qos 1", "qos 2" };
+ int[] qoses = { 0, 1, 2 };
+ try {
+ // Retained messages.
+ log.info("Assigning callback...");
+ MqttV3Receiver receiver = new MqttV3Receiver(client,
+ LoggingUtilities.getPrintStream());
+ client.setCallback(receiver);
+
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:"
+ + clientid[0]);
+ client.connect();
+
+ log.info("Publishing retained messages to...");
+ for (int i = 0; i < 3; i++) {
+ client.publish(topics[i + 1], messages[i].getBytes(), qoses[i],
+ true);
+ }
+
+ Thread.sleep(500);
+ // Subscribe "+/+"
+ log.info("Subscribing to..." + wildTopics[5]);
+ client.subscribe(wildTopics[5], 2);
+ Thread.sleep(2000);
+
+ // Verify
+ List receivedMessages = receiver
+ .getReceivedMessagesInCopy();
+ Assert.assertEquals("Should receive 3 messages", 3,
+ receivedMessages.size());
+ client.disconnect();
+ } finally {
+ cleanUpAllStatus();
+ }
+ }
+
+ /**
+ * @throws Exception
+ */
+ /*@Test
+ public void will_message_test() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+
+ IMqttClient cClient = null;
+ try {
+ log.info("Assigning callback...");
+ MqttV3Receiver receiver = new MqttV3Receiver(client,
+ LoggingUtilities.getPrintStream());
+
+ // Connect clientA
+ MqttConnectOptions options = new MqttConnectOptions();
+ options.setCleanSession(true);
+ options.setWill(topics[2], "client not disconnected".getBytes(), 0,
+ false);
+ options.setKeepAliveInterval(200);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:"
+ + clientid[0] + "with options (" + options + ")");
+ client.setCallback(receiver);
+ client.connect(options);
+
+ MqttConnectOptions mqttOptions2 = new MqttConnectOptions();
+ mqttOptions2.setCleanSession(false);
+ MqttV3Receiver receiverB = new MqttV3Receiver(bClient,
+ LoggingUtilities.getPrintStream());
+ bClient.setCallback(receiverB);
+ bClient.connect(mqttOptions2);
+ bClient.subscribe(topics[2], 2);
+
+ try {
+ client.disconnect();
+ } catch(MqttException m) {
+ log.info(m.toString());
+ }
+
+ ReceivedMessage msg = receiverB.receiveNext(1000);
+ Assert.assertNotNull(" Should have one will message.", msg);
+ } catch (MqttException mqttException) {
+ mqttException.printStackTrace();
+ } finally {
+ if (cClient != null) {
+ if (cClient.isConnected()) {
+ cClient.disconnect();
+ }
+ cClient.close();
+ }
+ }
+ }*/
+
+ /**
+ * Test when client id is zero length
+ *
+ * @throws Exception
+ */
+ @Test
+ public void zero_length_clientId_test() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+
+ boolean fails = false;
+ client = clientFactory.createMqttClient(serverURI, "");
+ client.setProtocolVersion(MqttProtocolVersion.V3_1_1);
+ MqttConnectOptions options = new MqttConnectOptions();
+ options.setCleanSession(false);
+ try {
+ client.connect(options);
+ fails = false;
+ } catch (MqttException e) {
+ e.printStackTrace();
+ fails = true;
+ }
+ Assert.assertTrue("Clean session = false should be rejected.", fails);
+
+ options.setCleanSession(true);
+ try {
+ client.connect(options);
+ fails = false;
+ } catch (MqttException e) {
+ e.printStackTrace();
+ fails = true;
+ }
+ Assert.assertFalse("Clean session = true should be accepted.", fails);
+ }
+
+ @Test
+ public void offline_message_queueing_test() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+
+ String[] messages = { "qos 0", "qos 1", "qos 2" };
+ int[] qoses = { 0, 1, 2 };
+
+ log.info("Assigning callback...");
+ MqttV3Receiver receiver = new MqttV3Receiver(client,
+ LoggingUtilities.getPrintStream());
+ client.setCallback(receiver);
+
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:"
+ + clientid[0]);
+ MqttConnectOptions options = new MqttConnectOptions();
+ options.setCleanSession(false);
+ client.connect(options);
+
+ log.info("Subscribing to..." + wildTopics[5]);
+ client.subscribe(wildTopics[5], 2);
+ client.disconnect();
+
+ bClient.connect();
+ log.info("Publishing to..." + wildTopics[5]);
+ for (int i = 0; i < 3; i++) {
+ bClient.publish(topics[i], messages[i].getBytes(), qoses[i], false);
+ }
+ bClient.disconnect();
+
+ client.connect(options);
+ Thread.sleep(1000);
+ client.disconnect();
+
+ // Verify, only have two messages, QoS 0 should be ignored.
+ Assert.assertEquals(
+ "Should have two messages, QoS 0 should be ignored", 2,
+ receiver.receivedMessageCount());
+ }
+
+ /**
+ * overlapping subscriptions. When there is more than one matching
+ * subscription for the same client for a topic, the server may send back
+ * one message with the highest QoS of any matching subscription, or one
+ * message for each subscription with a matching QoS.
+ */
+ @Test
+ public void overlapping_subscriptions_test() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+
+ log.info("Assigning callback...");
+ MqttV3Receiver receiver = new MqttV3Receiver(client,
+ LoggingUtilities.getPrintStream());
+ client.setCallback(receiver);
+
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:"
+ + clientid[0]);
+ client.connect();
+
+ log.info("Subscribing to..." + wildTopics[6]);
+ // "TopicA/#", "TopicA/+"
+ client.subscribe(new String[] { wildTopics[6], wildTopics[0] },
+ new int[] { 2, 1 });
+ // Publish to "TopicA/C"
+ client.publish(topics[3], "overlapping topic filters".getBytes(), 2,
+ false);
+ Thread.sleep(1000);
+
+ // Verify
+ List receivesMessages = receiver
+ .getReceivedMessagesInCopy();
+ int length = receivesMessages.size();
+ Assert.assertTrue("Received Messages should be 1 or 2.", length >= 1
+ && length <= 2);
+
+ if (length == 1) {
+ log.info("This server is publishing one message for all matching overlapping subscriptions, not one for each.");
+ Assert.assertEquals(2, receivesMessages.get(0).message.getQos());
+ } else { // length == 2
+ log.info("This server is publishing one message per each matching overlapping subscription.");
+ int message1QoS = receivesMessages.get(0).message.getQos();
+ int message2QoS = receivesMessages.get(1).message.getQos();
+ Assert.assertTrue((message1QoS == 1 && message2QoS == 2)
+ || (message1QoS == 2 && message2QoS == 1));
+ }
+
+ log.info("Disconnecting...");
+ client.disconnect();
+ }
+
+ /**
+ * keepalive processing. We should be kicked off by the server if we don't
+ * send or receive any data, and don't send any pings either.
+ */
+ /*@Test
+ public void keepalive_test() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+
+ String payload = "keepalive expiry";
+ MqttConnectOptions options = new MqttConnectOptions();
+ options.setCleanSession(true);
+ options.setKeepAliveInterval(0);
+ options.setWill(topics[4], payload.getBytes(), 2, false);
+
+ client.setCallback(new MqttV3Receiver(client, LoggingUtilities
+ .getPrintStream()));
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:"
+ + clientid[0] + "with options " + options);
+ client.connect(options);
+
+ MqttV3Receiver receiver = new MqttV3Receiver(bClient,
+ LoggingUtilities.getPrintStream());
+ bClient.setCallback(receiver);
+ options.setKeepAliveInterval(5);
+ bClient.connect(options);
+ bClient.subscribe(topics[4], 2);
+ //hang the client for a time
+ bClient.disconnect();
+
+ ReceivedMessage message = receiver.receiveNext(500);
+ Assert.assertNotNull(message);
+ Assert.assertArrayEquals(payload.getBytes(),
+ message.message.getPayload());
+ }*/
+
+ /**
+ * redelivery on reconnect. When a QoS 1 or 2 exchange has not been
+ * completed, the server should retry the appropriate MQTT packets.
+ */
+ /*public void redelivery_on_reconnect_test() {
+ // Pause? Resume?
+ }*/
+
+ /**
+ * Subscribe failure. A new feature of MQTT 3.1.1 is the ability to send
+ * back negative reponses to subscribe requests. One way of doing this is to
+ * subscribe to a topic which is not allowed to be subscribed to.
+ */
+ /*@Test
+ public void subscribe_failure_test() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+
+ boolean succeeded = true;
+ try {
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:"
+ + clientid[0]);
+ client.connect();
+
+ log.info("Subscribing to..." + nosubscribe_topic);
+ client.subscribe(nosubscribe_topic, 2);
+ Thread.sleep(200); // Wait for all retained messages, hopefully
+ client.publish("$" + topics[1], new byte[0], 1, false);
+ Thread.sleep(200);
+
+ // TODO: No subscribe callback?
+ // assert callback.subscribeds[0][1][0] == 0x80,
+ // "return code should be 0x80 %s" % callback.subscribeds
+ } catch (Exception exception) {
+ exception.printStackTrace();
+ succeeded = false;
+ }
+ Assert.fail();// Not implemented.
+ }*/
+
+ /**
+ * $ topics. The specification says that a topic filter which starts with a
+ * wildcard does not match topic names that begin with a $. Publishing to a
+ * topic which starts with a $ may not be allowed on some servers (which is
+ * entirely valid), so this test will not work and should be omitted in that
+ * case.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void dollar_topics_test() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+
+ log.info("Assigning callback...");
+ MqttV3Receiver receiver = new MqttV3Receiver(client,
+ LoggingUtilities.getPrintStream());
+ MqttConnectOptions options = new MqttConnectOptions();
+ options.setKeepAliveInterval(0);
+ options.setCleanSession(true);
+ client.setCallback(receiver);
+
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:"
+ + clientid[0]);
+ client.connect(options);
+
+ log.info("Subscribing to..." + topics[0]);
+ client.subscribe(wildTopics[5], 2);
+ Thread.sleep(1000); // Wait for all retained messages, hopefully
+ client.publish("$" + topics[1], "".getBytes(), 1, false);
+ Thread.sleep(200);
+
+ // Verify
+ ReceivedMessage message = receiver.receiveNext(500);
+ Assert.assertNull("Should not receive any messsage for '$' topics",
+ message);
+ }
+
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/LiveTakeOverTest.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/LiveTakeOverTest.java
new file mode 100644
index 00000000..84636962
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/LiveTakeOverTest.java
@@ -0,0 +1,292 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test;
+
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.eclipse.paho.client.mqttv3.IMqttClient;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttTopic;
+import org.eclipse.paho.client.mqttv3.test.client.MqttClientFactoryPaho;
+import org.eclipse.paho.client.mqttv3.test.logging.LoggingUtilities;
+import org.eclipse.paho.client.mqttv3.test.properties.TestProperties;
+import org.eclipse.paho.client.mqttv3.test.utilities.MqttV3Receiver;
+import org.eclipse.paho.client.mqttv3.test.utilities.MqttV3Receiver.ReceivedMessage;
+import org.eclipse.paho.client.mqttv3.test.utilities.Utility;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class LiveTakeOverTest {
+
+ private static final Class> cclass = LiveTakeOverTest.class;
+ private static final String className = cclass.getName();
+ private static final Logger log = Logger.getLogger(className);
+
+ private static URI serverURI;
+ private static MqttClientFactoryPaho clientFactory;
+
+ static enum FirstClientState {
+ INITIAL,
+ READY,
+ RUNNING,
+ FINISHED,
+ ERROR
+ }
+
+ private static String ClientId = "TakeOverClient";
+ private static String FirstSubTopicString = "FirstClient/Topic";
+
+ /**
+ * @throws Exception
+ */
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+
+ try {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+
+ serverURI = TestProperties.getServerURI();
+ clientFactory = new MqttClientFactoryPaho();
+ clientFactory.open();
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ throw exception;
+ }
+ }
+
+ /**
+ * @throws Exception
+ */
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+
+ try {
+ if (clientFactory != null) {
+ clientFactory.close();
+ clientFactory.disconnect();
+ }
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ }
+ }
+
+ /**
+ * Test that a client actively doing work can be taken over
+ * @throws Exception
+ */
+ @Test
+ public void testLiveTakeOver() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ log.entering(className, methodName);
+
+ IMqttClient mqttClient = null;
+ try {
+ FirstClient firstClient = new FirstClient();
+ Thread firstClientThread = new Thread(firstClient);
+ log.info("Starting the firstClient thread");
+ firstClientThread.start();
+ log.info("firstClientThread Started");
+
+ firstClient.waitForState(FirstClientState.READY);
+
+ log.fine("telling the 1st client to go and let it publish for 2 seconds");
+ //Tell the first client to go and let it publish for a couple of seconds
+ firstClient.setState(FirstClientState.RUNNING);
+ Thread.sleep(2000);
+
+ log.fine("Client has been run for 2 seconds, now taking over connection");
+
+ //Now lets take over the connection
+ // Create a second MQTT client connection with the same clientid. The
+ // server should spot this and kick the first client connection off.
+ // To do this from the same box the 2nd client needs to use either
+ // a different form of persistent store or a different locaiton for
+ // the store to the first client.
+ // MqttClientPersistence persist = new MemoryPersistence();
+ mqttClient = clientFactory.createMqttClient(serverURI, ClientId, null);
+
+ MqttV3Receiver mqttV3Receiver = new MqttV3Receiver(mqttClient, LoggingUtilities.getPrintStream());
+ mqttClient.setCallback(mqttV3Receiver);
+ MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
+ mqttConnectOptions.setCleanSession(false);
+ mqttConnectOptions.setWill("WillTopic", "payload".getBytes(), 2, true);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:" + ClientId);
+ mqttClient.connect(mqttConnectOptions);
+
+ //We should have taken over the first Client's subscription...we may have some
+ //of his publishes arrive.
+ // NOTE: as a different persistence is used for the second client any inflight
+ // publications from the client will not be recovered / restarted. This will
+ // leave debris on the server.
+ log.fine("We should have taken over the first Client's subscription...we may have some of his publishes arrive.");
+ //Ignore his publishes that arrive...
+ ReceivedMessage oldMsg;
+ do {
+ oldMsg = mqttV3Receiver.receiveNext(1000);
+ }
+ while (oldMsg != null);
+
+ log.fine("Now check we have grabbed his subscription by publishing..");
+ //Now check we have grabbed his subscription by publishing..
+ byte[] payload = ("Message payload from second client " + getClass().getName() + "." + methodName).getBytes();
+ MqttTopic mqttTopic = mqttClient.getTopic(FirstSubTopicString);
+ log.info("Publishing to..." + FirstSubTopicString);
+ mqttTopic.publish(payload, 1, false);
+ log.info("Publish sent, checking for receipt...");
+
+ boolean ok = mqttV3Receiver.validateReceipt(FirstSubTopicString, 1, payload);
+ if (!ok) {
+ throw new Exception("Receive failed");
+ }
+ }
+ catch (Exception exception) {
+ log.throwing(className, methodName, exception);
+ throw exception;
+ }
+ finally {
+ try {
+ if (mqttClient != null) {
+ mqttClient.disconnect();
+ log.info("Disconnecting...");
+ mqttClient.close();
+ log.info("Close...");
+ }
+ }
+ catch (Exception exception) {
+ log.throwing(className, methodName, exception);
+ throw exception;
+ }
+ }
+
+ log.exiting(className, methodName);
+ }
+
+ class FirstClient implements Runnable {
+
+ private FirstClientState state = FirstClientState.INITIAL;
+ public Object stateLock = new Object();
+ IMqttClient mqttClient = null;
+ MqttV3Receiver mqttV3Receiver = null;
+
+ void waitForState(FirstClientState desiredState) throws InterruptedException {
+ final String methodName = "waitForState";
+ synchronized (stateLock) {
+ while ((state != desiredState) && (state != FirstClientState.ERROR)) {
+ try {
+ stateLock.wait();
+ }
+ catch (InterruptedException exception) {
+ log.throwing(className, methodName, exception);
+ throw exception;
+ }
+ }
+
+ if (state == FirstClientState.ERROR) {
+ Assert.fail("Firstclient entered an ERROR state");
+ }
+ }
+ log.exiting(className, methodName);
+ }
+
+ void setState(FirstClientState newState) {
+ synchronized (stateLock) {
+ state = newState;
+ stateLock.notifyAll();
+ }
+ }
+
+ void connectAndSub() {
+ String methodName = Utility.getMethodName();
+ try {
+ mqttClient = clientFactory.createMqttClient(serverURI, ClientId);
+ mqttV3Receiver = new MqttV3Receiver(mqttClient, LoggingUtilities.getPrintStream());
+ mqttV3Receiver.setReportConnectionLoss(false);
+ mqttClient.setCallback(mqttV3Receiver);
+ MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
+ mqttConnectOptions.setCleanSession(false);
+ mqttConnectOptions.setWill("WillTopic", "payload".getBytes(), 2, true);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:" + ClientId);
+ mqttClient.connect(mqttConnectOptions);
+ log.info("Subscribing to..." + FirstSubTopicString);
+ mqttClient.subscribe(FirstSubTopicString, 2);
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caugh exception:" + exception);
+ setState(FirstClientState.ERROR);
+ Assert.fail("Failed ConnectAndSub exception=" + exception);
+ }
+ }
+
+ void repeatedlyPub() {
+ String methodName = Utility.getMethodName();
+
+ int i = 0;
+ while (mqttClient.isConnected()) {
+ try {
+ if (i > 999999) {
+ i = 0;
+ }
+ byte[] payload = ("Message payload " + getClass().getName() + ".publish" + (i++)).getBytes();
+ MqttTopic mqttTopic = mqttClient.getTopic(FirstSubTopicString);
+ log.info("Publishing to..." + FirstSubTopicString);
+ mqttTopic.publish(payload, 1, false);
+
+ }
+ catch (Exception exception) {
+ log.fine("Caught exception:" + exception);
+ // Don't fail - we are going to get an exception as we disconnected during takeOver
+ // Its likely the publish rate is too high i.e. inflight window is full
+ }
+ }
+ }
+
+ public void run() {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ log.entering(className, methodName);
+
+ connectAndSub();
+ try {
+ setState(FirstClientState.READY);
+ waitForState(FirstClientState.RUNNING);
+ repeatedlyPub();
+ log.info("FirstClient exiting...");
+ log.exiting(className, methodName);
+
+ mqttClient.close();
+
+ }
+ catch (InterruptedException exception) {
+ setState(FirstClientState.ERROR);
+ log.log(Level.SEVERE, "caught exception:", exception);
+ }
+ catch (MqttException exception) {
+ setState(FirstClientState.ERROR);
+ log.log(Level.SEVERE, "caught exception:", exception);
+ }
+ }
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/ManualTest.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/ManualTest.java
new file mode 100644
index 00000000..041ab052
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/ManualTest.java
@@ -0,0 +1,5 @@
+package org.eclipse.paho.client.mqttv3.test;
+
+public interface ManualTest {
+
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/ModelTestCase.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/ModelTestCase.java
new file mode 100644
index 00000000..0e0c6e40
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/ModelTestCase.java
@@ -0,0 +1,686 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.URI;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Random;
+import java.util.UUID;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+import org.eclipse.paho.client.mqttv3.IMqttClient;
+import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.MqttCallback;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.eclipse.paho.client.mqttv3.test.client.MqttClientFactoryPaho;
+import org.eclipse.paho.client.mqttv3.test.logging.LoggingUtilities;
+import org.eclipse.paho.client.mqttv3.test.properties.TestProperties;
+import org.eclipse.paho.client.mqttv3.test.utilities.Utility;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Client test which rapidly tries random combinations of connect,
+ * disconnect, publish and subscribe on a single thread with varying options
+ * (retained, qos etc) and verifies the results. A log is produced
+ * of the history of commands tried which can be fed back into the
+ * test to re-produce a previous run (See run(String filename)
+ */
+
+public class ModelTestCase implements MqttCallback {
+
+ private static final Class> cclass = ModelTestCase.class;
+ private static final String className = cclass.getName();
+ private static final Logger log = Logger.getLogger(className);
+
+ private static URI serverURI;
+ private static MqttClientFactoryPaho clientFactory;
+
+ public static final String LOGDIR = "./";
+ public static final String CLIENTID = "mqttv3.ModelTestCase";
+
+ public String logFilename = null;
+ public File logDirectory = null;
+ public PrintWriter logout = null;
+ public HashMap subscribedTopics;
+ public Object lock;
+ public ArrayList messages;
+ public ArrayList topics;
+ public Random random;
+ public IMqttClient client;
+ public HashMap retainedPublishes;
+ public HashMap currentTokens;
+ public boolean cleanSession;
+
+ private int numOfIterations = 500;
+
+ /**
+ * Constructor
+ **/
+ public ModelTestCase() {
+ logDirectory = new File(LOGDIR);
+ if (logDirectory.exists()) {
+ deleteLogFiles();
+ logFilename = "mqttv3.ModelTestCase." + System.currentTimeMillis() + ".log";
+
+ File logfile = new File(logDirectory, logFilename);
+ try {
+ logout = new PrintWriter(new FileWriter(logfile));
+ }
+ catch (IOException e) {
+ logout = null;
+ }
+ }
+ }
+
+ /**
+ * @throws IOException
+ */
+ private void deleteLogFiles() {
+ log.info("Deleting log files");
+ File[] files = logDirectory.listFiles(new FilenameFilter() {
+
+ public boolean accept(File dir, String name) {
+ return name.matches("mqttv3\\.ModelTestCase\\..*\\.log");
+ }
+ });
+
+ for (File file : files) {
+ boolean isDeleted = file.delete();
+ if (isDeleted == false) {
+ log.info(" failed to delete: " + file.getAbsolutePath());
+ file.deleteOnExit();
+ }
+ }
+ }
+
+ /**
+ * Test definitions
+ * @throws Exception
+ */
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+
+ try {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+
+ serverURI = TestProperties.getServerURI();
+ clientFactory = new MqttClientFactoryPaho();
+ clientFactory.open();
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ throw exception;
+ }
+ }
+
+ /**
+ * @throws Exception
+ */
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+
+ try {
+ if (clientFactory != null) {
+ clientFactory.close();
+ clientFactory.disconnect();
+ }
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ }
+ }
+
+ /**
+ * @throws Exception
+ */
+ @Test
+ public void testRunModel() throws Exception {
+ log.info("Test core operations and parameters by random selection");
+ log.info("See file: " + logFilename + " for details of selected test sequence");
+ initialise();
+ try {
+ this.run(numOfIterations);
+ }
+ finally {
+ finish();
+ }
+ }
+
+ /**
+ * @param msg
+ */
+ public void logToFile(String msg) {
+
+ DateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss.SSS");
+ Date d = new Date();
+ String tsMsg = df.format(d) + " " + msg;
+
+ if (logout != null) {
+ logout.println(tsMsg);
+ }
+ else {
+ System.out.println(tsMsg);
+ }
+ }
+
+ /**
+ * @param e
+ */
+ public void logToFile(Throwable e) {
+ e.printStackTrace(System.out);
+ if (logout != null) {
+ e.printStackTrace(logout);
+ }
+ }
+
+ /**
+ * @throws Exception
+ */
+ public void initialise() throws Exception {
+ random = new Random();
+ subscribedTopics = new HashMap();
+ messages = new ArrayList();
+ topics = new ArrayList();
+ lock = new Object();
+ retainedPublishes = new HashMap();
+ currentTokens = new HashMap();
+
+ client = clientFactory.createMqttClient(serverURI, CLIENTID);
+ client.setCallback(this);
+ // Clean any hungover state
+ MqttConnectOptions connOpts = new MqttConnectOptions();
+ connOpts.setCleanSession(true);
+ client.connect(connOpts);
+ client.disconnect();
+ }
+
+ /**
+ * @throws Exception
+ */
+ public void finish() throws Exception {
+ if (logout != null) {
+ logout.flush();
+ logout.close();
+ }
+ client.close();
+ }
+
+ private final double[] CONNECTED_TABLE = new double[]{0.05, // disconnect
+ 0.2, // subscribe
+ 0.2, // unsubscribe
+ 0.5, // publish
+ 0.05 // pendingDeliveryTokens
+ };
+ private final double[] DISCONNECTED_TABLE = new double[]{0.5, // connect
+ 0.2, // pendingDeliveryTokens
+ 0.3, // disconnect
+ };
+
+ /**
+ * @param table
+ */
+ private int getOption(double[] table) {
+ double n = random.nextDouble();
+ double c = 0;
+ for (int i = 0; i < table.length; i++) {
+ c += table[i];
+ if (c > n) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * @param iterations
+ * @throws Exception
+ */
+ public void run(int iterations) throws Exception {
+ try {
+ for (int i = 0; i < iterations; i++) {
+ if (client.isConnected()) {
+ int o = getOption(CONNECTED_TABLE);
+ switch (o) {
+ case 0 :
+ disconnect(true);
+ break;
+ case 1 :
+ subscribe();
+ break;
+ case 2 :
+ unsubscribe();
+ break;
+ case 3 :
+ publish();
+ break;
+ case 4 :
+ pendingDeliveryTokens();
+ break;
+ }
+ }
+ else {
+ int o = getOption(DISCONNECTED_TABLE);
+ switch (o) {
+ case 0 :
+ connect();
+ break;
+ case 1 :
+ pendingDeliveryTokens();
+ break;
+ case 2 :
+ disconnect(false);
+ break;
+ }
+ }
+ }
+ }
+ catch (Exception e) {
+ logToFile(e);
+ throw (e);
+ }
+ finally {
+ try {
+ if (client.isConnected()) {
+ client.disconnect();
+ }
+
+ client.close();
+ }
+ catch (Exception e) {
+ // ignore - cleanup for error cases, allow any previous exception to be seen
+ }
+ }
+ }
+
+ // TODO:
+ /**
+ * @param filename
+ * @throws Exception
+ */
+ public void run(String filename) throws Exception {
+ /*
+ 26/07/2010 16:28:46.972 connect [cleanSession:false]
+ 26/07/2010 16:28:46.972 disconnect [cleanSession:false][isConnected:true]
+ 26/07/2010 16:28:46.972 subscribe [topic:5f00344b-530a-414a-91e6-57f7d8662b80][qos:2][expectRetained:true]
+ 26/07/2010 16:28:46.972 unsubscribe [topic:0c94dacd-71b0-4692-8b49-4cb225e5f505][existing:false]
+ 26/07/2010 16:28:46.972 publish [topic:5f00344b-530a-414a-91e6-57f7d8662b80][payload:0dbc3ef9-85b3-4cf0-b1f3-e086289d8152][qos:2][retained:true][subscribed:false][waitForCompletion:false]
+ 26/07/2010 16:28:46.972 pendingDeliveryTokens [count:0]
+ */
+ Pattern pConnect = Pattern.compile("^.*? connect \\[cleanSession:(.+)\\]$");
+ Pattern pDisconnect = Pattern
+ .compile("^.*? disconnect \\[cleanSession:(.+)\\]\\[isConnected:(.+)\\]$");
+ Pattern pSubscribe = Pattern
+ .compile("^.*? subscribe \\[topic:(.+)\\]\\[qos:(.+)\\]\\[expectRetained:(.+)\\]$");
+ Pattern pUnsubscribe = Pattern.compile("^.*? unsubscribe \\[topic:(.+)\\]\\[existing:(.+)\\]$");
+ Pattern pPublish = Pattern
+ .compile("^.*? publish \\[topic:(.+)\\]\\[payload:(.+)\\]\\[qos:(.+)\\]\\[retained:(.+)\\]\\[subscribed:(.+)\\]\\[waitForCompletion:(.+)\\]$");
+ Pattern pPendingDeliveryTokens = Pattern.compile("^.*? pendingDeliveryTokens \\[count:(.+)\\]$");
+ BufferedReader in = new BufferedReader(new FileReader(filename));
+ String line;
+ try {
+ while ((line = in.readLine()) != null) {
+ Matcher m = pConnect.matcher(line);
+ if (m.matches()) {
+ connect(Boolean.parseBoolean(m.group(1)));
+ }
+ else if ((m = pDisconnect.matcher(line)).matches()) {
+ disconnect(Boolean.parseBoolean(m.group(1)), Boolean.parseBoolean(m.group(2)));
+ }
+ else if ((m = pSubscribe.matcher(line)).matches()) {
+ subscribe(m.group(1), Integer.parseInt(m.group(2)), Boolean.parseBoolean(m.group(3)));
+ }
+ else if ((m = pUnsubscribe.matcher(line)).matches()) {
+ unsubscribe(m.group(1), Boolean.parseBoolean(m.group(2)));
+ }
+ else if ((m = pPublish.matcher(line)).matches()) {
+ publish(m.group(1), m.group(2), Integer.parseInt(m.group(3)), Boolean.parseBoolean(m
+ .group(4)), Boolean.parseBoolean(m.group(5)), Boolean.parseBoolean(m.group(6)));
+ }
+ else if ((m = pPendingDeliveryTokens.matcher(line)).matches()) {
+ pendingDeliveryTokens(Integer.parseInt(m.group(1)));
+ }
+ }
+ }
+ catch (Exception e) {
+ if (client.isConnected()) {
+ client.disconnect();
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * @throws Exception
+ */
+ public void connect() throws Exception {
+ cleanSession = random.nextBoolean();
+ connect(cleanSession);
+ }
+
+ /**
+ * Connects the client.
+ * @param cleanSession1 whether to connect clean session.
+ * @throws Exception
+ */
+ public void connect(boolean cleanSession1) throws Exception {
+ logToFile("connect [cleanSession:" + cleanSession1 + "]");
+ if (cleanSession1) {
+ subscribedTopics.clear();
+ }
+ MqttConnectOptions opts = new MqttConnectOptions();
+ opts.setCleanSession(cleanSession1);
+ client.connect(opts);
+ }
+
+ /**
+ * @param connected
+ * @throws Exception
+ */
+ public void disconnect(boolean connected) throws Exception {
+ disconnect(cleanSession, connected);
+ }
+
+ /**
+ * Disconnects the client
+ * @param cleanSession1 whether this is a clean session being disconnected
+ * @param isConnected whether we think the client is currently connected
+ * @throws Exception
+ */
+ public void disconnect(boolean cleanSession1, boolean isConnected) throws Exception {
+ logToFile("disconnect [cleanSession:" + cleanSession1 + "][isConnected:" + client.isConnected()
+ + "]");
+ if (isConnected != client.isConnected()) {
+ throw new Exception("Client state mismatch [expected:" + isConnected + "][actual:"
+ + client.isConnected() + "]");
+ }
+ if (isConnected && cleanSession1) {
+ subscribedTopics.clear();
+ }
+ try {
+ client.disconnect();
+ }
+ catch (MqttException e) {
+ if (((e.getReasonCode() != 6) && (e.getReasonCode() != 32101)) || isConnected) {
+ throw e;
+ }
+ }
+ }
+
+ /**
+ * @throws Exception
+ */
+ public void subscribe() throws Exception {
+ String topic;
+ boolean expectRetained;
+ if (!retainedPublishes.isEmpty() && (random.nextInt(5) == 0)) {
+ Object[] topics1 = retainedPublishes.keySet().toArray();
+ topic = (String) topics1[random.nextInt(topics1.length)];
+
+ expectRetained = true;
+ }
+ else {
+ topic = UUID.randomUUID().toString();
+ expectRetained = false;
+ }
+ int qos = random.nextInt(3);
+ subscribe(topic, qos, expectRetained);
+ }
+
+ /**
+ * Subscribes to a given topic at the given qos
+ * @param topic the topic to subscribe to
+ * @param qos the qos to subscribe at
+ * @param expectRetained whether a retained message is expected to exist on this topic
+ * @throws Exception
+ */
+ public void subscribe(String topic, int qos, boolean expectRetained) throws Exception {
+ logToFile("subscribe [topic:" + topic + "][qos:" + qos + "][expectRetained:" + expectRetained
+ + "]");
+ subscribedTopics.put(topic, new Integer(qos));
+ client.subscribe(topic, qos);
+ if (expectRetained) {
+ waitForMessage(topic, retainedPublishes.get(topic), true);
+ }
+ }
+
+ /**
+ * @throws Exception
+ */
+ public void unsubscribe() throws Exception {
+ String topic;
+ boolean existing = false;
+ if (random.nextBoolean() && (subscribedTopics.size() > 0)) {
+ Object[] topics1 = subscribedTopics.keySet().toArray();
+ topic = (String) topics1[random.nextInt(topics1.length)];
+ existing = true;
+ }
+ else {
+ topic = UUID.randomUUID().toString();
+ }
+ unsubscribe(topic, existing);
+ }
+
+ /**
+ * Unsubscribes the given topic
+ * @param topic the topic to unsubscribe from
+ * @param existing whether we think we're currently subscribed to the topic
+ * @throws Exception
+ */
+ public void unsubscribe(String topic, boolean existing) throws Exception {
+ logToFile("unsubscribe [topic:" + topic + "][existing:" + existing + "]");
+ client.unsubscribe(topic);
+ Object o = subscribedTopics.remove(topic);
+ if (existing == (o == null)) {
+ throw new Exception("Subscription state mismatch [topic:" + topic + "][expected:" + existing
+ + "]");
+ }
+ }
+
+ /**
+ * @throws Exception
+ */
+ public void publish() throws Exception {
+ String topic;
+ boolean subscribed = false;
+ if (random.nextBoolean() && (subscribedTopics.size() > 0)) {
+ Object[] topics1 = subscribedTopics.keySet().toArray();
+ topic = (String) topics1[random.nextInt(topics1.length)];
+ subscribed = true;
+ }
+ else {
+ topic = UUID.randomUUID().toString();
+ }
+ String payload = UUID.randomUUID().toString();
+ int qos = random.nextInt(3);
+ boolean retained = random.nextInt(3) == 0;
+
+ // If the message is retained then we should wait for completion. If this isn't done there
+ // is a risk that a subsequent subscriber could be created to receive the message before it
+ // has been fully delivered and hence would not see the retained flag.
+ boolean waitForCompletion = (retained || (random.nextInt(1000) == 1));
+ publish(topic, payload, qos, retained, subscribed, waitForCompletion);
+
+ // For QoS0 messages, wait for completion takes no effect as there is no feedback from
+ // the server, and so even though not very deterministic, a small sleep is taken.
+ if (waitForCompletion && retained && (qos == 0)) {
+ Thread.sleep(50);
+ }
+ }
+
+ /**
+ * Publishes to the given topic
+ * @param topic the topic to publish to
+ * @param payload the payload to publish
+ * @param qos the qos to publish at
+ * @param retained whether to publish retained
+ * @param subscribed whether we think we're currently subscribed to the topic
+ * @param waitForCompletion whether we should wait for the message to complete delivery
+ * @throws Exception
+ */
+ public void publish(String topic, String payload, int qos, boolean retained, boolean subscribed,
+ boolean waitForCompletion) throws Exception {
+ logToFile("publish [topic:" + topic + "][payload:" + payload + "][qos:" + qos + "][retained:"
+ + retained + "][subscribed:" + subscribed + "][waitForCompletion:" + waitForCompletion
+ + "]");
+ if (subscribed != subscribedTopics.containsKey(topic)) {
+ throw new Exception("Subscription state mismatch [topic:" + topic + "][expected:"
+ + subscribed + "]");
+ }
+ MqttMessage msg = new MqttMessage(payload.getBytes());
+ msg.setQos(qos);
+ msg.setRetained(retained);
+ if (retained) {
+ retainedPublishes.put(topic, msg);
+ }
+ MqttDeliveryToken token = client.getTopic(topic).publish(msg);
+ synchronized (currentTokens) {
+ if (!token.isComplete()) {
+ currentTokens.put(token, "[" + topic + "][" + msg.toString() + "]");
+ }
+ }
+
+ if (retained || waitForCompletion) {
+ token.waitForCompletion();
+ synchronized (currentTokens) {
+ currentTokens.remove(token);
+ }
+ }
+ if (subscribed) {
+ waitForMessage(topic, msg, false);
+ }
+ }
+
+ /**
+ * @throws Exception
+ */
+ public void pendingDeliveryTokens() throws Exception {
+ IMqttDeliveryToken[] tokens = client.getPendingDeliveryTokens();
+
+ }
+
+ /**
+ * Checks the pending delivery tokens
+ * @param count the expected number of tokens to be returned
+ * @throws Exception
+ */
+ public void pendingDeliveryTokens(int count) throws Exception {
+ IMqttDeliveryToken[] tokens = client.getPendingDeliveryTokens();
+ logToFile("pendingDeliveryTokens [count:" + tokens.length + "]");
+ if (!client.isConnected() && (tokens.length != count)) {
+ throw new Exception("Unexpected pending tokens [expected:" + count + "][actual:"
+ + tokens.length + "]");
+ }
+ }
+
+ /**
+ * @param cause
+ */
+ public void connectionLost(Throwable cause) {
+ logToFile("Connection Lost:");
+ logToFile(cause);
+ }
+
+ /**
+ * @param token
+ */
+ public void deliveryComplete(IMqttDeliveryToken token) {
+ synchronized (currentTokens) {
+ currentTokens.remove(token);
+ }
+ }
+
+ /**
+ * Waits for the next message to arrive and checks it's values
+ * @param topic the topic expected
+ * @param message the message expected
+ * @param expectRetained whether the retain flag is expected to be set
+ * @throws Exception
+ */
+ public void waitForMessage(String topic, MqttMessage message, boolean expectRetained)
+ throws Exception {
+ synchronized (lock) {
+ int count = 0;
+ while (messages.size() == 0 && ++count < 10) {
+ lock.wait(1000);
+ }
+ if (messages.size() == 0) {
+ throw new Exception("message timeout [topic:" + topic + "]");
+ }
+ String rtopic = topics.remove(0);
+ MqttMessage rmessage = messages.remove(0);
+ if (!rtopic.equals(topic)) {
+ if (rmessage.isRetained() && !expectRetained) {
+ throw new Exception("pre-existing retained message [expectedTopic:" + topic
+ + "][expectedPayload:" + message.toString() + "] [receivedTopic:" + rtopic
+ + "][receivedPayload:" + rmessage.toString() + "]");
+ }
+ throw new Exception("message topic mismatch [expectedTopic:" + topic
+ + "][expectedPayload:" + message.toString() + "] [receivedTopic:" + rtopic
+ + "][receivedPayload:" + rmessage.toString() + "]");
+ }
+ if (!rmessage.toString().equals(message.toString())) {
+ if (rmessage.isRetained() && !expectRetained) {
+ throw new Exception("pre-existing retained message [expectedTopic:" + topic
+ + "][expectedPayload:" + message.toString() + "] [receivedTopic:" + rtopic
+ + "][receivedPayload:" + rmessage.toString() + "]");
+ }
+ throw new Exception("message payload mismatch [expectedTopic:" + topic
+ + "][expectedPayload:" + message.toString() + "] [receivedTopic:" + rtopic
+ + "][receivedPayload:" + rmessage.toString() + "]");
+ }
+ if (expectRetained && !rmessage.isRetained()) {
+ throw new Exception("message not retained [topic:" + topic + "]");
+ }
+ else if (!expectRetained && rmessage.isRetained()) {
+ throw new Exception("message retained [topic:" + topic + "]");
+ }
+ }
+ }
+
+ /**
+ * @param topic
+ * @param message
+ * @throws Exception
+ */
+ public void messageArrived(String topic, MqttMessage message) throws Exception {
+ synchronized (lock) {
+ messages.add(message);
+ topics.add(topic);
+ lock.notifyAll();
+ }
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/MqttTopicTest.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/MqttTopicTest.java
new file mode 100644
index 00000000..5cf52093
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/MqttTopicTest.java
@@ -0,0 +1,103 @@
+/** Copyright (c) 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test;
+
+import java.util.logging.Logger;
+
+import org.eclipse.paho.client.mqttv3.MqttTopic;
+import org.eclipse.paho.client.mqttv3.test.logging.LoggingUtilities;
+import org.eclipse.paho.client.mqttv3.test.utilities.Utility;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+
+/**
+ * Tests mqtt topic wildcards
+ */
+public class MqttTopicTest {
+
+ static final Class> cclass = MqttTopicTest.class;
+ private static final String className = cclass.getName();
+ private static final Logger log = Logger.getLogger(className);
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ }
+
+
+ @Test
+ public void testValidTopicFilterWildcards() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ String[] topics = new String[] {
+ "+", "+/+", "+/foo",
+ "+/tennis/#",
+ "foo/+", "foo/+/bar",
+ "/+", "/+/sport/+/player1",
+ "#", "/#",
+ "sport/#",
+ "sport/tennis/#"
+ };
+
+ for(String topic:topics){
+ MqttTopic.validate(topic, true);
+ }
+ }
+
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidTopicFilterWildcards1() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ MqttTopic.validate("sport/tennis#", true);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidTopicFilterWildcards2() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ MqttTopic.validate("sport/tennis/#/ranking", true);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidTopicFilterWildcards3() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ MqttTopic.validate("sport+", true);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidTopicFilterWildcards4() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ MqttTopic.validate("sport/+aa", true);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidTopicFilterWildcards5() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ MqttTopic.validate("sport/#/ball/+/aa", true);
+ }
+
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/SendReceiveAsyncTest.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/SendReceiveAsyncTest.java
new file mode 100644
index 00000000..41782737
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/SendReceiveAsyncTest.java
@@ -0,0 +1,654 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test;
+
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.eclipse.paho.client.mqttv3.IMqttAsyncClient;
+import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.IMqttToken;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.test.client.MqttClientFactoryPaho;
+import org.eclipse.paho.client.mqttv3.test.logging.LoggingUtilities;
+import org.eclipse.paho.client.mqttv3.test.properties.TestProperties;
+import org.eclipse.paho.client.mqttv3.test.utilities.MqttV3Receiver;
+import org.eclipse.paho.client.mqttv3.test.utilities.Utility;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class SendReceiveAsyncTest {
+
+ static final Class> cclass = SendReceiveAsyncTest.class;
+ static final String className = cclass.getName();
+ static final Logger log = Logger.getLogger(className);
+
+ private static URI serverURI;
+ private static MqttClientFactoryPaho clientFactory;
+
+ /**
+ * @throws Exception
+ */
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+
+ try {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+
+ serverURI = TestProperties.getServerURI();
+ clientFactory = new MqttClientFactoryPaho();
+ clientFactory.open();
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ throw exception;
+ }
+ }
+
+ /**
+ * @throws Exception
+ */
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+
+ try {
+ if (clientFactory != null) {
+ clientFactory.close();
+ clientFactory.disconnect();
+ }
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ }
+ }
+
+ /**
+ * Tests that a client can be constructed and that it can connect to and
+ * disconnect from the service
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testConnect() throws Exception {
+ final String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ log.entering(className, methodName);
+
+ IMqttAsyncClient mqttClient = null;
+ try {
+ mqttClient = clientFactory.createMqttAsyncClient(serverURI, methodName);
+ IMqttToken connectToken = null;
+ IMqttToken disconnectToken = null;
+
+ connectToken = mqttClient.connect(null, null);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:" + methodName);
+ connectToken.waitForCompletion();
+
+ disconnectToken = mqttClient.disconnect(null, null);
+ log.info("Disconnecting...");
+ disconnectToken.waitForCompletion();
+
+ connectToken = mqttClient.connect(null, null);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:" + methodName);
+ connectToken.waitForCompletion();
+
+ disconnectToken = mqttClient.disconnect(null, null);
+ log.info("Disconnecting...");
+ disconnectToken.waitForCompletion();
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ Assert.fail("Failed:" + methodName + " exception=" + exception);
+ }
+ finally {
+ if (mqttClient != null) {
+ log.info("Close...");
+ mqttClient.close();
+ }
+ }
+
+ log.exiting(className, methodName);
+ }
+
+ /**
+ * Test connection using a remote host name for the local host.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testRemoteConnect() throws Exception {
+ final String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ log.entering(className, methodName);
+
+ IMqttAsyncClient mqttClient = null;
+ try {
+ mqttClient = clientFactory.createMqttAsyncClient(serverURI, methodName);
+ IMqttToken connectToken = null;
+ IMqttToken subToken = null;
+ IMqttDeliveryToken pubToken = null;
+ IMqttToken disconnectToken = null;
+
+ connectToken = mqttClient.connect(null, null);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:" + methodName);
+ connectToken.waitForCompletion();
+
+ disconnectToken = mqttClient.disconnect(null, null);
+ log.info("Disconnecting...");
+ disconnectToken.waitForCompletion();
+
+ MqttV3Receiver mqttV3Receiver = new MqttV3Receiver(mqttClient, LoggingUtilities.getPrintStream());
+ log.info("Assigning callback...");
+ mqttClient.setCallback(mqttV3Receiver);
+
+ MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
+ mqttConnectOptions.setCleanSession(false);
+
+ connectToken = mqttClient.connect(mqttConnectOptions, null, null);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:" + methodName + ", cleanSession: false");
+ connectToken.waitForCompletion();
+
+ String[] topicNames = new String[]{methodName + "/Topic"};
+ int[] topicQos = {0};
+ subToken = mqttClient.subscribe(topicNames, topicQos, null, null);
+ log.info("Subscribing to..." + topicNames[0]);
+ subToken.waitForCompletion();
+
+ byte[] payload = ("Message payload " + className + "." + methodName).getBytes();
+ pubToken = mqttClient.publish(topicNames[0], payload, 1, false, null, null);
+ log.info("Publishing to..." + topicNames[0]);
+ pubToken.waitForCompletion();
+
+ boolean ok = mqttV3Receiver.validateReceipt(topicNames[0], 0,
+ payload);
+ if (!ok) {
+ Assert.fail("Receive failed");
+ }
+
+ disconnectToken = mqttClient.disconnect(null, null);
+ log.info("Disconnecting...");
+ disconnectToken.waitForCompletion();
+
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ Assert.fail("Failed:" + methodName + " exception=" + exception);
+ }
+ finally {
+ if (mqttClient != null) {
+ log.info("Close...");
+ mqttClient.close();
+ }
+ }
+
+ log.exiting(className, methodName);
+ }
+
+ /**
+ * Test client pubSub using very large messages
+ */
+ @Test
+ public void testLargeMessage() {
+ final String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ log.entering(className, methodName);
+
+ IMqttAsyncClient mqttClient = null;
+ try {
+ mqttClient = clientFactory.createMqttAsyncClient(serverURI, methodName);
+ IMqttToken connectToken;
+ IMqttToken subToken;
+ IMqttToken unsubToken;
+ IMqttDeliveryToken pubToken;
+
+ MqttV3Receiver mqttV3Receiver = new MqttV3Receiver(mqttClient, LoggingUtilities.getPrintStream());
+ log.info("Assigning callback...");
+ mqttClient.setCallback(mqttV3Receiver);
+
+ connectToken = mqttClient.connect(null, null);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:" + methodName);
+ connectToken.waitForCompletion();
+
+ int largeSize = 1000;
+ String[] topicNames = new String[]{methodName + "/Topic"};
+ int[] topicQos = {0};
+ byte[] message = new byte[largeSize];
+
+ java.util.Arrays.fill(message, (byte) 's');
+
+ subToken = mqttClient.subscribe(topicNames, topicQos, null, null);
+ log.info("Subscribing to..." + topicNames[0]);
+ subToken.waitForCompletion();
+
+ unsubToken = mqttClient.unsubscribe(topicNames, null, null);
+ log.info("Unsubscribing from..." + topicNames[0]);
+ unsubToken.waitForCompletion();
+
+ subToken = mqttClient.subscribe(topicNames, topicQos, null, null);
+ log.info("Subscribing to..." + topicNames[0]);
+ subToken.waitForCompletion();
+
+ pubToken = mqttClient.publish(topicNames[0], message, 0, false, null, null);
+ log.info("Publishing to..." + topicNames[0]);
+ pubToken.waitForCompletion();
+
+ boolean ok = mqttV3Receiver.validateReceipt(topicNames[0], 0,
+ message);
+ if (!ok) {
+ Assert.fail("Receive failed");
+ }
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ Assert.fail("Failed to instantiate:" + methodName + " exception="+ exception);
+ }
+ finally {
+ try {
+ if (mqttClient != null) {
+ IMqttToken disconnectToken;
+ disconnectToken = mqttClient.disconnect(null, null);
+ log.info("Disconnecting...");
+ disconnectToken.waitForCompletion();
+ log.info("Close...");
+ mqttClient.close();
+ }
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ }
+ }
+
+ log.exiting(className, methodName);
+ }
+
+ /**
+ * Multiple publishers and subscribers.
+ */
+ @Test
+ public void testMultipleClients() {
+ final String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ log.entering(className, methodName);
+
+ int publishers = 2;
+ int subscribers = 10;
+
+ IMqttAsyncClient[] mqttPublisher = new IMqttAsyncClient[publishers];
+ IMqttAsyncClient[] mqttSubscriber = new IMqttAsyncClient[subscribers];
+
+ IMqttToken connectToken;
+ IMqttToken subToken;
+ IMqttDeliveryToken pubToken;
+ IMqttToken disconnectToken;
+
+ try {
+ String[] topicNames = new String[]{methodName + "/Topic"};
+ int[] topicQos = {0};
+
+ for (int i = 0; i < mqttPublisher.length; i++) {
+ mqttPublisher[i] = clientFactory.createMqttAsyncClient(serverURI, "MultiPub" + i);
+ connectToken = mqttPublisher[i].connect(null, null);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId: MultiPub" + i);
+ connectToken.waitForCompletion();
+ } // for...
+
+ MqttV3Receiver[] mqttV3Receiver = new MqttV3Receiver[mqttSubscriber.length];
+ for (int i = 0; i < mqttSubscriber.length; i++) {
+ mqttSubscriber[i] = clientFactory.createMqttAsyncClient(serverURI, "MultiSubscriber" + i);
+ mqttV3Receiver[i] = new MqttV3Receiver(mqttSubscriber[i], LoggingUtilities.getPrintStream());
+ log.info("Assigning callback...");
+ mqttSubscriber[i].setCallback(mqttV3Receiver[i]);
+ connectToken = mqttSubscriber[i].connect(null, null);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId: MultiSubscriber" + i);
+ connectToken.waitForCompletion();
+ subToken = mqttSubscriber[i].subscribe(topicNames, topicQos, null, null);
+ log.info("Subcribing to..." + topicNames[0]);
+ subToken.waitForCompletion();
+ } // for...
+
+ for (int iMessage = 0; iMessage < 10; iMessage++) {
+ byte[] payload = ("Message " + iMessage).getBytes();
+ for (int i = 0; i < mqttPublisher.length; i++) {
+ pubToken = mqttPublisher[i].publish(topicNames[0], payload, 0, false, null, null);
+ log.info("Publishing to..." + topicNames[0]);
+ pubToken.waitForCompletion();
+ }
+
+ for (int i = 0; i < mqttSubscriber.length; i++) {
+ for (int ii = 0; ii < mqttPublisher.length; ii++) {
+ boolean ok = mqttV3Receiver[i].validateReceipt(
+ topicNames[0], 0, payload);
+ if (!ok) {
+ Assert.fail("Receive failed");
+ }
+ } // for publishers...
+ } // for subscribers...
+ } // for messages...
+
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ Assert.fail("Failed to instantiate:" + methodName + " exception="+ exception);
+ }
+ finally {
+ try {
+ for (int i = 0; i < mqttPublisher.length; i++) {
+ disconnectToken = mqttPublisher[i].disconnect(null, null);
+ log.info("Disconnecting...MultiPub" + i);
+ disconnectToken.waitForCompletion();
+ log.info("Close...");
+ mqttPublisher[i].close();
+ }
+ for (int i = 0; i < mqttSubscriber.length; i++) {
+ disconnectToken = mqttSubscriber[i].disconnect(null, null);
+ log.info("Disconnecting...MultiSubscriber" + i);
+ disconnectToken.waitForCompletion();
+ log.info("Close...");
+ mqttSubscriber[i].close();
+ }
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ }
+ }
+
+ log.exiting(className, methodName);
+ }
+
+ /**
+ * Test the behaviour of the cleanStart flag, used to clean up before
+ * re-connecting.
+ */
+ @Test
+ public void testCleanStart() throws Exception {
+ final String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ log.entering(className, methodName);
+
+ IMqttAsyncClient mqttClient = null;
+
+ IMqttToken connectToken;
+ IMqttToken subToken;
+ IMqttDeliveryToken pubToken;
+ IMqttToken disconnectToken;
+
+ try {
+ mqttClient = clientFactory.createMqttAsyncClient(serverURI, methodName);
+ MqttV3Receiver mqttV3Receiver = new MqttV3Receiver(mqttClient, LoggingUtilities.getPrintStream());
+ log.info("Assigning callback...");
+ mqttClient.setCallback(mqttV3Receiver);
+
+ MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
+ // Clean start: true - The broker cleans up all client state, including subscriptions, when the client is disconnected.
+ // Clean start: false - The broker remembers all client state, including subscriptions, when the client is disconnected.
+ // Matching publications will get queued in the broker whilst the client is disconnected.
+ // For Mqtt V3 cleanSession=false, implies new subscriptions are durable.
+ mqttConnectOptions.setCleanSession(false);
+ connectToken = mqttClient.connect(mqttConnectOptions, null, null);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:" + methodName + ", cleanSession: false");
+ connectToken.waitForCompletion();
+
+ String[] topicNames = new String[]{methodName + "/Topic"};
+ int[] topicQos = {0};
+ subToken = mqttClient.subscribe(topicNames, topicQos, null, null);
+ log.info("Subscribing to..." + topicNames[0]);
+ subToken.waitForCompletion();
+
+ byte[] payload = ("Message payload " + className + "." + methodName + " First").getBytes();
+ pubToken = mqttClient.publish(topicNames[0], payload, 1, false, null, null);
+ log.info("Publishing to..." + topicNames[0]);
+ pubToken.waitForCompletion();
+ boolean ok = mqttV3Receiver.validateReceipt(topicNames[0], 0,
+ payload);
+ if (!ok) {
+ Assert.fail("Receive failed");
+ }
+
+ // Disconnect and reconnect to make sure the subscription and all queued messages are cleared.
+ disconnectToken = mqttClient.disconnect(null, null);
+ log.info("Disconnecting...");
+ disconnectToken.waitForCompletion();
+ log.info("Close");
+ mqttClient.close();
+
+ // Send a message from another client, to our durable subscription.
+ mqttClient = clientFactory.createMqttAsyncClient(serverURI, methodName + "Other");
+ mqttV3Receiver = new MqttV3Receiver(mqttClient, LoggingUtilities.getPrintStream());
+ log.info("Assigning callback...");
+ mqttClient.setCallback(mqttV3Receiver);
+
+ mqttConnectOptions = new MqttConnectOptions();
+ mqttConnectOptions.setCleanSession(true);
+ connectToken = mqttClient.connect(mqttConnectOptions, null, null);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:" + methodName + "Other, cleanSession: true");
+ connectToken.waitForCompletion();
+
+ // Receive the publication so that we can be sure the first client has also received it.
+ // Otherwise the first client may reconnect with its clean session before the message has arrived.
+ subToken = mqttClient.subscribe(topicNames, topicQos, null, null);
+ log.info("Subscribing to..." + topicNames[0]);
+ subToken.waitForCompletion();
+ payload = ("Message payload " + className + "." + methodName + " Other client").getBytes();
+ pubToken = mqttClient.publish(topicNames[0], payload, 1, false, null, null);
+ log.info("Publishing to..." + topicNames[0]);
+ pubToken.waitForCompletion();
+ ok = mqttV3Receiver.validateReceipt(topicNames[0], 0, payload);
+ if (!ok) {
+ Assert.fail("Receive failed");
+ }
+ disconnectToken = mqttClient.disconnect(null, null);
+ log.info("Disconnecting...");
+ disconnectToken.waitForCompletion();
+ log.info("Close...");
+ mqttClient.close();
+
+ // Reconnect and check we have no messages.
+ mqttClient = clientFactory.createMqttAsyncClient(serverURI, methodName);
+ mqttV3Receiver = new MqttV3Receiver(mqttClient, LoggingUtilities.getPrintStream());
+ log.info("Assigning callback...");
+ mqttClient.setCallback(mqttV3Receiver);
+ mqttConnectOptions = new MqttConnectOptions();
+ mqttConnectOptions.setCleanSession(true);
+ connectToken = mqttClient.connect(mqttConnectOptions, null, null);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:" + methodName + ", cleanSession: true");
+ connectToken.waitForCompletion();
+ MqttV3Receiver.ReceivedMessage receivedMessage = mqttV3Receiver.receiveNext(100);
+ if (receivedMessage != null) {
+ Assert.fail("Receive messaqe:" + new String(receivedMessage.message.getPayload()));
+ }
+
+ // Also check that subscription is cancelled.
+ payload = ("Message payload " + className + "." + methodName + " Cancelled Subscription").getBytes();
+ pubToken = mqttClient.publish(topicNames[0], payload, 1, false, null, null);
+ log.info("Publishing to..." + topicNames[0]);
+ pubToken.waitForCompletion();
+
+ receivedMessage = mqttV3Receiver.receiveNext(100);
+ if (receivedMessage != null) {
+ log.fine("Message I shouldn't have: " + new String(receivedMessage.message.getPayload()));
+ Assert.fail("Receive messaqe:" + new String(receivedMessage.message.getPayload()));
+ }
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ Assert.fail("Failed:" + methodName + " exception=" + exception);
+ }
+ finally {
+ try {
+ if (mqttClient != null) {
+ disconnectToken = mqttClient.disconnect(null, null);
+ log.info("Disconnecting...");
+ disconnectToken.waitForCompletion();
+ log.info("Close...");
+ mqttClient.close();
+ }
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ }
+ }
+
+ log.exiting(className, methodName);
+ }
+
+ /**
+ * Short keep-alive intervals along with very large payloads (some MBis) results in the client being disconnected by
+ * the broker.
+ *
+ * In order to recreate the issue increase the value of waitMilliseconds in
+ * org.eclipse.paho.client.mqttv3.test.utilities.MqttV3Receiver.validateReceipt to some large value (e.g.
+ * 60*60*1000). This allows the test to wait for a longer time.
+ *
+ * The issue occurs because while receiving such a large payload no PING is sent by the client to the broker. This
+ * can be seen adding some debug statements in:
+ * org.eclipse.paho.client.mqttv3.internal.ClientState.checkForActivity.
+ *
+ * Since no other activity (messages from the client to the broker) is generated, the broker disconnects the client.
+ */
+ @Test
+ public void testVeryLargeMessageWithShortKeepAlive() {
+ final String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ log.entering(className, methodName);
+
+ IMqttAsyncClient mqttClient = null;
+ try {
+ mqttClient = clientFactory.createMqttAsyncClient(serverURI, "testVeryLargeMessage");
+ MqttV3Receiver mqttV3Receiver = new MqttV3Receiver(mqttClient, LoggingUtilities.getPrintStream());
+ log.info("Assigning callback...");
+ mqttClient.setCallback(mqttV3Receiver);
+
+ //keepAlive=30s
+ MqttConnectOptions options = new MqttConnectOptions();
+ options.setKeepAliveInterval(30);
+
+ IMqttToken connectToken = mqttClient.connect(options);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:" + methodName);
+ connectToken.waitForCompletion();
+
+ String topic = "testLargeMsg/Topic";
+ //10MB
+ int largeSize = 20 * (1 << 20);
+ byte[] message = new byte[largeSize];
+
+ java.util.Arrays.fill(message, (byte) 's');
+
+ IMqttToken subToken = mqttClient.subscribe(topic, 0);
+ log.info("Subscribing to..." + topic);
+ subToken.waitForCompletion();
+
+ IMqttToken pubToken = mqttClient.publish(topic, message, 0, false, null, null);
+ log.info("Publishing to..." + topic);
+ pubToken.waitForCompletion();
+ log.info("Published");
+
+ boolean ok = mqttV3Receiver.validateReceipt(topic, 0, message);
+ if (!ok) {
+ Assert.fail("Receive failed");
+ }
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ Assert.fail("Failed to instantiate:" + methodName + " exception=" + exception);
+ }
+ finally {
+ try {
+ if (mqttClient != null) {
+ IMqttToken disconnectToken = mqttClient.disconnect(null, null);
+ log.info("Disconnecting...");
+ disconnectToken.waitForCompletion();
+ mqttClient.close();
+ log.info("Closed");
+ }
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ }
+ }
+
+ log.exiting(className, methodName);
+ }
+
+ /**
+ * For bug - https://bugs.eclipse.org/bugs/show_bug.cgi?id=414783
+ * Test the behavior of the connection timeout when connecting to a non MQTT server.
+ * i.e. ssh port 22
+ */
+ @Test
+ public void testConnectTimeout() throws Exception {
+ final String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ log.entering(className, methodName);
+
+ IMqttAsyncClient mqttClient = null;
+ // Change the URI to a none MQTT server
+ URI uri = new URI("tcp://iot.eclipse.org:22");
+ IMqttToken connectToken = null;
+ try {
+ mqttClient = clientFactory.createMqttAsyncClient(uri, methodName);
+ log.info("Connecting...(serverURI:" + uri + ", ClientId:" + methodName);
+ connectToken = mqttClient.connect(new MqttConnectOptions());
+ connectToken.waitForCompletion(5000);
+ Assert.fail("Should throw an timeout exception.");
+ }
+ catch (Exception exception) {
+ log.log(Level.INFO, "Connect action failed at expected.");
+ Assert.assertTrue(exception instanceof MqttException);
+ Assert.assertEquals(MqttException.REASON_CODE_CLIENT_TIMEOUT, ((MqttException) exception).getReasonCode());
+ }
+ finally {
+ if (mqttClient != null) {
+ log.info("Close..." + mqttClient);
+ mqttClient.disconnectForcibly(5000, 5000);
+ }
+ }
+
+ //reuse the client instance to reconnect
+ try {
+ connectToken = mqttClient.connect(new MqttConnectOptions());
+ log.info("Connecting again...(serverURI:" + uri + ", ClientId:" + methodName);
+ connectToken.waitForCompletion(5000);
+ }
+ catch (Exception exception) {
+ log.log(Level.INFO, "Connect action failed at expected.");
+ Assert.assertTrue(exception instanceof MqttException);
+ Assert.assertEquals(
+ (MqttException.REASON_CODE_CLIENT_TIMEOUT == ((MqttException) exception).getReasonCode() ||
+ MqttException.REASON_CODE_CONNECT_IN_PROGRESS == ((MqttException) exception).getReasonCode())
+ , true);
+ }
+ finally {
+ if (mqttClient != null) {
+ log.info("Close..." + mqttClient);
+ mqttClient.disconnectForcibly(5000, 5000);
+ mqttClient.close();
+ }
+ }
+
+ Assert.assertFalse(mqttClient.isConnected());
+
+ log.exiting(className, methodName);
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/SendReceiveTest.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/SendReceiveTest.java
new file mode 100644
index 00000000..9ab3f0bc
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/SendReceiveTest.java
@@ -0,0 +1,503 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test;
+
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.eclipse.paho.client.mqttv3.IMqttClient;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttTopic;
+import org.eclipse.paho.client.mqttv3.test.client.MqttClientFactoryPaho;
+import org.eclipse.paho.client.mqttv3.test.logging.LoggingUtilities;
+import org.eclipse.paho.client.mqttv3.test.properties.TestProperties;
+import org.eclipse.paho.client.mqttv3.test.utilities.MqttV3Receiver;
+import org.eclipse.paho.client.mqttv3.test.utilities.Utility;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * This test expects an MQTT Server to be listening on the port
+ * given by the SERVER_URI property (which is 1883 by default)
+ */
+public class SendReceiveTest {
+
+ static final Class> cclass = SendReceiveTest.class;
+ private static final String className = cclass.getName();
+ private static final Logger log = Logger.getLogger(className);
+
+ private static URI serverURI;
+ private static MqttClientFactoryPaho clientFactory;
+
+ /**
+ * @throws Exception
+ */
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+
+ try {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+
+ serverURI = TestProperties.getServerURI();
+ clientFactory = new MqttClientFactoryPaho();
+ clientFactory.open();
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ throw exception;
+ }
+ }
+
+ /**
+ * @throws Exception
+ */
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+
+ try {
+ if (clientFactory != null) {
+ clientFactory.close();
+ clientFactory.disconnect();
+ }
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ }
+ }
+
+ /**
+ * Tests that a client can be constructed and that it can connect to and disconnect from the
+ * service
+ * @throws Exception
+ */
+ @Test
+ public void testConnect() throws Exception {
+ final String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ log.entering(className, methodName);
+
+ IMqttClient mqttClient = null;
+ try {
+ mqttClient = clientFactory.createMqttClient(serverURI, methodName);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:" + methodName);
+ mqttClient.connect();
+ log.info("Disconnecting...");
+ mqttClient.disconnect();
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:" + methodName);
+ mqttClient.connect();
+ log.info("Disconnecting...");
+ mqttClient.disconnect();
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ Assert.fail("Failed:" + methodName + " exception=" + exception);
+ }
+ finally {
+ if (mqttClient != null) {
+ log.info("Close...");
+ mqttClient.close();
+ }
+ }
+
+ log.exiting(className, methodName);
+ }
+
+ /**
+ * Test connection using a remote host name for the local host.
+ * @throws Exception
+ */
+ @Test
+ public void testRemoteConnect() throws Exception {
+ final String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ log.entering(className, methodName);
+
+ IMqttClient mqttClient = null;
+ try {
+ mqttClient = clientFactory.createMqttClient(serverURI, methodName);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:" + methodName);
+ mqttClient.connect();
+ log.info("Disconnecting...");
+ mqttClient.disconnect();
+
+ MqttV3Receiver mqttV3Receiver = new MqttV3Receiver(mqttClient, LoggingUtilities.getPrintStream());
+ log.info("Assigning callback...");
+ mqttClient.setCallback(mqttV3Receiver);
+ MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
+ mqttConnectOptions.setCleanSession(false);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:" + methodName + ", cleanSession: false");
+ mqttClient.connect(mqttConnectOptions);
+
+ String[] topicNames = new String[]{methodName + "/Topic"};
+ int[] topicQos = {0};
+ log.info("Subscribing to..." + topicNames[0]);
+ mqttClient.subscribe(topicNames, topicQos);
+
+ byte[] payload = ("Message payload " + className + "." + methodName).getBytes();
+ MqttTopic mqttTopic = mqttClient.getTopic(topicNames[0]);
+ log.info("Publishing to..." + topicNames[0]);
+ mqttTopic.publish(payload, 1, false);
+ boolean ok = mqttV3Receiver.validateReceipt(topicNames[0], 0, payload);
+ if (!ok) {
+ Assert.fail("Receive failed");
+ }
+ log.info("Disconnecting...");
+ mqttClient.disconnect();
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ Assert.fail("Failed:" + methodName + " exception=" + exception);
+ }
+ finally {
+ if (mqttClient != null) {
+ log.info("Close...");
+ mqttClient.close();
+ }
+ }
+
+ log.exiting(className, methodName);
+ }
+
+ /**
+ * Test client pubSub using largish messages
+ */
+ @Test
+ public void testLargeMessage() {
+ final String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ log.entering(className, methodName);
+
+ IMqttClient mqttClient = null;
+ try {
+ mqttClient = clientFactory.createMqttClient(serverURI, methodName);
+ MqttV3Receiver mqttV3Receiver = new MqttV3Receiver(mqttClient, LoggingUtilities.getPrintStream());
+ log.info("Assigning callback...");
+ mqttClient.setCallback(mqttV3Receiver);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:" + methodName);
+ mqttClient.connect();
+
+ int largeSize = 10000;
+ String[] topicNames = new String[]{methodName + "/Topic"};
+ int[] topicQos = {0};
+ byte[] message = new byte[largeSize];
+
+ java.util.Arrays.fill(message, (byte) 's');
+
+ log.info("Subscribing to..." + topicNames[0]);
+ mqttClient.subscribe(topicNames, topicQos);
+ log.info("Unsubscribing from..." + topicNames[0]);
+ mqttClient.unsubscribe(topicNames);
+ log.info("Subscribing to..." + topicNames[0]);
+ mqttClient.subscribe(topicNames, topicQos);
+ MqttTopic mqttTopic = mqttClient.getTopic(topicNames[0]);
+ log.info("Publishing to..." + topicNames[0]);
+ mqttTopic.publish(message, 0, false);
+
+ boolean ok = mqttV3Receiver.validateReceipt(topicNames[0], 0, message);
+ if (!ok) {
+ Assert.fail("Receive failed");
+ }
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ Assert.fail("Failed to instantiate:" + methodName + " exception=" + exception);
+ }
+ finally {
+ try {
+ if (mqttClient != null) {
+ log.info("Disconnecting...");
+ mqttClient.disconnect();
+ log.info("Close...");
+ mqttClient.close();
+ }
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ }
+ }
+
+ log.exiting(className, methodName);
+ }
+
+ /**
+ * Test that QOS values are preserved between MQTT publishers and subscribers.
+ */
+ @Test
+ public void testQoSPreserved() {
+ final String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ log.entering(className, methodName);
+
+ IMqttClient mqttClient = null;
+ try {
+ mqttClient = clientFactory.createMqttClient(serverURI, methodName);
+ MqttV3Receiver mqttV3Receiver = new MqttV3Receiver(mqttClient, LoggingUtilities.getPrintStream());
+ log.info("Assigning callback...");
+ mqttClient.setCallback(mqttV3Receiver);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:" + methodName);
+ mqttClient.connect();
+
+ String[] topicNames = new String[]{methodName + "/Topic0", methodName + "/Topic1", methodName + "/Topic2"};
+ int[] topicQos = {0, 1, 2};
+ for (int i = 0; i < topicNames.length; i++) {
+ log.info("Subscribing to..." + topicNames[i] + " at Qos " + topicQos[i]);
+ }
+ mqttClient.subscribe(topicNames, topicQos);
+
+ for (int i = 0; i < topicNames.length; i++) {
+ byte[] message = ("Message payload " + className + "." + methodName + " " + topicNames[i]).getBytes();
+ MqttTopic mqttTopic = mqttClient.getTopic(topicNames[i]);
+ for (int iQos = 0; iQos < 3; iQos++) {
+ log.info("Publishing to..." + topicNames[i] + " at Qos " + iQos);
+ mqttTopic.publish(message, iQos, false);
+ boolean ok = mqttV3Receiver.validateReceipt(topicNames[i], Math.min(iQos, topicQos[i]), message);
+ if (!ok) {
+ Assert.fail("Receive failed sub Qos=" + topicQos[i] + " PublishQos=" + iQos);
+ }
+ }
+ }
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ Assert.fail("Failed:" + methodName + " exception=" + exception);
+ }
+ finally {
+ try {
+ if (mqttClient != null) {
+ log.info("Disconnecting...");
+ mqttClient.disconnect();
+ log.info("Close...");
+ mqttClient.close();
+ }
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ }
+ }
+
+ log.exiting(className, methodName);
+ }
+
+ /**
+ * Multiple publishers and subscribers.
+ * @throws Exception
+ */
+ @Test
+ public void testMultipleClients() throws Exception {
+ final String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ log.entering(className, methodName);
+
+ IMqttClient[] mqttPublisher = new IMqttClient[2];
+ IMqttClient[] mqttSubscriber = new IMqttClient[10];
+ try {
+ String[] topicNames = new String[]{methodName + "/Topic"};
+ int[] topicQos = {0};
+
+ MqttTopic[] mqttTopic = new MqttTopic[mqttPublisher.length];
+ for (int i = 0; i < mqttPublisher.length; i++) {
+ mqttPublisher[i] = clientFactory.createMqttClient(serverURI, "MultiPub" + i);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId: MultiPub" + i);
+ mqttPublisher[i].connect();
+ mqttTopic[i] = mqttPublisher[i].getTopic(topicNames[0]);
+ } // for...
+
+ MqttV3Receiver[] mqttV3Receiver = new MqttV3Receiver[mqttSubscriber.length];
+ for (int i = 0; i < mqttSubscriber.length; i++) {
+ mqttSubscriber[i] = clientFactory.createMqttClient(serverURI, "MultiSubscriber" + i);
+ mqttV3Receiver[i] = new MqttV3Receiver(mqttSubscriber[i], LoggingUtilities.getPrintStream());
+ log.info("Assigning callback...");
+ mqttSubscriber[i].setCallback(mqttV3Receiver[i]);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId: MultiSubscriber" + i);
+ mqttSubscriber[i].connect();
+ log.info("Subcribing to..." + topicNames[0]);
+ mqttSubscriber[i].subscribe(topicNames, topicQos);
+ } // for...
+
+ for (int iMessage = 0; iMessage < 10; iMessage++) {
+ byte[] payload = ("Message " + iMessage).getBytes();
+ for (int i = 0; i < mqttPublisher.length; i++) {
+ log.info("Publishing to..." + topicNames[0]);
+ mqttTopic[i].publish(payload, 0, false);
+ }
+
+ for (int i = 0; i < mqttSubscriber.length; i++) {
+ for (int ii = 0; ii < mqttPublisher.length; ii++) {
+ boolean ok = mqttV3Receiver[i].validateReceipt(topicNames[0], 0, payload);
+ if (!ok) {
+ Assert.fail("Receive failed");
+ }
+ } // for publishers...
+ } // for subscribers...
+ } // for messages...
+
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ throw exception;
+ }
+ finally {
+ try {
+ for (int i = 0; i < mqttPublisher.length; i++) {
+ log.info("Disconnecting...MultiPub" + i);
+ mqttPublisher[i].disconnect();
+ log.info("Close...");
+ mqttPublisher[i].close();
+ }
+ for (int i = 0; i < mqttSubscriber.length; i++) {
+ log.info("Disconnecting...MultiSubscriber" + i);
+ mqttSubscriber[i].disconnect();
+ log.info("Close...");
+ mqttSubscriber[i].close();
+ }
+
+ Thread.sleep(5000);
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ }
+ }
+
+ log.exiting(className, methodName);
+ }
+
+ /**
+ * Test the behaviour of the cleanStart flag, used to clean up before re-connecting.
+ * @throws Exception
+ */
+ @Test
+ public void testCleanStart() throws Exception {
+ final String methodName = Utility.getMethodName();
+ LoggingUtilities.banner(log, cclass, methodName);
+ log.entering(className, methodName);
+
+ IMqttClient mqttClient = null;
+ try {
+ mqttClient = clientFactory.createMqttClient(serverURI, methodName);
+ MqttV3Receiver mqttV3Receiver = new MqttV3Receiver(mqttClient, LoggingUtilities.getPrintStream());
+ log.info("Assigning callback...");
+ mqttClient.setCallback(mqttV3Receiver);
+ MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
+ // Clean start: true - The broker cleans up all client state, including subscriptions, when the client is disconnected.
+ // Clean start: false - The broker remembers all client state, including subscriptions, when the client is disconnected.
+ // Matching publications will get queued in the broker whilst the client is disconnected.
+ // For Mqtt V3 cleanSession=false, implies new subscriptions are durable.
+ mqttConnectOptions.setCleanSession(false);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:" + methodName + ", cleanSession: false");
+ mqttClient.connect(mqttConnectOptions);
+
+ String[] topicNames = new String[]{methodName + "/Topic"};
+ int[] topicQos = {0};
+ log.info("Subscribing to..." + topicNames[0]);
+ mqttClient.subscribe(topicNames, topicQos);
+
+ byte[] payload = ("Message payload " + className + "." + methodName + " First").getBytes();
+ MqttTopic mqttTopic = mqttClient.getTopic(topicNames[0]);
+ log.info("Publishing to..." + topicNames[0]);
+ mqttTopic.publish(payload, 1, false);
+ boolean ok = mqttV3Receiver.validateReceipt(topicNames[0], 0, payload);
+ if (!ok) {
+ Assert.fail("Receive failed");
+ }
+
+ // Disconnect and reconnect to make sure the subscription and all queued messages are cleared.
+ log.info("Disconnecting...");
+ mqttClient.disconnect();
+ log.info("Close");
+ mqttClient.close();
+
+ // Send a message from another client, to our durable subscription.
+ mqttClient = clientFactory.createMqttClient(serverURI, methodName + "Other");
+ mqttV3Receiver = new MqttV3Receiver(mqttClient, LoggingUtilities.getPrintStream());
+ log.info("Assigning callback...");
+ mqttClient.setCallback(mqttV3Receiver);
+ mqttConnectOptions = new MqttConnectOptions();
+ mqttConnectOptions.setCleanSession(true);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:" + methodName + "Other, cleanSession: true");
+ mqttClient.connect(mqttConnectOptions);
+ // Receive the publication so that we can be sure the first client has also received it.
+ // Otherwise the first client may reconnect with its clean session before the message has arrived.
+ log.info("Subscribing to..." + topicNames[0]);
+ mqttClient.subscribe(topicNames, topicQos);
+ payload = ("Message payload " + className + "." + methodName + " Other client").getBytes();
+ mqttTopic = mqttClient.getTopic(topicNames[0]);
+ log.info("Publishing to..." + topicNames[0]);
+ mqttTopic.publish(payload, 1, false);
+ ok = mqttV3Receiver.validateReceipt(topicNames[0], 0, payload);
+ if (!ok) {
+ Assert.fail("Receive failed");
+ }
+ log.info("Disconnecting...");
+ mqttClient.disconnect();
+ log.info("Close...");
+ mqttClient.close();
+
+ // Reconnect and check we have no messages.
+ mqttClient = clientFactory.createMqttClient(serverURI, methodName);
+ mqttV3Receiver = new MqttV3Receiver(mqttClient, LoggingUtilities.getPrintStream());
+ log.info("Assigning callback...");
+ mqttClient.setCallback(mqttV3Receiver);
+ mqttConnectOptions = new MqttConnectOptions();
+ mqttConnectOptions.setCleanSession(true);
+ log.info("Connecting...(serverURI:" + serverURI + ", ClientId:" + methodName + ", cleanSession: true");
+ mqttClient.connect(mqttConnectOptions);
+ MqttV3Receiver.ReceivedMessage receivedMessage = mqttV3Receiver.receiveNext(100);
+ if (receivedMessage != null) {
+ Assert.fail("Receive messaqe:" + new String(receivedMessage.message.getPayload()));
+ }
+
+ // Also check that subscription is cancelled.
+ payload = ("Message payload " + className + "." + methodName + " Cancelled Subscription").getBytes();
+ mqttTopic = mqttClient.getTopic(topicNames[0]);
+ log.info("Publishing to..." + topicNames[0]);
+ mqttTopic.publish(payload, 1, false);
+
+ receivedMessage = mqttV3Receiver.receiveNext(100);
+ if (receivedMessage != null) {
+ log.info("Message I shouldn't have: " + new String(receivedMessage.message.getPayload()));
+ Assert.fail("Receive messaqe:" + new String(receivedMessage.message.getPayload()));
+ }
+ }
+ catch (Exception exception) {
+ log.log(Level.SEVERE, "caught exception:", exception);
+ throw exception;
+ }
+ finally {
+ try {
+ log.info("Disconnecting...");
+ mqttClient.disconnect();
+ }
+ catch (Exception exception) {
+ // do nothing
+ }
+
+ try {
+ log.info("Close...");
+ mqttClient.close();
+ }
+ catch (Exception exception) {
+ // do nothing
+ }
+ }
+
+ log.exiting(className, methodName);
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/client/MqttAsyncClientPaho.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/client/MqttAsyncClientPaho.java
new file mode 100644
index 00000000..922feada
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/client/MqttAsyncClientPaho.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test.client;
+
+import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
+import org.eclipse.paho.client.mqttv3.MqttClientPersistence;
+import org.eclipse.paho.client.mqttv3.MqttException;
+
+
+/**
+ *
+ */
+public class MqttAsyncClientPaho extends MqttAsyncClient {
+
+ /**
+ * @param serverURI
+ * @param clientId
+ * @throws MqttException
+ */
+ public MqttAsyncClientPaho(String serverURI, String clientId) throws MqttException {
+ super(serverURI, clientId);
+ }
+
+ /**
+ * @param serverURI
+ * @param clientId
+ * @param persistence
+ * @throws MqttException
+ */
+ public MqttAsyncClientPaho(String serverURI, String clientId, MqttClientPersistence persistence) throws MqttException {
+ super(serverURI, clientId, persistence);
+ }
+
+ /**
+ * @throws Exception
+ */
+ public void startTrace() throws Exception {
+ // not implemented
+ }
+
+ /**
+ * @throws Exception
+ */
+ public void stopTrace() throws Exception {
+ // not implemented
+ }
+
+ /**
+ * @return trace buffer
+ * @throws Exception
+ */
+ public String getTraceLog() throws Exception {
+ return null;
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/client/MqttClientFactoryPaho.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/client/MqttClientFactoryPaho.java
new file mode 100644
index 00000000..90c29b57
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/client/MqttClientFactoryPaho.java
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test.client;
+
+import java.net.URI;
+
+import org.eclipse.paho.client.mqttv3.IMqttAsyncClient;
+import org.eclipse.paho.client.mqttv3.IMqttClient;
+import org.eclipse.paho.client.mqttv3.MqttClientPersistence;
+
+
+/**
+ *
+ */
+public class MqttClientFactoryPaho {
+
+ /**
+ * @param serverURI
+ * @param clientId
+ * @return MqttClient
+ * @throws Exception
+ */
+ public IMqttClient createMqttClient(URI serverURI, String clientId) throws Exception {
+ return new MqttClientPaho(serverURI.toString(), clientId);
+ }
+
+ /**
+ * @param serverURI
+ * @param clientId
+ * @param persistence
+ * @return MqttClient
+ * @throws Exception
+ */
+ public IMqttClient createMqttClient(URI serverURI, String clientId, MqttClientPersistence persistence) throws Exception {
+ return new MqttClientPaho(serverURI.toString(), clientId, persistence);
+ }
+
+ /**
+ * @param serverURI
+ * @param clientId
+ * @return client
+ * @throws Exception
+ */
+ public IMqttAsyncClient createMqttAsyncClient(URI serverURI, String clientId) throws Exception {
+ return new MqttAsyncClientPaho(serverURI.toString(), clientId);
+ }
+
+ /**
+ * @param serverURI
+ * @param clientId
+ * @param persistence
+ * @return client
+ * @throws Exception
+ */
+ public IMqttAsyncClient createMqttAsyncClient(URI serverURI, String clientId, MqttClientPersistence persistence) throws Exception {
+ return new MqttAsyncClientPaho(serverURI.toString(), clientId, persistence);
+ }
+
+ /**
+ *
+ */
+ public void open() {
+ // empty
+ }
+
+ /**
+ *
+ */
+ public void close() {
+ // empty
+ }
+
+ /**
+ *
+ */
+ public void disconnect() {
+ // empty
+ }
+
+ /**
+ * @return flag indicating if this client supports High Availability
+ */
+ public boolean isHighAvalabilitySupported() {
+ return true;
+ }
+
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/client/MqttClientPaho.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/client/MqttClientPaho.java
new file mode 100644
index 00000000..60c9acbe
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/client/MqttClientPaho.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test.client;
+
+import org.eclipse.paho.client.mqttv3.MqttClient;
+import org.eclipse.paho.client.mqttv3.MqttClientPersistence;
+import org.eclipse.paho.client.mqttv3.MqttException;
+
+
+/**
+ *
+ */
+public class MqttClientPaho extends MqttClient {
+
+ /**
+ * @param serverURI
+ * @param clientId
+ * @throws MqttException
+ */
+ public MqttClientPaho(String serverURI, String clientId) throws MqttException {
+ super(serverURI, clientId);
+ }
+
+ /**
+ * @param serverURI
+ * @param clientId
+ * @param persistence
+ * @throws MqttException
+ */
+ public MqttClientPaho(String serverURI, String clientId, MqttClientPersistence persistence) throws MqttException {
+ super(serverURI, clientId, persistence);
+ }
+
+ /**
+ * @throws Exception
+ */
+ public void startTrace() throws Exception {
+ // not implemented
+ }
+
+ /**
+ * @throws Exception
+ */
+ public void stopTrace() throws Exception {
+ // not implemented
+ }
+
+ /**
+ * @return trace buffer
+ * @throws Exception
+ */
+ public String getTraceLog() throws Exception {
+ return null;
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/connectionLoss/ConnectionLossManualTest.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/connectionLoss/ConnectionLossManualTest.java
new file mode 100644
index 00000000..0ada5644
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/connectionLoss/ConnectionLossManualTest.java
@@ -0,0 +1,382 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test.connectionLoss;
+
+import java.util.Date;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.logging.Logger;
+
+import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
+import org.eclipse.paho.client.mqttv3.MqttCallback;
+import org.eclipse.paho.client.mqttv3.MqttClient;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence;
+import org.eclipse.paho.client.mqttv3.test.ManualTest;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+/**
+ * These tests verify whether paho can successfully detect a loss of connection with a broker.
+ *
+ * >> The tests MUST BE run manually and they WILL FAIL in an automated test environment. <<
+ * The pom.xml of the test project, all tests categorized as ManualTests are excluded from the execution.
+ *
+ * The tests will print a messages in the console notifying the operator when to "unplug" the internet connection of the test machine.
+ *
+ * @author mcarrer
+ */
+@Category(ManualTest.class)
+public class ConnectionLossManualTest implements MqttCallback
+{
+ static final Class> cclass = ConnectionLossManualTest.class;
+ private static final String className = cclass.getName();
+ private static final Logger log = Logger.getLogger(className);
+
+ private static final MqttDefaultFilePersistence DATA_STORE = new MqttDefaultFilePersistence("/tmp");
+
+ private String username = "username";
+ private char[] password = "password".toCharArray();
+ private String clientId = "device-client-id";
+ private String message = "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890";
+
+ /**
+ * Tests whether paho can detect a connection loss with the server even if it has outbound activity by publishing messages with QoS 0.
+ * @throws Exception
+ */
+ @Test
+ public void testConnectionLossWhilePublishingQos0()
+ throws Exception
+ {
+ final int keepAlive = 15;
+
+ MqttConnectOptions options = new MqttConnectOptions();
+ options.setCleanSession(true);
+ options.setUserName(username);
+ options.setPassword(password);
+ options.setKeepAliveInterval(keepAlive);
+
+ MqttClient client = new MqttClient("tcp://iot.eclipse.org:1883", clientId, DATA_STORE);
+ client.setCallback(this);
+ client.connect(options);
+
+ log.info((new Date())+" - Connected.");
+ for (int i=0; i<10; i++) {
+ log.info("Disconnect your network in "+(10-i)+" sec...");
+ client.publish(username+"/"+clientId+"/abc", message.getBytes(), 0, false);
+ Thread.sleep(1000);
+ }
+
+ final int[] res = new int[1];
+ new Timer().schedule( new TimerTask() {
+ @Override
+ public void run() {
+ res[0]++;
+ if (res[0] == keepAlive + 1) {
+ log.info((new Date())+" - Connection should be lost...");
+ }
+ }
+ }, 0, 1000);
+
+ while (client.isConnected() && res[0] < 2*keepAlive) {
+ try {
+ client.publish(username+"/"+clientId+"/abc", message.getBytes(), 0, false);
+ Thread.sleep(1000);
+ }
+ catch (MqttException e) {
+ // ignore
+ }
+ }
+
+ Assert.assertFalse("Disconected", client.isConnected());
+ if (client.isConnected()) client.disconnect(0);
+ client.close();
+ }
+
+
+ /**
+ * Tests whether paho can detect a connection loss with the server even if it has outbound activity by publishing messages with QoS 1.
+ * @throws Exception
+ */
+ @Test
+ public void testConnectionLossWhilePublishingQos1()
+ throws Exception
+ {
+ final int keepAlive = 15;
+
+ MqttConnectOptions options = new MqttConnectOptions();
+ options.setCleanSession(true);
+ options.setUserName(username);
+ options.setPassword(password);
+ options.setKeepAliveInterval(keepAlive);
+
+ MqttClient client = new MqttClient("tcp://iot.eclipse.org:1883", clientId, DATA_STORE);
+ client.setCallback(this);
+ client.connect(options);
+
+ log.info((new Date())+" - Connected.");
+ for (int i=0; i<10; i++) {
+ log.info("Disconnect your network in "+(10-i)+" sec...");
+ client.publish(username+"/"+clientId+"/abc", message.getBytes(), 1, false);
+ Thread.sleep(1000);
+ }
+
+ final int[] res = new int[1];
+ new Timer().schedule( new TimerTask() {
+ @Override
+ public void run() {
+ res[0]++;
+ if (res[0] == keepAlive + 1) {
+ log.info((new Date())+" - Connection should be lost...");
+ }
+ }
+ }, 0, 1000);
+
+ while (client.isConnected() && res[0] < 2*keepAlive) {
+ try {
+ client.publish(username+"/"+clientId+"/abc", message.getBytes(), 1, false);
+ Thread.sleep(1000);
+ }
+ catch (MqttException e) {
+ // ignore
+ }
+ }
+ log.info("Finished publishing...");
+
+ Assert.assertFalse("Disconected", client.isConnected());
+ if (client.isConnected()) client.disconnect(0);
+ client.close();
+ }
+
+
+ /**
+ * Tests whether paho can detect a connection loss with the server even if it has outbound activity by publishing messages with QoS 2.
+ * @throws Exception
+ */
+ @Test
+ public void testConnectionLossWhilePublishingQos2()
+ throws Exception
+ {
+ final int keepAlive = 15;
+
+ MqttConnectOptions options = new MqttConnectOptions();
+ options.setCleanSession(true);
+ options.setUserName(username);
+ options.setPassword(password);
+ options.setKeepAliveInterval(keepAlive);
+
+ MqttClient client = new MqttClient("tcp://iot.eclipse.org:1883", clientId, DATA_STORE);
+ client.setCallback(this);
+ client.connect(options);
+
+ log.info((new Date())+" - Connected.");
+ for (int i=0; i<10; i++) {
+ log.info("Disconnect your network in "+(10-i)+" sec...");
+ client.publish(username+"/"+clientId+"/abc", message.getBytes(), 2, false);
+ Thread.sleep(1000);
+ }
+
+ final int[] res = new int[1];
+ new Timer().schedule( new TimerTask() {
+ @Override
+ public void run() {
+ res[0]++;
+ if (res[0] == keepAlive + 1) {
+ log.info((new Date())+" - Connection should be lost...");
+ }
+ }
+ }, 0, 1000);
+
+ while (client.isConnected() && res[0] < 2*keepAlive) {
+ try {
+ client.publish(username+"/"+clientId+"/abc", message.getBytes(), 2, false);
+ Thread.sleep(1000);
+ }
+ catch (MqttException e) {
+ // ignore
+ }
+ }
+
+ Assert.assertFalse("Disconected", client.isConnected());
+ if (client.isConnected()) client.disconnect(0);
+ client.close();
+ }
+
+
+ /**
+ * Tests whether async paho can detect a connection loss with the server even if it has outbound activity by publishing messages with QoS 1.
+ * @throws Exception
+ */
+ @Test
+ public void testConnectionLossWhilePublishingQos1Async()
+ throws Exception
+ {
+ final int keepAlive = 15;
+
+ MqttConnectOptions options = new MqttConnectOptions();
+ options.setCleanSession(true);
+ options.setUserName(username);
+ options.setPassword(password);
+ options.setKeepAliveInterval(keepAlive);
+
+ MqttAsyncClient client = new MqttAsyncClient("tcp://iot.eclipse.org:1883", clientId, DATA_STORE);
+ client.setCallback(this);
+
+ log.info((new Date())+" - Connecting...");
+ client.connect(options);
+ while (!client.isConnected()) {
+ Thread.sleep(1000);
+ }
+
+ log.info((new Date())+" - Connected.");
+ for (int i=0; i<10; i++) {
+ log.info("Disconnect your network in "+(10-i)+" sec...");
+ client.publish(username+"/"+clientId+"/abc", message.getBytes(), 1, false);
+ Thread.sleep(1000);
+ }
+
+ final int[] res = new int[1];
+ new Timer().schedule( new TimerTask() {
+ @Override
+ public void run() {
+ res[0]++;
+ if (res[0] == keepAlive + 1) {
+ log.info((new Date())+" - Connection should be lost...");
+ }
+ }
+ }, 0, 1000);
+
+ boolean stopPublishing = false;
+ while (client.isConnected() && res[0] < 10*keepAlive) {
+ if (!stopPublishing) {
+ try {
+ client.publish(username+"/"+clientId+"/abc", message.getBytes(), 1, false);
+ log.info((new Date())+" - Published...");
+ Thread.sleep(1000);
+ }
+ catch (MqttException e) {
+ stopPublishing = true;
+ }
+ }
+ }
+
+ Assert.assertFalse("Disconected", client.isConnected());
+ if (client.isConnected()) client.disconnect(0);
+ client.close();
+ }
+
+
+ /**
+ * Tests whether paho keeps the connection alive for 10 keep alive intervals while publishing messages with QoS 0.
+ * @throws Exception
+ */
+ @Test
+ public void testKeepConnectionOpenWhilePublishingQos0()
+ throws Exception
+ {
+ final int keepAlive = 15;
+
+ MqttConnectOptions options = new MqttConnectOptions();
+ options.setCleanSession(true);
+ options.setUserName(username);
+ options.setPassword(password);
+ options.setKeepAliveInterval(keepAlive);
+
+ MqttClient client = new MqttClient("tcp://iot.eclipse.org:1883", clientId, DATA_STORE);
+ client.setCallback(this);
+ client.connect(options);
+
+ log.info((new Date())+" - Connected.");
+
+ final int[] res = new int[1];
+ new Timer().schedule( new TimerTask() {
+ @Override
+ public void run() {
+ res[0]++;
+ if (res[0] % keepAlive == 0) {
+ log.info((new Date())+" - Still running keep alive count: "+res[0]+"...");
+ }
+ }
+ }, 0, 1000);
+
+ while (client.isConnected() && res[0] < 10*keepAlive) {
+ client.publish(username+"/"+clientId+"/abc", message.getBytes(), 0, false);
+ Thread.sleep(1000);
+ }
+
+ Assert.assertTrue("Connected", client.isConnected());
+ if (client.isConnected()) client.disconnect(0);
+ client.close();
+ }
+
+
+ /**
+ * Tests whether paho keeps the connection alive for 10 keep alive in idle state.
+ * @throws Exception
+ */
+ @Test
+ public void testKeepConnectionOpenIdle()
+ throws Exception
+ {
+ final int keepAlive = 15;
+
+ MqttConnectOptions options = new MqttConnectOptions();
+ options.setCleanSession(true);
+ options.setUserName(username);
+ options.setPassword(password);
+ options.setKeepAliveInterval(keepAlive);
+
+ MqttClient client = new MqttClient("tcp://iot.eclipse.org:1883", clientId, DATA_STORE);
+ client.setCallback(this);
+ client.connect(options);
+
+ log.info((new Date())+" - Connected.");
+
+ final int[] res = new int[1];
+ new Timer().schedule( new TimerTask() {
+ @Override
+ public void run() {
+ res[0]++;
+ if (res[0] % keepAlive == 0) {
+ log.info((new Date())+" - Still running keep alive count: "+res[0]+"...");
+ }
+ }
+ }, 0, 1000);
+
+ while (client.isConnected() && res[0] < 10*keepAlive) {
+ Thread.sleep(1000);
+ }
+
+ Assert.assertTrue("Connected", client.isConnected());
+ if (client.isConnected()) client.disconnect(0);
+ client.close();
+ }
+
+ public void connectionLost(Throwable cause) {
+ log.info((new Date())+" - Connection Lost");
+ }
+
+ public void messageArrived(String topic, MqttMessage message) throws Exception {
+ log.info("Message Arrived on " + topic + " with " + new String(message.getPayload()));
+ }
+
+ public void deliveryComplete(IMqttDeliveryToken token) {
+// log.info("Delivery Complete: " + token.getMessageId());
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/ConsoleHandler.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/ConsoleHandler.java
new file mode 100644
index 00000000..5ad87dcc
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/ConsoleHandler.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test.logging;
+
+import java.util.logging.LogRecord;
+import java.util.logging.StreamHandler;
+
+/**
+ * Write console output to stdout (rather the default implementation which writes to stderr)
+ */
+public class ConsoleHandler extends StreamHandler {
+
+ /**
+ * Constructs a ConsoleHandler object.
+ */
+ public ConsoleHandler() {
+ super();
+ setOutputStream(System.out);
+ }
+
+ /**
+ * Logs a record if necessary. A flush operation will be done.
+ * @param record
+ */
+ @Override
+ public void publish(LogRecord record) {
+ super.publish(record);
+ super.flush();
+ }
+
+ /**
+ *
+ */
+ @Override
+ public void close() {
+ // Do nothing (the default implementation would close the stream!)
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/DetailFormatter.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/DetailFormatter.java
new file mode 100644
index 00000000..29af109b
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/DetailFormatter.java
@@ -0,0 +1,182 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test.logging;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.logging.Formatter;
+import java.util.logging.LogRecord;
+
+import org.eclipse.paho.client.mqttv3.test.utilities.StringUtilities;
+
+/**
+ * A log formatter which formats most of the LogRecord fields.
+ */
+public class DetailFormatter extends Formatter {
+
+ private final static SimpleDateFormat formater = new SimpleDateFormat("yyyyMMdd kkmmss.SSS");
+ private String NL = StringUtilities.NL;
+ private Date date = new Date();
+
+ /**
+ * Format the given LogRecord.
+ * @param record the log record to be formatted.
+ * @return a formatted log record
+ */
+ @Override
+ public synchronized String format(LogRecord record) {
+ StringBuffer sb = new StringBuffer();
+
+ String[] array = parseLogRecord(record);
+ String type = array[0];
+ String text = array[1];
+
+ addTimeStamp(record, sb);
+ addClassName(record, sb);
+ addTypeName(record, sb, type);
+ addMethodName(record, sb);
+ addText(record, sb, text);
+ addThrown(record, sb);
+
+ return sb.toString();
+ }
+
+ /**
+ * @param record
+ * @param sb
+ */
+ public void addThrown(LogRecord record, StringBuffer sb) {
+ Throwable thrown = record.getThrown();
+ if (thrown != null) {
+ try {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ thrown.printStackTrace(pw);
+ pw.close();
+ sb.append(sw.toString());
+ }
+ catch (Exception ex) {
+ // do nothing
+ }
+ }
+ }
+
+ /**
+ * @param record
+ * @param sb
+ * @param text
+ */
+ public void addText(LogRecord record, StringBuffer sb, String text) {
+ sb.append(text);
+ sb.append(NL);
+ }
+
+ /**
+ * @param record
+ * @param sb
+ */
+ public void addMethodName(LogRecord record, StringBuffer sb) {
+ sb.append(formatJavaName(record.getSourceMethodName(), 30));
+ sb.append(" ");
+ }
+
+ /**
+ * @param record
+ * @param sb
+ * @param type
+ */
+ public void addTypeName(LogRecord record, StringBuffer sb, String type) {
+ sb.append(type);
+ sb.append(" ");
+ }
+
+ /**
+ * @param record
+ * @param sb
+ */
+ public void addClassName(LogRecord record, StringBuffer sb) {
+ sb.append(formatJavaName(record.getSourceClassName(), 60));
+ sb.append(" ");
+ }
+
+ /**
+ * @param record
+ * @param sb
+ */
+ public void addTimeStamp(LogRecord record, StringBuffer sb) {
+ date.setTime(record.getMillis());
+ synchronized (formater) {
+ sb.append(formater.format(date));
+ }
+ sb.append(" ");
+ }
+
+ /**
+ * @param width
+ * @param n
+ * @return string
+ */
+ public String formatJavaName(String n, int width) {
+ String string = (n == null) ? "" : n;
+ return StringUtilities.left(string, width);
+ }
+
+ /**
+ * @param r
+ * @return string
+ */
+ public String[] parseLogRecord(LogRecord r) {
+
+ String type = " ";
+ String text = "";
+
+ String message = r.getMessage();
+ Throwable throwable = r.getThrown();
+ if (message != null) {
+ if (message.startsWith("ENTRY")) {
+ type = "-->";
+ text = formatParameters(r);
+ }
+ else if (message.startsWith("RETURN")) {
+ type = "<--";
+ text = formatParameters(r);
+ }
+ else if ((throwable != null) && ("THROW".equals(message))) {
+ text = "";
+ }
+ else {
+ text = message;
+ }
+ }
+
+ return new String[]{type, text};
+ }
+
+ /**
+ * @param r
+ * @return string
+ */
+ public String formatParameters(LogRecord r) {
+ String string = "";
+ Object[] parameters = r.getParameters();
+ if (parameters != null) {
+ string = ObjectFormatter.format(parameters);
+ }
+ return string;
+ }
+
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/HumanFormatter.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/HumanFormatter.java
new file mode 100644
index 00000000..7cf98b2b
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/HumanFormatter.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test.logging;
+
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+/**
+ * A log formatter which formats a reduced selection of the LogRecord fields.
+ */
+public class HumanFormatter extends DetailFormatter {
+
+ /**
+ * @param record
+ * @param sb
+ */
+ @Override
+ public void addClassName(LogRecord record, StringBuffer sb) {
+ // do nothing
+ }
+
+ /**
+ * @param sb
+ * @param type
+ */
+ @Override
+ public void addTypeName(LogRecord record, StringBuffer sb, String type) {
+
+ int intLevel = record.getLevel().intValue();
+ int intFINER = Level.FINER.intValue();
+
+ if (intLevel <= intFINER) {
+ sb.append(type);
+ sb.append(" ");
+ }
+ }
+
+ /**
+ * @param record
+ * @param sb
+ */
+ @Override
+ public void addMethodName(LogRecord record, StringBuffer sb) {
+
+ int intLevel = record.getLevel().intValue();
+ int intFINER = Level.FINER.intValue();
+
+ if (intLevel <= intFINER) {
+ sb.append(formatJavaName(record.getSourceMethodName(), 30));
+ sb.append(" ");
+ }
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/LoggerDumper.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/LoggerDumper.java
new file mode 100644
index 00000000..057dffd2
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/LoggerDumper.java
@@ -0,0 +1,133 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test.logging;
+
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+
+import org.eclipse.paho.client.mqttv3.test.utilities.StringUtilities;
+
+/**
+ * Utility class which dumps the formatters and handlers under a log manager
+ *
+ * The Java Util Logger package does not provide public method to dump its handlers and formatters in a useful way
+ * so the LogDumper class builds up a collection of the handlers using this simple container class which it can then dump
+ * in a human readable way.
+ */
+public class LoggerDumper {
+
+ static final Class> cclass = LoggerDumper.class;
+
+ private LoggerNode rootNode = null;
+
+ /**
+ * @throws Exception
+ *
+ */
+ public LoggerDumper() throws Exception {
+ LogManager mgr = LogManager.getLogManager();
+ Enumeration enumeration = mgr.getLoggerNames();
+ while (enumeration.hasMoreElements()) {
+ String name = enumeration.nextElement();
+ Logger log = mgr.getLogger(name);
+ findParentNode(log);
+ }
+ }
+
+ /**
+ * @param logger
+ * @throws Exception
+ */
+ private LoggerNode findParentNode(Logger logger) throws Exception {
+ LoggerNode parentNode = null;
+ Logger parent = logger.getParent();
+
+ if (parent == null) {
+ if (rootNode == null) {
+ parentNode = rootNode = new LoggerNode(null, logger);
+ }
+ else if (rootNode.getLogger() == logger) {
+ parentNode = rootNode;
+ }
+ else {
+ throw new Exception("duplicate root");
+ }
+ }
+ else {
+ parentNode = findParentNode(parent);
+
+ LoggerNode found = null;
+ for (Iterator iterator = parentNode.getChildren().iterator(); iterator.hasNext();) {
+ LoggerNode childNode = iterator.next();
+ if (childNode.getLogger() == logger) {
+ found = childNode;
+ break;
+ }
+ }
+ if (found == null) {
+ LoggerNode node = new LoggerNode(parentNode, logger);
+ parentNode.getChildren().add(node);
+ }
+ }
+
+ return parentNode;
+ }
+
+ /**
+ *
+ */
+ public void dump() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("-----------------------------------------------------------------" + StringUtilities.NL);
+ dumpLoggerNode(rootNode, 0, sb);
+ sb.append("-----------------------------------------------------------------");
+ System.out.println(sb.toString());
+ }
+
+ /**
+ * @param node
+ * @param sb
+ */
+ private void dumpLoggerNode(LoggerNode node, int indent, StringBuilder sb) {
+ Logger l = node.getLogger();
+ String padding = StringUtilities.left("", indent * 2);
+
+ sb.append(padding);
+ // sb.append("@" + Integer.toHexString(System.identityHashCode(l)) + " ");
+ sb.append("\"" + l.getName() + "\" ");
+ sb.append(l.getLevel());
+ sb.append(StringUtilities.NL);
+
+ Handler[] handlers = l.getHandlers();
+ for (Handler h : handlers) {
+ Formatter f = h.getFormatter();
+ sb.append(padding);
+ sb.append(" Handler = ");
+ sb.append(StringUtilities.left(h.getClass().getName(), 40));
+ sb.append("Formatter = ");
+ sb.append(StringUtilities.left(f.getClass().getName(), 40));
+ sb.append(StringUtilities.NL);
+ }
+
+ for (Iterator iterator = node.getChildren().iterator(); iterator.hasNext();) {
+ LoggerNode child = iterator.next();
+ dumpLoggerNode(child, indent + 1, sb);
+ }
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/LoggerNode.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/LoggerNode.java
new file mode 100644
index 00000000..ac23b1f0
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/LoggerNode.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test.logging;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.logging.Logger;
+
+/**
+ * The Java Util Logger package does not provide public method to dump its handlers and formatters in a useful way
+ * so the LogDumper class builds up a collection of the handlers using this simple container class which it can then dump
+ * in a human readable way.
+ */
+public class LoggerNode {
+
+ private LoggerNode parent;
+ private Logger logger;
+ private Set children;
+
+ /**
+ * @param p
+ * @param l
+ */
+ public LoggerNode(LoggerNode p, Logger l) {
+ parent = p;
+ logger = l;
+ children = new HashSet();
+ }
+
+ /**
+ * @return the parent
+ */
+ public LoggerNode getParent() {
+ return parent;
+ }
+
+ /**
+ * @return the logger
+ */
+ public Logger getLogger() {
+ return logger;
+ }
+
+ /**
+ * @return the children
+ */
+ public Collection getChildren() {
+ return children;
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return logger.getName();
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/LoggingUtilities.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/LoggingUtilities.java
new file mode 100644
index 00000000..7c5dbddb
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/LoggingUtilities.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test.logging;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+
+/**
+ * Class containing logging utility methods
+ */
+public class LoggingUtilities {
+
+ /**
+ * Configure logging by loading the logging.properties file
+ */
+ public static final Class> cclass = LoggingUtilities.class;
+
+ static {
+ String configClass = System.getProperty("java.util.logging.config.class");
+ String configFile = System.getProperty("java.util.logging.config.file");
+
+ if ((configClass == null) && (configFile == null)) {
+ try {
+ InputStream inputStream = cclass.getClassLoader().getResourceAsStream("logging.properties");
+ LogManager manager = LogManager.getLogManager();
+ manager.readConfiguration(inputStream);
+ inputStream.close();
+ }
+ catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * @return logStream
+ */
+ public static PrintStream getPrintStream() {
+ return System.out;
+ }
+
+ /**
+ * Log a banner containing the class and method name
+ *
+ * @param logger
+ * @param clazz
+ * @param methodName
+ */
+ public static void banner(Logger logger, Class> clazz, String methodName) {
+ banner(logger, clazz, methodName, null);
+ }
+
+ /**
+ * Log a banner containing the class and method name and text
+ *
+ * @param logger
+ * @param clazz
+ * @param methodName
+ * @param text
+ */
+ public static void banner(Logger logger, Class> clazz, String methodName, String text) {
+ String string = clazz.getSimpleName() + "." + methodName;
+ if (text != null) {
+ string += " " + text;
+ }
+
+ logger.info("");
+ logger.info("*************************************************************");
+ logger.info("* " + string);
+ logger.info("*************************************************************");
+ }
+
+ /**
+ * Dump the configuration of the log manager
+ *
+ * @throws Exception
+ */
+ public static void dump() throws Exception {
+ LoggerDumper loggerDumper = new LoggerDumper();
+ loggerDumper.dump();
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/ObjectFormatter.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/ObjectFormatter.java
new file mode 100644
index 00000000..099f3930
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/ObjectFormatter.java
@@ -0,0 +1,273 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test.logging;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Array;
+
+import org.eclipse.paho.client.mqttv3.test.utilities.StringUtilities;
+import org.xml.sax.Attributes;
+
+/**
+ * Utility class used by the framework logger to format an arbitrary object
+ */
+public class ObjectFormatter {
+
+ private StringBuffer buffer;
+ private int width1;
+ private int width2;
+ private String separator;
+
+ /**
+ * @param width1
+ * @param width2
+ * @param separator
+ */
+ public ObjectFormatter(int width1, int width2, String separator) {
+ buffer = new StringBuffer();
+ this.width1 = width1;
+ this.width2 = width2;
+ this.separator = separator;
+ }
+
+ private void addField(String lquote, String rquote, String name, String value) {
+ buffer.append(StringUtilities.left(name, width1));
+ buffer.append(StringUtilities.left(":", width2));
+ buffer.append(lquote);
+ buffer.append(value);
+ buffer.append(rquote);
+ buffer.append(separator);
+ }
+
+ private void addField(String lquote, String rquote, int name, String value) {
+ buffer.append(StringUtilities.right(Integer.toString(name), width1));
+ buffer.append(StringUtilities.left(":", width2));
+ buffer.append(lquote);
+ buffer.append(value);
+ buffer.append(rquote);
+ buffer.append(separator);
+ }
+
+ /**
+ * @param name
+ * @param value
+ */
+ public void add(String name, int value) {
+ addField("", "", name, Integer.toString(value));
+ }
+
+ /**
+ * @param name
+ * @param value
+ */
+ public void add(String name, long value) {
+ addField("", "", name, Long.toString(value));
+ }
+
+ /**
+ * @param name
+ * @param value
+ */
+ public void add(String name, boolean value) {
+ addField("", "", name, Boolean.toString(value));
+ }
+
+ /**
+ * @param name
+ * @param value
+ */
+ public void add(String name, String value) {
+ addField("'", "'", name, StringUtilities.safeString(value));
+ }
+
+ /**
+ * @param name
+ * @param value
+ */
+ public void add(String name, String[] value) {
+ if (value != null) {
+ buffer.append(StringUtilities.left(name, width1));
+ buffer.append(":");
+ buffer.append(separator);
+ for (int i = 0; i < value.length; i++) {
+ addField("(", ")", i, StringUtilities.safeString(value[i]));
+ }
+ }
+ }
+
+ /**
+ * @param name
+ * @param value
+ */
+ public void add(String name, int[] value) {
+ buffer.append(StringUtilities.left(name, width1));
+ buffer.append(StringUtilities.left(":", width2));
+ buffer.append("(");
+ for (int i = 0; i < value.length; i++) {
+ if (i > 0) {
+ buffer.append(',');
+ }
+ buffer.append(value[i]);
+ }
+ buffer.append(")");
+ }
+
+ /**
+ * @param name
+ * @param value
+ */
+ public void add(String name, byte[] value) {
+ addField("", "", name, StringUtilities.arrayToHexString(value));
+ }
+
+ /**
+ * @param name
+ * @param value
+ */
+ public void add(String name, Object value) {
+ addField("[", "]", name, StringUtilities.safeString(value));
+ }
+
+ /**
+ * @param value
+ * @return this instance
+ */
+ public ObjectFormatter append(String value) {
+ buffer.append(value);
+ buffer.append(separator);
+ return this;
+ }
+
+ /**
+ * @return string
+ */
+ @Override
+ public String toString() {
+ return buffer.toString();
+ }
+
+ /**
+ * @param object
+ * @return string
+ */
+ public static String format(Object object) {
+ StringBuilder sb = new StringBuilder();
+
+ if (object == null) {
+ sb.append("(null)");
+ }
+ else if (object instanceof Attributes) {
+ sb.append(format((Attributes) object));
+ }
+ else if (object instanceof String) {
+ sb.append(format((String) object));
+ }
+ else if (object instanceof StringBuffer) {
+ sb.append(format((StringBuffer) object));
+ }
+ else if (object instanceof StringBuilder) {
+ sb.append(format((StringBuilder) object));
+ }
+ else if (object instanceof Throwable) {
+ sb.append(format((Throwable) object));
+ }
+ else {
+ boolean isArray = object.getClass().isArray();
+ if (isArray) {
+ int arrayLength = Array.getLength(object);
+ if (arrayLength > 1) {
+ sb.append("[");
+ }
+ for (int i = 0; i < arrayLength; i++) {
+ Object element = Array.get(object, i);
+ if (i > 0) {
+ sb.append(", ");
+ }
+ sb.append(format(element));
+ }
+ if (arrayLength > 1) {
+ sb.append("]");
+ }
+ }
+ else {
+ sb.append(object);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * @param attributes
+ * @return string
+ */
+ public static String format(Attributes attributes) {
+
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < attributes.getLength(); i++) {
+ if (i > 0) {
+ sb.append(" ");
+ }
+ sb.append("[");
+ String name = attributes.getQName(i);
+ String value = attributes.getValue(i);
+ sb.append(name);
+ sb.append("=\"");
+ sb.append(value);
+ sb.append("\"");
+ sb.append("]");
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * @param text
+ * @return string
+ */
+ public static String format(String text) {
+ return StringUtilities.toJavaString(text);
+ }
+
+ /**
+ * @param sb
+ * @return string
+ */
+ public static String format(StringBuffer sb) {
+ return format(sb.toString());
+ }
+
+ /**
+ * @param sb
+ * @return string
+ */
+ public static String format(StringBuilder sb) {
+ return format(sb.toString());
+ }
+
+ /**
+ * @param t
+ * @return string
+ */
+ public static String format(Throwable t) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw, true);
+ t.printStackTrace(pw);
+ pw.flush();
+ sw.flush();
+ return sw.toString();
+ }
+
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/TraceFormatter.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/TraceFormatter.java
new file mode 100644
index 00000000..8cf81a85
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/logging/TraceFormatter.java
@@ -0,0 +1,135 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test.logging;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.logging.Formatter;
+import java.util.logging.LogRecord;
+
+import org.eclipse.paho.client.mqttv3.test.utilities.StringUtilities;
+
+/**
+ * A log formatter which formats the LogRecord fields in a way which is suitable for tracing
+ */
+public class TraceFormatter extends Formatter {
+
+ private final static SimpleDateFormat formater = new SimpleDateFormat("kk:mm:ss.SSS");
+ private String NL = StringUtilities.NL;
+ private Date date = new Date();
+
+ /**
+ *
+ */
+ public TraceFormatter() {
+ System.out.println("");
+ }
+
+ /**
+ * Format the given LogRecord.
+ * @param record the log record to be formatted.
+ * @return a formatted log record
+ */
+ @Override
+ public synchronized String format(LogRecord record) {
+ StringBuffer sb = new StringBuffer();
+
+ String[] array = parseLogRecord(record);
+ String type = array[0];
+ String text = array[1];
+
+ date.setTime(record.getMillis());
+ sb.append(formater.format(date));
+ sb.append(" ");
+
+ sb.append(formatJavaName(record.getSourceClassName(), 60));
+ sb.append(" ");
+ sb.append(type);
+ sb.append(" ");
+ sb.append(formatJavaName(record.getSourceMethodName(), 30));
+ sb.append(" ");
+ sb.append(text);
+ sb.append(NL);
+
+ Throwable thrown = record.getThrown();
+ if (thrown != null) {
+ try {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ thrown.printStackTrace(pw);
+ pw.close();
+ sb.append(sw.toString());
+ }
+ catch (Exception ex) {
+ // do nothing
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * @param width
+ * @param n
+ * @return string
+ */
+ private String formatJavaName(String n, int width) {
+ String string = (n == null) ? "" : n;
+ return StringUtilities.left(string, width);
+ }
+
+ /**
+ * @param r
+ * @return string
+ */
+ public String[] parseLogRecord(LogRecord r) {
+
+ String string = " ";
+ String text = "";
+
+ String message = r.getMessage();
+ if (message != null) {
+ if (message.startsWith("ENTRY")) {
+ string = "-->";
+ text = formatParameters(r);
+ }
+ else if (message.startsWith("RETURN")) {
+ string = "<--";
+ text = formatParameters(r);
+ }
+ else {
+ text = message;
+ }
+ }
+
+ return new String[]{string, text};
+ }
+
+ /**
+ * @param r
+ * @return string
+ */
+ public String formatParameters(LogRecord r) {
+ String string = "";
+ Object[] parameters = r.getParameters();
+ if (parameters != null) {
+ string = ObjectFormatter.format(parameters);
+ }
+ return string;
+ }
+
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/properties/TestProperties.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/properties/TestProperties.java
new file mode 100644
index 00000000..97fd466b
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/properties/TestProperties.java
@@ -0,0 +1,439 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test.properties;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.eclipse.paho.client.mqttv3.test.client.MqttClientFactoryPaho;
+import org.eclipse.paho.client.mqttv3.test.utilities.Utility;
+
+/**
+ * Contains the classes and utilities used to configure the MQTT testcases.
+ *
+ *
+ * The way in which a test is run is controlled by properties which are typically supplied in property file.
+ * A {@link org.eclipse.paho.client.mqttv3.test.properties.TestProperties TestProperties} class provides default values and getter methods
+ * to supported properties. When the properties have been loaded, the framework logs the non-default values
+ *
+ *
+ * Test properties are initialised by loading a property file as follows:
+ *
+ *
Get filename from system property
+ *
+ *
Get filename from system property TEST_PROPERTY_FILENAME with a default value of test.properties
+ *
Load properties using this filename as a file on the filesystem
+ *
+ *
+ *
Else use the default filename
+ *
+ *
Use the default filename test.properties
+ *
Load properties using this filename as a resource in the same package as the TestProperties class
+ *
+ *
+ *
+ *
+ * A property loaded from a file is overridden by a system property of the same name.
+ *
+ *
+ * A property file loaded as a resource may be located in any eclipse project on the runtime classpath.
+ *
+ *
Note: If you intend to run a testcase to run against a server but the the property file is in a server eclipse project,
+ * remember to set the "project" setting on the eclipse run configuration to make eclipse add the server eclipse project
+ * to the runtime classpath, otherwise the testcase will load the wrong properties
+ *
+ *
+ */
+public class TestProperties {
+
+ static private final Class> cclass = TestProperties.class;
+ static private final String className = cclass.getName();
+ static private final Logger log = Logger.getLogger(className);
+
+ /**
+ * The URI of the test MQTT Server.
+ *
+ * The default value is tcp://<localhost>:1883> where <localhost> is expressed as a IPv4 dotted decimal value
+ */
+ static public final String KEY_SERVER_URI = "SERVER_URI";
+
+ /**
+ * The working directory usd by the framework.
+ *
+ * The default value is system property java.io.tmpdir
+ */
+ static public final String KEY_WORKING_DIR = "WORKING_DIR";
+
+ /**
+ * The class name of the client factory the tests are to be run against.
+ *
+ *
+ * The following client factories have been defined
+ *
+ *
{@link org.eclipse.paho.client.mqttv3.test.client.pahoJava.MqttClientFactoryPahoJava PahaJava} (This is the default)
+ *
+ */
+ static public final String KEY_CLIENT_TYPE = "CLIENT_TYPE";
+
+ static public final String KEY_CLIENT_KEY_STORE = "CLIENT_KEY_STORE";
+
+ static public final String KEY_CLIENT_KEY_STORE_PASSWORD = "CLIENT_KEY_STORE_PASSWORD";
+
+ static public final String KEY_CLIENT_TRUST_STORE = "CLIENT_TRUST_STORE";
+
+ static public final String KEY_SERVER_SSL_PORT = "SERVER_SSL_PORT";
+
+ static private Map defaults = new HashMap();
+
+ private static TestProperties singleton;
+ private Properties properties = new Properties();
+
+ static {
+ String temporaryDirectoryName = System.getProperty("java.io.tmpdir");
+
+ String localhost = "localhost";
+ try {
+ localhost = InetAddress.getLocalHost().getHostAddress();
+ }
+ catch (UnknownHostException e) {
+ // empty
+ }
+ String defaultServerURI = "tcp://" + localhost + ":1883";
+
+ putDefault(KEY_WORKING_DIR, temporaryDirectoryName);
+ putDefault(KEY_SERVER_URI, defaultServerURI);
+ putDefault(KEY_CLIENT_TYPE, MqttClientFactoryPaho.class.getName());
+ putDefault(KEY_SERVER_SSL_PORT, "8883");
+
+ // Make sure all the property classes we know about get initialised
+ List list = new ArrayList();
+ list.add("org.eclipse.paho.client.mqttv3.test.properties.ClientTestProperties");
+ list.add("org.eclipse.paho.client.mqttv3.test.properties.MqTestProperties");
+ list.add("org.eclipse.paho.client.mqttv3.test.properties.ImsTestProperties");
+
+ for (String name : list) {
+ try {
+ Class.forName(name);
+ }
+ catch (ClassNotFoundException exception) {
+ log.finest("Property class '" + name + "' not found");
+ }
+ }
+ }
+
+ /**
+ * @param key
+ * @param defaultValue
+ */
+ public static void putDefault(String key, String defaultValue) {
+ defaults.put(key, defaultValue);
+ }
+
+ /**
+ * @return TestProperties
+ */
+ public static TestProperties getInstance() {
+ if (singleton == null) {
+ singleton = new TestProperties();
+ }
+ return singleton;
+ }
+
+ /**
+ * Reads properties from a properties file in the same path as this class
+ * - first look for the property file on the filesystem
+ */
+ public TestProperties() {
+
+ InputStream stream = null;
+ try {
+ String filename = System.getProperty("TEST_PROPERTY_FILENAME", "test.properties");
+ stream = getPropertyFileAsStream(filename);
+
+ if (stream == null) {
+ filename = "test.properties";
+ stream = cclass.getClassLoader().getResourceAsStream(filename);
+ }
+
+ // Read the properties from the property file
+ if (stream != null) {
+ log.info("Loading properties from: '" + filename + "'");
+ properties.load(stream);
+ }
+ }
+ catch (Exception e) {
+ log.log(Level.SEVERE, "caught exception:", e);
+ }
+ finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ }
+ catch (IOException e) {
+ log.log(Level.SEVERE, "caught exception:", e);
+ }
+ }
+ }
+
+ // Override the default property values from SystemProperties
+ for (String key : defaults.keySet()) {
+ String systemValue = System.getProperty(key);
+ if (systemValue != null) {
+ properties.put(key, systemValue);
+ }
+ }
+
+ for (Object object : properties.keySet()) {
+ if (object instanceof String) {
+ String key = (String) object;
+
+ // Override the property values from SystemProperties
+ String systemValue = System.getProperty(key);
+ if (systemValue != null) {
+ properties.put(key, systemValue);
+ }
+
+ String defaultValue = defaults.get(key);
+ String value = getProperty(key);
+
+ // Output the non-default properties
+ boolean isSame = false;
+
+ if (defaultValue == null) {
+ if (value == null) {
+ isSame = true;
+ }
+ }
+ else if (value != null) {
+ isSame = defaultValue.equals(value);
+ }
+
+ if (systemValue != null) {
+ log.info(" System property: " + key + " = " + getProperty(key));
+ }
+ else if (isSame == false) {
+ log.info(" " + key + " = " + getProperty(key));
+ }
+ }
+ }
+ }
+
+ /**
+ * @param filename
+ * @return stream
+ * @throws IOException
+ */
+ private InputStream getPropertyFileAsStream(String filename) throws IOException {
+ InputStream stream = null;
+ try {
+ stream = new FileInputStream(filename);
+ }
+ catch (Exception exception) {
+ log.finest("Property file: '" + filename + "' not found");
+ }
+
+ return stream;
+ }
+
+ /**
+ * This is equivalent to class.getResourceAsStream but allows us to report the URL location
+ *
+ * @param filename
+ * @return stream
+ * @throws IOException
+ */
+ private InputStream getPropertyResourceAsStream(String filename) throws IOException {
+
+ InputStream stream = null;
+ URL url = TestProperties.class.getResource(filename);
+
+ if (url == null) {
+ log.info("Property resource: '" + filename + "' not found");
+ }
+ else {
+ log.info("Property resource: '" + filename + "' found at '" + url + "'");
+ stream = url.openStream();
+
+ if (stream == null) {
+ log.info("Could not open stream to Property resource: '" + filename + "'");
+ }
+ }
+
+ return stream;
+ }
+
+ /**
+ * @param key
+ * @return value
+ */
+ public String getProperty(String key) {
+ String value = properties.getProperty(key);
+
+ if (value == null) {
+ value = defaults.get(key);
+ }
+
+ return value;
+ }
+
+ /**
+ * @param key
+ * @return value
+ */
+ public boolean getBooleanProperty(String key) {
+ String value = getProperty(key);
+ return Boolean.parseBoolean(value);
+ }
+
+ /**
+ * @param key
+ * @return value
+ */
+ public int getIntProperty(String key) {
+ String value = getProperty(key);
+ return Integer.parseInt(value);
+ }
+
+ /**
+ * @return working directory
+ */
+ public static File getTemporaryDirectory() {
+ String pathname = getInstance().getProperty(KEY_WORKING_DIR);
+ return new File(pathname);
+ }
+
+ /**
+ * @return keystore file
+ */
+
+ public static String getClientKeyStore() {
+ URL keyStore = cclass.getClassLoader().getResource(getInstance().getProperty(KEY_CLIENT_KEY_STORE));
+ return keyStore.getPath();
+ }
+
+ /**
+ * @return keystore file password
+ */
+
+ public static String getClientKeyStorePassword() {
+ String keyStorePassword = getInstance().getProperty(KEY_CLIENT_KEY_STORE_PASSWORD);
+ return keyStorePassword;
+ }
+
+ /**
+ * @return truststore file
+ */
+
+ public static String getClientTrustStore() {
+ URL trustStore = cclass.getClassLoader().getResource(getInstance().getProperty(KEY_CLIENT_TRUST_STORE));
+ return trustStore.getPath();
+ }
+
+ /**
+ * @return the SSL port of the server for testing
+ */
+
+ public static int getServerSSLPort() {
+ int port = Integer.parseInt(getInstance().getProperty(KEY_SERVER_SSL_PORT));
+ return port;
+ }
+
+ /**
+ * @return The server URI which may be set in the constructor of an MqttClient
+ * @throws URISyntaxException
+ */
+ public static URI getServerURI() throws URISyntaxException {
+ String methodName = Utility.getMethodName();
+ log.entering(className, methodName);
+
+ String string = getInstance().getProperty(KEY_SERVER_URI);
+ URI uri = new URI(string);
+
+ log.exiting(className, methodName, string);
+ return uri;
+ }
+
+ /**
+ * Returns a list of URIs which may set in the MQTTConnectOptions for an HA testcase
+ *
+ * @return value
+ * @throws URISyntaxException
+ */
+ public static List getServerURIs() throws URISyntaxException {
+ TestProperties testProperties = getInstance();
+
+ List list = new ArrayList();
+ int index = 0;
+ String uri = testProperties.getProperty(KEY_SERVER_URI + "." + index);
+ while (uri != null) {
+ list.add(new URI(uri));
+ index++;
+ uri = testProperties.getProperty(KEY_SERVER_URI + "." + index);
+ }
+
+ return list;
+ }
+
+ /**
+ * Returns an array list or URIs which may be used by an HA testcase
+ *
+ * @return value
+ * @throws URISyntaxException
+ */
+ public static List getServerURIsAsListOfStrings() throws URISyntaxException {
+ List list1 = getServerURIs();
+
+ List list2 = new ArrayList();
+
+ for (int i = 0; i < list1.size(); i++) {
+ list2.add(list1.get(i).toString());
+ }
+
+ return list2;
+ }
+
+ /**
+ * Returns an array list or URIs which may be used by an HA testcase
+ *
+ * @return value
+ * @throws URISyntaxException
+ */
+ public static String[] getServerURIsAsStringArray() throws URISyntaxException {
+ List list = getServerURIs();
+
+ String[] array = new String[list.size()];
+
+ for (int i = 0; i < list.size(); i++) {
+ array[i] = list.get(i).toString();
+ }
+
+ return array;
+ }
+
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/utilities/MqttV3Receiver.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/utilities/MqttV3Receiver.java
new file mode 100644
index 00000000..fb52ad88
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/utilities/MqttV3Receiver.java
@@ -0,0 +1,454 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test.utilities;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Logger;
+
+import org.eclipse.paho.client.mqttv3.IMqttAsyncClient;
+import org.eclipse.paho.client.mqttv3.IMqttClient;
+import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.MqttCallback;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+/**
+ * Listen for in bound messages and connection loss.
+ */
+public class MqttV3Receiver implements MqttCallback {
+
+ static final String className = MqttV3Receiver.class.getName();
+ static final Logger log = Logger.getLogger(className);
+
+ final static String TRACE_GROUP = "Test";
+
+ private final PrintStream reportStream;
+ private boolean reportConnectionLoss = true;
+ private boolean connected = false;
+ private String clientId;
+
+ /**
+ * For the in bound message.
+ */
+ public class ReceivedMessage {
+
+ /** */
+ public String topic;
+ /** */
+ public MqttMessage message;
+
+ ReceivedMessage(String topic, MqttMessage message) {
+ this.topic = topic;
+ this.message = message;
+ }
+ }
+
+ List receivedMessages = new ArrayList();
+
+ /**
+ * @param mqttClient
+ * @param reportStream
+ */
+ public MqttV3Receiver(IMqttClient mqttClient, PrintStream reportStream) {
+ String methodName = Utility.getMethodName();
+ log.entering(className, methodName);
+
+ this.reportStream = reportStream;
+ connected = true;
+
+ clientId = mqttClient.getClientId();
+
+ log.exiting(className, methodName);
+ }
+
+ /**
+ * @param mqttClient
+ * @param reportStream
+ */
+ public MqttV3Receiver(IMqttAsyncClient mqttClient, PrintStream reportStream) {
+ String methodName = Utility.getMethodName();
+ log.entering(className, methodName);
+
+ this.reportStream = reportStream;
+ connected = true;
+
+ clientId = mqttClient.getClientId();
+
+ log.exiting(className, methodName);
+ }
+
+ /**
+ * @return flag
+ */
+ public final boolean isReportConnectionLoss() {
+ return reportConnectionLoss;
+ }
+
+ /**
+ * @param reportConnectionLoss
+ */
+ public final void setReportConnectionLoss(boolean reportConnectionLoss) {
+ this.reportConnectionLoss = reportConnectionLoss;
+ }
+
+ /**
+ * @param waitMilliseconds
+ * @return message
+ * @throws InterruptedException
+ */
+ public synchronized ReceivedMessage receiveNext(long waitMilliseconds) throws InterruptedException {
+ final String methodName = "receiveNext";
+ log.entering(className, methodName);
+
+ ReceivedMessage receivedMessage = null;
+ if (receivedMessages.isEmpty()) {
+ wait(waitMilliseconds);
+ }
+ if (!receivedMessages.isEmpty()) {
+ receivedMessage = receivedMessages.remove(0);
+ }
+
+ log.exiting(className, methodName);
+ return receivedMessage;
+ }
+
+ /**
+ * @param sendTopic
+ * @param expectedQos
+ * @param sentBytes
+ * @return flag
+ * @throws MqttException
+ * @throws InterruptedException
+ */
+ public boolean validateReceipt(String sendTopic, int expectedQos, byte[] sentBytes) throws MqttException, InterruptedException {
+ final String methodName = "validateReceipt";
+ log.entering(className, methodName, new Object[]{sendTopic, expectedQos});
+
+ long waitMilliseconds = 40*30000;
+ ReceivedMessage receivedMessage = receiveNext(waitMilliseconds);
+ if (receivedMessage == null) {
+ report(" No message received in waitMilliseconds=" + waitMilliseconds);
+ log.exiting(className, methodName, "Return false: " + receivedMessage);
+ return false;
+ }
+
+ if (!sendTopic.equals(receivedMessage.topic)) {
+ report(" Received invalid topic sent=" + sendTopic + " received topic=" + receivedMessage.topic);
+ log.exiting(className, methodName, "Return false: " + receivedMessage);
+ return false;
+ }
+
+ if (!java.util.Arrays.equals(sentBytes,
+ receivedMessage.message.getPayload())) {
+ report("Received invalid payload="
+ + Arrays.toString(receivedMessage.message.getPayload()) + "\n" + "Sent:"
+ + new String(sentBytes) + "\n" + "Received:"
+ + new String(receivedMessage.message.getPayload()));
+ log.exiting(className, methodName, "Return false: " + receivedMessage);
+ return false;
+ }
+
+ if (expectedQos != receivedMessage.message.getQos()) {
+ report("expectedQos=" + expectedQos + " != Received Qos="
+ + receivedMessage.message.getQos());
+ log.exiting(className, methodName, "Return false: " + receivedMessage);
+ return false;
+ }
+
+ log.exiting(className, methodName, new Object[]{"true"});
+ return true;
+ }
+
+ /**
+ * Validate receipt of a batch of messages sent to a topic by a number of
+ * publishers The message payloads are expected to have the format
+ * "Batch Message payload ::::"
+ *
+ * We want to detect excess messages, so we don't just handle a certain
+ * number. Instead we wait for a timeout period, and exit if no message is
+ * received in that period. The timeout period can make this test long
+ * running, so we attempt to dynamically adjust, allowing 10 seconds for the
+ * first message and then averaging the time taken to receive messages and
+ * applying some fudge factors.
+ *
+ * @param sendTopics
+ * @param expectedQosList
+ * @param nPublishers
+ * @param expectedBatchNumber
+ * @param sentBytes
+ * @param expectOrdered
+ * @return flag
+ * @throws MqttException
+ * @throws InterruptedException
+ */
+ public boolean validateReceipt(List sendTopics, List expectedQosList,
+ int expectedBatchNumber, int nPublishers, List sentBytes,
+ boolean expectOrdered) throws MqttException, InterruptedException {
+ final String methodName = "validateReceipt";
+ log.entering(className, methodName, new Object[]{
+ sendTopics, expectedQosList, sentBytes});
+
+ int expectedMessageNumbers[] = new int[nPublishers];
+ for (int i = 0; i < nPublishers; i++) {
+ expectedMessageNumbers[i] = 0;
+ }
+ long waitMilliseconds = 10000;
+
+ // track time taken to receive messages
+ long totWait = 0;
+ int messageNo = 0;
+ while (true) {
+ long startWait = System.currentTimeMillis();
+ ReceivedMessage receivedMessage = receiveNext(waitMilliseconds);
+ if (receivedMessage == null) {
+ break;
+ }
+ messageNo++;
+ totWait += (System.currentTimeMillis() - startWait);
+
+ // Calculate new wait time based on experience, but not allowing it
+ // to get too small
+ waitMilliseconds = Math.max(totWait / messageNo, 500);
+
+ byte[] payload = receivedMessage.message.getPayload();
+ String payloadString = new String(payload);
+ if (!payloadString.startsWith("Batch Message payload :")) {
+ report("Received invalid payload\n" + "Received:"
+ + payloadString);
+ report("Payload did not start with {"
+ + "Batch Message payload :" + "}");
+ log.exiting(className, methodName, "Return false: " + receivedMessage);
+ return false;
+ }
+
+ String[] payloadParts = payloadString.split(":");
+ if (payloadParts.length != 5) {
+ report("Received invalid payload\n" + "Received:"
+ + payloadString);
+ report("Payload was not of expected format");
+ log.finer("Return false: " + receivedMessage);
+ return false;
+ }
+
+ try {
+ int batchNumber = Integer.parseInt(payloadParts[1]);
+ if (batchNumber != expectedBatchNumber) {
+ report("Received invalid payload\n" + "Received:"
+ + payloadString);
+ report("batchnumber" + batchNumber
+ + " was not the expected value "
+ + expectedBatchNumber);
+ log.exiting(className, methodName, "Return false: " + receivedMessage);
+ return false;
+ }
+ }
+ catch (NumberFormatException e) {
+ report("Received invalid payload\n" + "Received:"
+ + payloadString);
+ report("batchnumber was not a numeric value");
+ log.exiting(className, methodName, "Return false: " + receivedMessage);
+ return false;
+ }
+
+ int publisher = -1;
+ try {
+ publisher = Integer.parseInt(payloadParts[2]);
+ if ((publisher < 0) || (publisher >= nPublishers)) {
+ report("Received invalid payload\n" + "Received:"
+ + payloadString);
+ report("publisher " + publisher
+ + " was not in the range 0 - " + (nPublishers - 1));
+ log.exiting(className, methodName, "Return false: " + receivedMessage);
+ return false;
+ }
+ }
+ catch (NumberFormatException e) {
+ report("Received invalid payload\n" + "Received:"
+ + payloadString);
+ report("publisher was not a numeric value");
+ log.exiting(className, methodName, "Return false: " + receivedMessage);
+ return false;
+ }
+
+ if (expectOrdered) {
+ try {
+ int messageNumber = Integer.parseInt(payloadParts[3]);
+ if (messageNumber == expectedMessageNumbers[publisher]) {
+ expectedMessageNumbers[publisher] += 1;
+ }
+ else {
+ report("Received invalid payload\n" + "Received:"
+ + payloadString);
+ report("messageNumber "
+ + messageNumber
+ + " was received out of sequence - expected value was "
+ + expectedMessageNumbers[publisher]);
+ log.exiting(className, methodName, "Return false: " + receivedMessage);
+ return false;
+ }
+ }
+ catch (NumberFormatException e) {
+ report("Received invalid payload\n" + "Received:"
+ + payloadString);
+ report("messageNumber was not a numeric value");
+ log.exiting(className, methodName, "Return false: " + receivedMessage);
+ return false;
+ }
+ }
+
+ int location;
+ for (location = 0; location < sentBytes.size(); location++) {
+ if (Arrays.equals(payload, sentBytes.get(location))) {
+ break;
+ }
+ }
+
+ String sendTopic = null;
+ int expectedQos = -1;
+ if (location < sentBytes.size()) {
+ sentBytes.remove(location);
+ sendTopic = sendTopics.remove(location);
+ expectedQos = expectedQosList.remove(location);
+ }
+ else {
+ report("Received invalid payload\n" + "Received:"
+ + payloadString);
+ for (byte[] expectedPayload : sentBytes) {
+ report("\texpected message :" + new String(expectedPayload));
+ }
+ log.exiting(className, methodName, "Return false: " + receivedMessage);
+ return false;
+ }
+
+ if (!sendTopic.equals(receivedMessage.topic)) {
+ report(" Received invalid topic sent=" + sendTopic
+ + " received topic=" + receivedMessage.topic);
+ log.exiting(className, methodName, "Return false: " + receivedMessage);
+ return false;
+ }
+
+ if (expectedQos != receivedMessage.message.getQos()) {
+ report("expectedQos=" + expectedQos + " != Received Qos="
+ + receivedMessage.message.getQos());
+ log.exiting(className, methodName, "Return false: " + receivedMessage);
+ return false;
+ }
+
+ }
+
+ if (!sentBytes.isEmpty()) {
+ for (byte[] missedPayload : sentBytes) {
+ report("Did not receive message \n" + new String(missedPayload));
+ }
+ log.exiting(className, methodName, "Return false");
+ return false;
+ }
+
+ log.exiting(className, methodName,
+ new Object[]{"return true"});
+ return true;
+ }
+
+ /**
+ * @param waitMilliseconds
+ * @return flag
+ * @throws InterruptedException
+ */
+ public synchronized boolean waitForConnectionLost(long waitMilliseconds)
+ throws InterruptedException {
+ final String methodName = "waitForConnectionLost";
+ log.entering(className, methodName, new Object[]{
+ waitMilliseconds, connected});
+
+ if (connected) {
+ wait(waitMilliseconds);
+ }
+
+ log.exiting(className, methodName,
+ new Object[]{connected});
+ return connected;
+ }
+
+ /**
+ * @param cause
+ */
+ public void connectionLost(Throwable cause) {
+ final String methodName = "connectionLost";
+ log.entering(className, methodName, new Object[]{cause,
+ connected});
+
+ if (reportConnectionLoss) {
+ report("ConnectionLost: clientId=" + clientId + " cause=" + cause);
+ }
+
+ synchronized (this) {
+ connected = false;
+ notifyAll();
+ }
+
+ log.exiting(className, methodName);
+ }
+
+ /**
+ * @param arg0
+ */
+ public void deliveryComplete(IMqttDeliveryToken arg0) {
+ // Auto-generated method stub
+ }
+
+ /**
+ * @param arg0
+ * @param arg1
+ */
+ public void deliveryFailed(IMqttDeliveryToken arg0, MqttException arg1) {
+ // Auto-generated method stub
+ }
+
+ /**
+ * @param topic
+ * @param message
+ * @throws Exception
+ */
+ public synchronized void messageArrived(String topic, MqttMessage message) throws Exception {
+ final String methodName = "messageArrived";
+ log.entering(className, methodName, new Object[]{topic,
+ message});
+
+ // logger.fine(methodName + ": '" + new String(message.getPayload()) + "'");
+ receivedMessages.add(new ReceivedMessage(topic, message));
+ notify();
+
+ log.exiting(className, methodName);
+ }
+
+ public synchronized List getReceivedMessagesInCopy(){
+ return new ArrayList(receivedMessages);
+ }
+
+ /**
+ * @param text
+ */
+ public void report(String text) {
+ StackTraceElement[] stack = (new Throwable()).getStackTrace();
+ reportStream.println(stack[1].getClassName() + ":" + stack[1].getLineNumber() + " " + text);
+ }
+
+ public int receivedMessageCount(){
+ return receivedMessages.size();
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/utilities/StringUtilities.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/utilities/StringUtilities.java
new file mode 100644
index 00000000..8b5552ae
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/utilities/StringUtilities.java
@@ -0,0 +1,226 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test.utilities;
+
+import java.text.MessageFormat;
+import java.util.logging.Logger;
+
+/**
+ * Static utility functions
+ */
+public class StringUtilities {
+
+ private static final String className = StringUtilities.class.getName();
+ private final static Logger log = Logger.getLogger(className);
+
+ /** Lookup the line separator once */
+ public static final String NL = System.getProperty("line.separator");
+
+ /**
+ * @param bytes
+ * @return string
+ */
+ public static String localizedByteCount(long bytes) {
+ MessageFormat form = new MessageFormat("{0,number,integer}");
+ Object[] args = {bytes};
+ return form.format(args);
+ }
+
+ /**
+ * @param bytes
+ * @param si
+ * @return string
+ */
+ public static String humanReadableByteCount(long bytes, boolean si) {
+ int unit = si ? 1000 : 1024;
+ if (bytes < unit) {
+ return bytes + " B";
+ }
+ int exp = (int) (Math.log(bytes) / Math.log(unit));
+ String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
+ return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
+ }
+
+ /**
+ * Helper method to convert a byte[] array (such as a MsgId) to a hex string
+ *
+ * @param array
+ * @return hex string
+ */
+ public static String arrayToHexString(byte[] array) {
+ return arrayToHexString(array, 0, array.length);
+ }
+
+ /**
+ * Helper method to convert a byte[] array (such as a MsgId) to a hex string
+ *
+ * @param array
+ * @param offset
+ * @param limit
+ * @return hex string
+ */
+ public static String arrayToHexString(byte[] array, int offset, int limit) {
+ String retVal;
+ if (array != null) {
+ StringBuffer hexString = new StringBuffer(array.length);
+ int hexVal;
+ char hexChar;
+ int length = Math.min(limit, array.length);
+ for (int i = offset; i < length; i++) {
+ hexVal = (array[i] & 0xF0) >> 4;
+ hexChar = (char) ((hexVal > 9) ? ('A' + (hexVal - 10)) : ('0' + hexVal));
+ hexString.append(hexChar);
+ hexVal = array[i] & 0x0F;
+ hexChar = (char) ((hexVal > 9) ? ('A' + (hexVal - 10)) : ('0' + hexVal));
+ hexString.append(hexChar);
+ }
+ retVal = hexString.toString();
+ }
+ else {
+ retVal = "";
+ }
+ return retVal;
+ }
+
+ /**
+ * @param text
+ * @return a Java string
+ */
+ public static String toJavaString(String text) {
+
+ String string = text;
+ if (string != null) {
+ string = string.replaceAll("\n", "\\\\n");
+ string = string.replaceAll("\r", "\\\\r");
+ string = string.replaceAll("\"", "\\\\\"");
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("\"");
+ sb.append(string);
+ sb.append("\"");
+
+ return sb.toString();
+ }
+
+ /**
+ * @param object
+ * @return a non-null string based on the given string
+ */
+ public static String safeString(Object object) {
+ return (object == null) ? "" : object.toString();
+ }
+
+ /**
+ * Left justify a string, padding with spaces.
+ *
+ * @param s the string to justify
+ * @param width the field width to justify within
+ *
+ * @return the justified string.
+ */
+ public static String left(String s, int width) {
+ return left(s, width, ' ');
+ }
+
+ /**
+ * Left justify a string.
+ *
+ * @param s the string to justify
+ * @param width the field width to justify within
+ * @param fillChar the character to fill with
+ *
+ * @return the justified string.
+ */
+ public static String left(String s, int width, char fillChar) {
+ if (s.length() >= width) {
+ return s;
+ }
+ StringBuffer sb = new StringBuffer(width);
+ sb.append(s);
+ for (int i = width - s.length(); --i >= 0;) {
+ sb.append(fillChar);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Right justify a string, padding with spaces.
+ *
+ * @param s the string to justify
+ * @param width the field width to justify within
+ *
+ * @return the justified string.
+ */
+ public static String right(String s, int width) {
+ return right(s, width, ' ');
+ }
+
+ /**
+ * Right justify a string.
+ *
+ * @param s the string to justify
+ * @param width the field width to justify within
+ * @param fillChar the character to fill with
+ *
+ * @return the justified string.
+ */
+ public static String right(String s, int width, char fillChar) {
+ if (s.length() >= width) {
+ return s;
+ }
+ StringBuffer sb = new StringBuffer(width);
+ for (int i = width - s.length(); --i >= 0;) {
+ sb.append(fillChar);
+ }
+ sb.append(s);
+ return sb.toString();
+ }
+
+ /**
+ * @param level1
+ * @param level2
+ * @return the higher level
+ */
+ public static String getHigherLevel(String level1, String level2) {
+ int idx;
+ String rlevel = level1;
+
+ idx = 6;
+ if (level1 == null) {
+ rlevel = level2;
+ }
+ else if (level2 == null) {
+ rlevel = level1;
+ }
+ else {
+
+ while (idx < Math.min(level1.length(), level2.length())) {
+ int f = Integer.parseInt(level1.substring(idx, idx + 1));
+ int c = Integer.parseInt(level2.substring(idx, idx + 1));
+ if (f > c) {
+ rlevel = level1;
+ break;
+ }
+ else if (f < c) {
+ rlevel = level2;
+ break;
+ }
+ idx++;
+ }
+ }
+ return rlevel;
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/utilities/Utility.java b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/utilities/Utility.java
new file mode 100644
index 00000000..1feea404
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test/utilities/Utility.java
@@ -0,0 +1,106 @@
+/* Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ *******************************************************************************/
+
+package org.eclipse.paho.client.mqttv3.test.utilities;
+
+import java.io.IOException;
+import java.util.logging.FileHandler;
+import java.util.logging.Logger;
+
+import org.eclipse.paho.client.mqttv3.IMqttAsyncClient;
+import org.eclipse.paho.client.mqttv3.IMqttClient;
+import org.eclipse.paho.client.mqttv3.IMqttToken;
+import org.eclipse.paho.client.mqttv3.MqttException;
+
+/**
+ * General purpose test utilities
+ */
+public class Utility {
+
+ static final String className = Utility.class.getName();
+ static final Logger log = Logger.getLogger(className);
+
+ /**
+ * @return the current method name for the caller.
+ */
+ public static String getMethodName() {
+ StackTraceElement[] stack = (new Throwable()).getStackTrace();
+ String methodName = stack[1].getMethodName();
+
+ // Skip over synthetic accessor methods
+ if (methodName.equals("access$0")) {
+ methodName = stack[2].getMethodName();
+ }
+
+ return methodName;
+ }
+
+ /**
+ * @return 'true' if running on Windows
+ */
+ public static boolean isWindows() {
+ String osName = System.getProperty("os.name");
+ if (osName.startsWith("Windows")) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @param client
+ * @throws MqttException
+ */
+ public static void disconnectAndCloseClient(IMqttAsyncClient client) throws MqttException {
+ if (client != null) {
+ if (client.isConnected()) {
+ IMqttToken token = client.disconnect(null, null);
+ token.waitForCompletion();
+ }
+ client.close();
+ }
+ }
+
+ /**
+ * @param client
+ * @throws MqttException
+ */
+ public static void disconnectAndCloseClient(IMqttClient client) throws MqttException {
+ if (client != null) {
+ if (client.isConnected()) {
+ client.disconnect(0);
+ }
+ client.close();
+ }
+ }
+
+ /**
+ * Used to turn trace on dynamically in a test case, eg.
+ * java.util.logging.Logger logger = Logger.getLogger("org.eclipse.paho.client.mqttv3");
+ * logger.addHandler(Utility.getHandler());
+ * logger.setLevel(Level.ALL);
+ */
+ private static java.util.logging.Handler handler = null;
+
+ public synchronized static java.util.logging.Handler getHandler() {
+ try {
+ if (handler == null) {
+ handler = new FileHandler("framework.log", true);
+ handler.setFormatter(new org.eclipse.paho.client.mqttv3.test.logging.HumanFormatter());
+ }
+ }
+ catch (IOException exception) {
+ exception.printStackTrace();
+ }
+ return handler;
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/resources/clientkeystore.jks b/org.eclipse.paho.client.mqttv3.test/src/test/resources/clientkeystore.jks
new file mode 100644
index 00000000..746cea91
Binary files /dev/null and b/org.eclipse.paho.client.mqttv3.test/src/test/resources/clientkeystore.jks differ
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/resources/logging.properties b/org.eclipse.paho.client.mqttv3.test/src/test/resources/logging.properties
new file mode 100644
index 00000000..1d66cc3a
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/resources/logging.properties
@@ -0,0 +1,34 @@
+
+# ***********************************************************************
+# * Configure Logging
+# ***********************************************************************
+
+# The set of handlers to be loaded upon startup.
+handlers=java.util.logging.FileHandler, org.eclipse.paho.client.mqttv3.test.logging.ConsoleHandler
+
+# Default global logging level.
+.level=INFO
+
+# Loggers
+# ------------------------------------------
+org.eclipse.paho.client.mqttv3.level=ALL
+org.eclipse.paho.client.mqttv3.test.level=ALL
+com.ibm.level=ALL
+utility.level=ALL
+
+# Handlers
+# -----------------------------------------
+
+# --- ConsoleHandler ---
+org.eclipse.paho.client.mqttv3.test.logging.ConsoleHandler.level=INFO
+org.eclipse.paho.client.mqttv3.test.logging.ConsoleHandler.formatter=org.eclipse.paho.client.mqttv3.test.logging.HumanFormatter
+
+# --- FileHandler ---
+java.util.logging.FileHandler.level=ALL
+java.util.logging.FileHandler.pattern=framework.log
+java.util.logging.FileHandler.formatter=org.eclipse.paho.client.mqttv3.test.logging.DetailFormatter
+java.util.logging.FileHandler.append=false
+
+
+
+
diff --git a/org.eclipse.paho.client.mqttv3.test/src/test/resources/test.properties b/org.eclipse.paho.client.mqttv3.test/src/test/resources/test.properties
new file mode 100644
index 00000000..7bddca1d
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3.test/src/test/resources/test.properties
@@ -0,0 +1,13 @@
+# This is the server URI which will be set in the constructor of an MQTT Client
+# The default is "tcp://:1883" with expressed in IPV4 dotted decimal notation
+SERVER_URI=tcp://iot.eclipse.org:1883
+CLIENT_KEY_STORE=clientkeystore.jks
+CLIENT_KEY_STORE_PASSWORD=password
+CLIENT_TRUST_STORE=clientkeystore.jks
+SERVER_SSL_PORT=18884
+
+# The list of server URIs which may be set in the MQTT ConnectOptions for an HA testcase.
+# There is no default value
+# URI.0=tcp://localhost:1883
+# URI.1=tcp://localhost:1884
+# URI.2=tcp://localhost:1885
diff --git a/org.eclipse.paho.client.mqttv3/.classpath b/org.eclipse.paho.client.mqttv3/.classpath
index bceaa2a1..a62f7d1d 100644
--- a/org.eclipse.paho.client.mqttv3/.classpath
+++ b/org.eclipse.paho.client.mqttv3/.classpath
@@ -1,6 +1,9 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/org.eclipse.paho.client.mqttv3/.externalToolBuilders/com.ibm.pvc.tools.bde.eclipse.CSBuilder.launch b/org.eclipse.paho.client.mqttv3/.externalToolBuilders/com.ibm.pvc.tools.bde.eclipse.CSBuilder.launch
deleted file mode 100644
index 0565b109..00000000
--- a/org.eclipse.paho.client.mqttv3/.externalToolBuilders/com.ibm.pvc.tools.bde.eclipse.CSBuilder.launch
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/org.eclipse.paho.client.mqttv3/.project b/org.eclipse.paho.client.mqttv3/.project
index 1210d3fe..b560d11b 100644
--- a/org.eclipse.paho.client.mqttv3/.project
+++ b/org.eclipse.paho.client.mqttv3/.project
@@ -1,39 +1,34 @@
-
-
- org.eclipse.paho.client.mqttv3
-
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- org.eclipse.pde.ManifestBuilder
-
-
-
-
- org.eclipse.pde.SchemaBuilder
-
-
-
-
- org.eclipse.ui.externaltools.ExternalToolBuilder
- full,incremental,
-
-
- LaunchConfigHandle
- <project>/.externalToolBuilders/com.ibm.pvc.tools.bde.eclipse.CSBuilder.launch
-
-
-
-
-
- com.ibm.pvc.tools.bde.ExtensionServicesNature
- org.eclipse.pde.PluginNature
- org.eclipse.jdt.core.javanature
-
-
+
+
+ org.eclipse.paho.client.mqttv3
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.pde.ManifestBuilder
+
+
+
+
+ org.eclipse.pde.SchemaBuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.m2e.core.maven2Nature
+ org.eclipse.jdt.core.javanature
+ org.eclipse.pde.PluginNature
+
+
diff --git a/org.eclipse.paho.client.mqttv3/.settings/com.ibm.pvc.tools.bde.prefs b/org.eclipse.paho.client.mqttv3/.settings/com.ibm.pvc.tools.bde.prefs
deleted file mode 100644
index 0640d38d..00000000
--- a/org.eclipse.paho.client.mqttv3/.settings/com.ibm.pvc.tools.bde.prefs
+++ /dev/null
@@ -1,7 +0,0 @@
-#Wed Feb 04 17:08:18 GMT 2009
-ManagePackageDependencies=true
-cs_project_version=6.2.0
-eclipse.preferences.version=1
-target_id=com.ibm.rcp.tools.bde.target.default
-selected_target_plugins=
-SearchPackageDependencies=true
diff --git a/org.eclipse.paho.client.mqttv3/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.paho.client.mqttv3/.settings/org.eclipse.jdt.core.prefs
index 039d65e1..008a2612 100644
--- a/org.eclipse.paho.client.mqttv3/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.paho.client.mqttv3/.settings/org.eclipse.jdt.core.prefs
@@ -1,7 +1,12 @@
-#Mon Jan 04 13:18:07 GMT 2010
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=warning
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2
eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.4
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=warning
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.3
-org.eclipse.jdt.core.compiler.compliance=1.4
diff --git a/org.eclipse.paho.client.mqttv3/.settings/org.eclipse.pde.core.prefs b/org.eclipse.paho.client.mqttv3/.settings/org.eclipse.pde.core.prefs
index 8187059c..bfdd232b 100644
--- a/org.eclipse.paho.client.mqttv3/.settings/org.eclipse.pde.core.prefs
+++ b/org.eclipse.paho.client.mqttv3/.settings/org.eclipse.pde.core.prefs
@@ -1,3 +1,3 @@
-#Wed Feb 04 17:08:18 GMT 2009
-eclipse.preferences.version=1
-resolve.requirebundle=false
+#Wed Feb 04 17:08:18 GMT 2009
+eclipse.preferences.version=1
+resolve.requirebundle=false
diff --git a/org.eclipse.paho.client.mqttv3/META-INF/MANIFEST.MF b/org.eclipse.paho.client.mqttv3/META-INF/MANIFEST.MF
index f65130eb..ea4c43ae 100644
--- a/org.eclipse.paho.client.mqttv3/META-INF/MANIFEST.MF
+++ b/org.eclipse.paho.client.mqttv3/META-INF/MANIFEST.MF
@@ -1,11 +1,15 @@
-Manifest-Version: 1.0
-Bundle-Name: MQTTv3_Client Plug-in
-Bundle-SymbolicName: org.eclipse.paho.client.mqttv3;singleton:=true
-Bundle-Version: 1.0.0
-Bundle-ManifestVersion: 2
-Import-Package: javax.net;resolution:=optional,
- javax.net.ssl;resolution:=optional
-Bundle-Classpath: ., bin
-Export-Package: org.eclipse.paho.client.mqttv3
-
-
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %bundle.name
+Bundle-SymbolicName: org.eclipse.paho.client.mqttv3
+Bundle-Version: 1.0.2
+Bundle-Localization: bundle
+Export-Package: org.eclipse.paho.client.mqttv3;version="1.0.2",
+ org.eclipse.paho.client.mqttv3.logging;version="1.0.2",
+ org.eclipse.paho.client.mqttv3.persist;version="1.0.2",
+ org.eclipse.paho.client.mqttv3.util;version="1.0.2"
+Bundle-Vendor: %bundle.provider
+Bundle-ActivationPolicy: lazy
+Bundle-RequiredExecutionEnvironment: J2SE-1.4
+Import-Package: javax.net;resolution:=optional,
+ javax.net.ssl;resolution:=optional
diff --git a/org.eclipse.paho.client.mqttv3/build.properties b/org.eclipse.paho.client.mqttv3/build.properties
new file mode 100644
index 00000000..08f6443c
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/build.properties
@@ -0,0 +1,5 @@
+source.. = src/main/resources/,\
+ src/main/java-templates/,\
+ src/main/java/
+bin.includes = META-INF/,\
+ .
diff --git a/org.eclipse.paho.client.mqttv3/build.xml b/org.eclipse.paho.client.mqttv3/build.xml
index 5ca93273..3c5dd5f0 100644
--- a/org.eclipse.paho.client.mqttv3/build.xml
+++ b/org.eclipse.paho.client.mqttv3/build.xml
@@ -1,81 +1,132 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ Builds a redistributable JAR and documentation for the Paho Java client.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Compiling client library...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Generating documentation...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Creating JAR file...
+
+
+
+
+
+
+
+
+ Cleaning project...
+
+
+
+
+
+
+
diff --git a/org.eclipse.paho.client.mqttv3/pom.xml b/org.eclipse.paho.client.mqttv3/pom.xml
new file mode 100644
index 00000000..655f8800
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/pom.xml
@@ -0,0 +1,123 @@
+
+
+ 4.0.0
+
+ org.eclipse.paho
+ java-parent
+ 1.0.2
+
+
+ org.eclipse.paho.client.mqttv3
+ eclipse-plugin
+
+
+
+
+ org.eclipse.tycho
+ tycho-maven-plugin
+
+
+ org.codehaus.mojo
+ templating-maven-plugin
+ 1.0-alpha-3
+
+
+ filter-src
+
+ filter-sources
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 2.4.2
+
+
+ default-copy-resources
+ process-resources
+
+ copy-resources
+
+
+ true
+ ${project.build.outputDirectory}
+
+
+
+ ../
+
+ about.html
+
+
+
+
+
+
+
+
+ org.eclipse.tycho
+ tycho-compiler-plugin
+ 0.20.0
+
+ ${mqttclient.java.version}
+ ${mqttclient.java.version}
+
+
+
+ org.eclipse.tycho
+ tycho-source-plugin
+ 0.20.0
+
+
+
+
+
+
+
+ org.eclipse.m2e
+ lifecycle-mapping
+ 1.0.0
+
+
+
+
+
+
+ org.codehaus.mojo
+
+
+ templating-maven-plugin
+
+
+ [1.0-alpha-3,)
+
+
+ filter-sources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java-templates/org/eclipse/paho/client/mqttv3/internal/ClientComms.java b/org.eclipse.paho.client.mqttv3/src/main/java-templates/org/eclipse/paho/client/mqttv3/internal/ClientComms.java
new file mode 100644
index 00000000..a957ea7e
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java-templates/org/eclipse/paho/client/mqttv3/internal/ClientComms.java
@@ -0,0 +1,736 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal;
+
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.Vector;
+
+import org.eclipse.paho.client.mqttv3.IMqttActionListener;
+import org.eclipse.paho.client.mqttv3.IMqttAsyncClient;
+import org.eclipse.paho.client.mqttv3.IMqttPingActionListener;
+import org.eclipse.paho.client.mqttv3.MqttCallback;
+import org.eclipse.paho.client.mqttv3.MqttClientPersistence;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
+import org.eclipse.paho.client.mqttv3.MqttPingSender;
+import org.eclipse.paho.client.mqttv3.MqttToken;
+import org.eclipse.paho.client.mqttv3.MqttTopic;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttConnack;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttConnect;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttDisconnect;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttPublish;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttWireMessage;
+import org.eclipse.paho.client.mqttv3.logging.Logger;
+import org.eclipse.paho.client.mqttv3.logging.LoggerFactory;
+
+/**
+ * Handles client communications with the server. Sends and receives MQTT V3
+ * messages.
+ */
+public class ClientComms {
+ public static String VERSION = "${project.version}";
+ public static String BUILD_LEVEL = "L${build.level}";
+ private static final String CLASS_NAME = ClientComms.class.getName();
+ private static final Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT,CLASS_NAME);
+
+ private static final byte CONNECTED = 0;
+ private static final byte CONNECTING = 1;
+ private static final byte DISCONNECTING = 2;
+ private static final byte DISCONNECTED = 3;
+ private static final byte CLOSED = 4;
+
+ private IMqttAsyncClient client;
+ private int networkModuleIndex;
+ private NetworkModule[] networkModules;
+ private CommsReceiver receiver;
+ private CommsSender sender;
+ private CommsCallback callback;
+ private ClientState clientState;
+ private MqttConnectOptions conOptions;
+ private MqttClientPersistence persistence;
+ private MqttPingSender pingSender;
+ private CommsTokenStore tokenStore;
+ private boolean stoppingComms = false;
+
+ private byte conState = DISCONNECTED;
+ private Object conLock = new Object(); // Used to synchronize connection state
+ private boolean closePending = false;
+
+ /**
+ * Creates a new ClientComms object, using the specified module to handle
+ * the network calls.
+ */
+ public ClientComms(IMqttAsyncClient client, MqttClientPersistence persistence, MqttPingSender pingSender) throws MqttException {
+ this.conState = DISCONNECTED;
+ this.client = client;
+ this.persistence = persistence;
+ this.pingSender = pingSender;
+ this.pingSender.init(this);
+
+ this.tokenStore = new CommsTokenStore(getClient().getClientId());
+ this.callback = new CommsCallback(this);
+ this.clientState = new ClientState(persistence, tokenStore, this.callback, this, pingSender);
+
+ callback.setClientState(clientState);
+ log.setResourceName(getClient().getClientId());
+ }
+
+ CommsReceiver getReceiver() {
+ return receiver;
+ }
+
+ /**
+ * Sends a message to the server. Does not check if connected this validation must be done
+ * by invoking routines.
+ * @param message
+ * @param token
+ * @throws MqttException
+ */
+ void internalSend(MqttWireMessage message, MqttToken token) throws MqttException {
+ final String methodName = "internalSend";
+ //@TRACE 200=internalSend key={0} message={1} token={2}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "200", new Object[]{message.getKey(), message, token});
+ }
+
+ if (token.getClient() == null ) {
+ // Associate the client with the token - also marks it as in use.
+ token.internalTok.setClient(getClient());
+ } else {
+ // Token is already in use - cannot reuse
+ //@TRACE 213=fail: token in use: key={0} message={1} token={2}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "213", new Object[]{message.getKey(), message, token});
+ }
+
+ throw new MqttException(MqttException.REASON_CODE_TOKEN_INUSE);
+ }
+
+ try {
+ // Persist if needed and send the message
+ this.clientState.send(message, token);
+ } catch(MqttException e) {
+ if (message instanceof MqttPublish) {
+ this.clientState.undo((MqttPublish)message);
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * Sends a message to the broker if in connected state, but only waits for the message to be
+ * stored, before returning.
+ */
+ public void sendNoWait(MqttWireMessage message, MqttToken token) throws MqttException {
+ final String methodName = "sendNoWait";
+ if (isConnected() ||
+ (!isConnected() && message instanceof MqttConnect) ||
+ (isDisconnecting() && message instanceof MqttDisconnect)) {
+ this.internalSend(message, token);
+ } else {
+ //@TRACE 208=failed: not connected
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "208");
+ }
+ throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_CLIENT_NOT_CONNECTED);
+ }
+ }
+
+ /**
+ * Close and tidy up.
+ *
+ * Call each main class and let it tidy up e.g. releasing the token
+ * store which normally survives a disconnect.
+ * @throws MqttException if not disconnected
+ */
+ public void close() throws MqttException {
+ final String methodName = "close";
+ synchronized (conLock) {
+ if (!isClosed()) {
+ // Must be disconnected before close can take place
+ if (!isDisconnected()) {
+ //@TRACE 224=failed: not disconnected
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "224");
+ }
+
+ if (isConnecting()) {
+ throw new MqttException(MqttException.REASON_CODE_CONNECT_IN_PROGRESS);
+ } else if (isConnected()) {
+ throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_CLIENT_CONNECTED);
+ } else if (isDisconnecting()) {
+ closePending = true;
+ return;
+ }
+ }
+
+ conState = CLOSED;
+
+ // ShutdownConnection has already cleaned most things
+ clientState.close();
+ clientState = null;
+ callback = null;
+ persistence = null;
+ sender = null;
+ pingSender = null;
+ receiver = null;
+ networkModules = null;
+ conOptions = null;
+ tokenStore = null;
+ }
+ }
+ }
+
+ /**
+ * Sends a connect message and waits for an ACK or NACK.
+ * Connecting is a special case which will also start up the
+ * network connection, receive thread, and keep alive thread.
+ */
+ public void connect(MqttConnectOptions options, MqttToken token) throws MqttException {
+ final String methodName = "connect";
+ synchronized (conLock) {
+ if (isDisconnected() && !closePending) {
+ //@TRACE 214=state=CONNECTING
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "214");
+ }
+
+ conState = CONNECTING;
+
+ this.conOptions = options;
+
+ MqttConnect connect = new MqttConnect(client.getClientId(),
+ options.getMqttVersion(),
+ options.isCleanSession(),
+ options.getKeepAliveInterval(),
+ options.getUserName(),
+ options.getPassword(),
+ options.getWillMessage(),
+ options.getWillDestination());
+
+ this.clientState.setKeepAliveSecs(options.getKeepAliveInterval());
+ this.clientState.setCleanSession(options.isCleanSession());
+
+ tokenStore.open();
+ ConnectBG conbg = new ConnectBG(this, token, connect);
+ conbg.start();
+ }
+ else {
+ // @TRACE 207=connect failed: not disconnected {0}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "207", new Object[]{new Byte(conState)});
+ }
+ if (isClosed() || closePending) {
+ throw new MqttException(MqttException.REASON_CODE_CLIENT_CLOSED);
+ } else if (isConnecting()) {
+ throw new MqttException(MqttException.REASON_CODE_CONNECT_IN_PROGRESS);
+ } else if (isDisconnecting()) {
+ throw new MqttException(MqttException.REASON_CODE_CLIENT_DISCONNECTING);
+ } else {
+ throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_CLIENT_CONNECTED);
+ }
+ }
+ }
+ }
+
+ public void connectComplete( MqttConnack cack, MqttException mex) throws MqttException {
+ final String methodName = "connectComplete";
+ int rc = cack.getReturnCode();
+ synchronized (conLock) {
+ if (rc == 0) {
+ // We've successfully connected
+ // @TRACE 215=state=CONNECTED
+ log.fine(CLASS_NAME,methodName,"215");
+
+ conState = CONNECTED;
+ return;
+ }
+ }
+
+ // @TRACE 204=connect failed: rc={0}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "204", new Object[]{new Integer(rc)});
+ }
+ throw mex;
+ }
+
+ /**
+ * Shuts down the connection to the server.
+ * This may have been invoked as a result of a user calling disconnect or
+ * an abnormal disconnection. The method may be invoked multiple times
+ * in parallel as each thread when it receives an error uses this method
+ * to ensure that shutdown completes successfully.
+ */
+ public void shutdownConnection(MqttToken token, MqttException reason) {
+ final String methodName = "shutdownConnection";
+ boolean wasConnected;
+ MqttToken endToken = null; //Token to notify after disconnect completes
+
+ // This method could concurrently be invoked from many places only allow it
+ // to run once.
+ synchronized(conLock) {
+ if (stoppingComms || closePending) {
+ return;
+ }
+ stoppingComms = true;
+
+ //@TRACE 216=state=DISCONNECTING
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "216");
+ }
+
+ wasConnected = (isConnected() || isDisconnecting());
+ conState = DISCONNECTING;
+ }
+
+ // Update the token with the reason for shutdown if it
+ // is not already complete.
+ if (token != null && !token.isComplete()) {
+ token.internalTok.setException(reason);
+ }
+
+ // Stop the thread that is used to call the user back
+ // when actions complete
+ if (callback!= null) {callback.stop(); }
+
+ // Stop the network module, send and receive now not possible
+ try {
+ if (networkModules != null) {
+ NetworkModule networkModule = networkModules[networkModuleIndex];
+ if (networkModule != null) {
+ networkModule.stop();
+ }
+ }
+ } catch (Exception ioe) {
+ // Ignore as we are shutting down
+ }
+
+ // Stop the thread that handles inbound work from the network
+ if (receiver != null) {receiver.stop();}
+
+ // Stop any new tokens being saved by app and throwing an exception if they do
+ tokenStore.quiesce(new MqttException(MqttException.REASON_CODE_CLIENT_DISCONNECTING));
+
+ // Notify any outstanding tokens with the exception of
+ // con or discon which may be returned and will be notified at
+ // the end
+ endToken = handleOldTokens(token, reason);
+
+ try {
+ // Clean session handling and tidy up
+ clientState.disconnected(reason);
+ }catch(Exception ex) {
+ // Ignore as we are shutting down
+ }
+
+ if (sender != null) { sender.stop(); }
+
+ if (pingSender != null){
+ pingSender.stop();
+ }
+
+ try {
+ if (persistence != null) {persistence.close();}
+ }catch(Exception ex) {
+ // Ignore as we are shutting down
+ }
+ // All disconnect logic has been completed allowing the
+ // client to be marked as disconnected.
+ synchronized(conLock) {
+ //@TRACE 217=state=DISCONNECTED
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "217");
+ }
+
+ conState = DISCONNECTED;
+ stoppingComms = false;
+ }
+
+ // Internal disconnect processing has completed. If there
+ // is a disconnect token or a connect in error notify
+ // it now. This is done at the end to allow a new connect
+ // to be processed and now throw a currently disconnecting error.
+ // any outstanding tokens and unblock any waiters
+ if (endToken != null & callback != null) {
+ callback.asyncOperationComplete(endToken);
+ }
+
+ if (wasConnected && callback != null) {
+ // Let the user know client has disconnected either normally or abnormally
+ callback.connectionLost(reason);
+ }
+
+ // While disconnecting, close may have been requested - try it now
+ synchronized(conLock) {
+ if (closePending) {
+ try {
+ close();
+ } catch (Exception e) { // ignore any errors as closing
+ }
+ }
+ }
+ }
+
+ // Tidy up. There may be tokens outstanding as the client was
+ // not disconnected/quiseced cleanly! Work out what tokens still
+ // need to be notified and waiters unblocked. Store the
+ // disconnect or connect token to notify after disconnect is
+ // complete.
+ private MqttToken handleOldTokens(MqttToken token, MqttException reason) {
+ final String methodName = "handleOldTokens";
+ //@TRACE 222=>
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "222");
+ }
+
+ MqttToken tokToNotifyLater = null;
+ try {
+ // First the token that was related to the disconnect / shutdown may
+ // not be in the token table - temporarily add it if not
+ if (token != null) {
+ if (tokenStore.getToken(token.internalTok.getKey())==null) {
+ tokenStore.saveToken(token, token.internalTok.getKey());
+ }
+ }
+
+ Vector toksToNot = clientState.resolveOldTokens(reason);
+ Enumeration toksToNotE = toksToNot.elements();
+ while(toksToNotE.hasMoreElements()) {
+ MqttToken tok = (MqttToken)toksToNotE.nextElement();
+
+ if (tok.internalTok.getKey().equals(MqttDisconnect.KEY) ||
+ tok.internalTok.getKey().equals(MqttConnect.KEY)) {
+ // Its con or discon so remember and notify @ end of disc routine
+ tokToNotifyLater = tok;
+ } else {
+ // notify waiters and callbacks of outstanding tokens
+ // that a problem has occurred and disconnect is in
+ // progress
+ callback.asyncOperationComplete(tok);
+ }
+ }
+ }catch(Exception ex) {
+ // Ignore as we are shutting down
+ }
+ return tokToNotifyLater;
+ }
+
+ public void disconnect(MqttDisconnect disconnect, long quiesceTimeout, MqttToken token) throws MqttException {
+ final String methodName = "disconnect";
+ synchronized (conLock){
+ if (isClosed()) {
+ //@TRACE 223=failed: in closed state
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "223");
+ }
+ throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_CLIENT_CLOSED);
+ } else if (isDisconnected()) {
+ //@TRACE 211=failed: already disconnected
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "211");
+ }
+ throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_CLIENT_ALREADY_DISCONNECTED);
+ } else if (isDisconnecting()) {
+ //@TRACE 219=failed: already disconnecting
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "219");
+ }
+ throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_CLIENT_DISCONNECTING);
+ } else if (Thread.currentThread() == callback.getThread()) {
+ //@TRACE 210=failed: called on callback thread
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "210");
+ }
+ // Not allowed to call disconnect() from the callback, as it will deadlock.
+ throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_CLIENT_DISCONNECT_PROHIBITED);
+ }
+
+ //@TRACE 218=state=DISCONNECTING
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "218");
+ }
+ conState = DISCONNECTING;
+ DisconnectBG discbg = new DisconnectBG(disconnect,quiesceTimeout,token);
+ discbg.start();
+ }
+ }
+
+ /**
+ * Disconnect the connection and reset all the states.
+ */
+ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout) throws MqttException {
+ // Allow current inbound and outbound work to complete
+ clientState.quiesce(quiesceTimeout);
+ MqttToken token = new MqttToken(client.getClientId());
+ try {
+ // Send disconnect packet
+ internalSend(new MqttDisconnect(), token);
+
+ // Wait util the disconnect packet sent with timeout
+ token.waitForCompletion(disconnectTimeout);
+ }
+ catch (Exception ex) {
+ // ignore, probably means we failed to send the disconnect packet.
+ }
+ finally {
+ token.internalTok.markComplete(null, null);
+ shutdownConnection(token, null);
+ }
+ }
+
+ public boolean isConnected() {
+ synchronized (conLock) {
+ return conState == CONNECTED;
+ }
+ }
+
+ public boolean isConnecting() {
+ synchronized (conLock) {
+ return conState == CONNECTING;
+ }
+ }
+
+ public boolean isDisconnected() {
+ synchronized (conLock) {
+ return conState == DISCONNECTED;
+ }
+ }
+
+ public boolean isDisconnecting() {
+ synchronized (conLock) {
+ return conState == DISCONNECTING;
+ }
+ }
+
+ public boolean isClosed() {
+ synchronized (conLock) {
+ return conState == CLOSED;
+ }
+ }
+
+
+ public void setCallback(MqttCallback mqttCallback) {
+ this.callback.setCallback(mqttCallback);
+ }
+
+ protected MqttTopic getTopic(String topic) {
+ return new MqttTopic(topic, this);
+ }
+ public void setNetworkModuleIndex(int index) {
+ this.networkModuleIndex = index;
+ }
+ public int getNetworkModuleIndex() {
+ return networkModuleIndex;
+ }
+ public NetworkModule[] getNetworkModules() {
+ return networkModules;
+ }
+ public void setNetworkModules(NetworkModule[] networkModules) {
+ this.networkModules = networkModules;
+ }
+ public MqttDeliveryToken[] getPendingDeliveryTokens() {
+ return tokenStore.getOutstandingDelTokens();
+ }
+ protected void deliveryComplete(MqttPublish msg) throws MqttPersistenceException {
+ this.clientState.deliveryComplete(msg);
+ }
+
+ public IMqttAsyncClient getClient() {
+ return client;
+ }
+
+ public long getKeepAlive() {
+ return this.clientState.getKeepAlive();
+ }
+
+ public ClientState getClientState() {
+ return clientState;
+ }
+
+ public MqttConnectOptions getConOptions() {
+ return conOptions;
+ }
+
+ public Properties getDebug() {
+ Properties props = new Properties();
+ props.put("conState", new Integer(conState));
+ props.put("serverURI", getClient().getServerURI());
+ props.put("callback", callback);
+ props.put("stoppingComms", new Boolean(stoppingComms));
+ return props;
+ }
+
+
+
+ // Kick off the connect processing in the background so that it does not block. For instance
+ // the socket could take time to create.
+ private class ConnectBG implements Runnable {
+ ClientComms clientComms = null;
+ Thread cBg = null;
+ MqttToken conToken;
+ MqttConnect conPacket;
+
+ ConnectBG(ClientComms cc, MqttToken cToken, MqttConnect cPacket) {
+ clientComms = cc;
+ conToken = cToken;
+ conPacket = cPacket;
+ cBg = new Thread(this, "MQTT Con: "+getClient().getClientId());
+ }
+
+ void start() {
+ cBg.start();
+ }
+
+ public void run() {
+ final String methodName = "connectBG:run";
+ MqttException mqttEx = null;
+ //@TRACE 220=>
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "220");
+ }
+
+ try {
+ // Reset an exception on existing delivery tokens.
+ // This will have been set if disconnect occured before delivery was
+ // fully processed.
+ MqttDeliveryToken[] toks = tokenStore.getOutstandingDelTokens();
+ for (int i=0; i
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "221");
+ }
+
+ // Allow current inbound and outbound work to complete
+ clientState.quiesce(quiesceTimeout);
+ try {
+ internalSend(disconnect, token);
+ token.internalTok.waitUntilSent();
+ }
+ catch (MqttException ex) {
+ }
+ finally {
+ token.internalTok.markComplete(null, null);
+ shutdownConnection(token, null);
+ }
+ }
+ }
+
+ /*
+ * Check and send a ping if needed and check for ping timeout.
+ * Need to send a ping if nothing has been sent or received
+ * in the last keepalive interval.
+ */
+ public MqttToken checkForActivity(){
+ MqttToken token = null;
+ try{
+ token = clientState.checkForActivity(null);
+ }catch(MqttException e){
+ handleRunException(e);
+ }catch(Exception e){
+ handleRunException(e);
+ }
+ return token;
+ }
+
+ public MqttToken checkForActivity(IMqttPingActionListener actionListener) {
+ MqttToken token = null;
+ try{
+ token = clientState.checkForActivity(actionListener);
+ }catch(MqttException e){
+ handleRunException(e);
+ }catch(Exception e){
+ handleRunException(e);
+ }
+ return token;
+ }
+
+ private void handleRunException(Exception ex) {
+ final String methodName = "handleRunException";
+ //@TRACE 804=exception
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "804", null, ex);
+ }
+ MqttException mex;
+ if ( !(ex instanceof MqttException)) {
+ mex = new MqttException(MqttException.REASON_CODE_CONNECTION_LOST, ex);
+ } else {
+ mex = (MqttException)ex;
+ }
+
+ shutdownConnection(null, mex);
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttActionListener.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttActionListener.java
new file mode 100644
index 00000000..176636bf
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttActionListener.java
@@ -0,0 +1,29 @@
+package org.eclipse.paho.client.mqttv3;
+
+/**
+ * Implementors of this interface will be notified when an asynchronous action completes.
+ *
+ *
A listener is registered on an MqttToken and a token is associated
+ * with an action like connect or publish. When used with tokens on the MqttAsyncClient
+ * the listener will be called back on the MQTT client's thread. The listener will be informed
+ * if the action succeeds or fails. It is important that the listener returns control quickly
+ * otherwise the operation of the MQTT client will be stalled.
+ *
+ */
+public interface IMqttActionListener {
+ /**
+ * This method is invoked when an action has completed successfully.
+ * @param asyncActionToken associated with the action that has completed
+ */
+ public void onSuccess(IMqttToken asyncActionToken );
+ /**
+ * This method is invoked when an action fails.
+ * If a client is disconnected while an action is in progress
+ * onFailure will be called. For connections
+ * that use cleanSession set to false, any QoS 1 and 2 messages that
+ * are in the process of being delivered will be delivered to the requested
+ * quality of service next time the client connects.
+ * @param asyncActionToken associated with the action that has failed
+ */
+ public void onFailure(IMqttToken asyncActionToken, Throwable exception);
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttAsyncClient.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttAsyncClient.java
new file mode 100644
index 00000000..90ff8f39
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttAsyncClient.java
@@ -0,0 +1,767 @@
+/*******************************************************************************
+ * Copyright (c) 2013, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ * Ian Craggs - MQTT 3.1.1 support
+ */
+
+package org.eclipse.paho.client.mqttv3;
+
+/**
+ * Enables an application to communicate with an MQTT server using non-blocking methods.
+ *
+ * It provides applications a simple programming interface to all features of the MQTT version 3.1
+ * specification including:
+ *
+ *
connect
+ *
publish
+ *
subscribe
+ *
unsubscribe
+ *
disconnect
+ *
+ *
+ *
+ * There are two styles of MQTT client, this one and {@link IMqttClient}.
+ *
+ *
IMqttAsyncClient provides a set of non-blocking methods that return control to the
+ * invoking application after initial validation of parameters and state. The main processing is
+ * performed in the background so as not to block the application program's thread. This non-
+ * blocking approach is handy when the application needs to carry on processing while the
+ * MQTT action takes place. For instance connecting to an MQTT server can take time, using
+ * the non-blocking connect method allows an application to display a busy indicator while the
+ * connect action takes place in the background. Non blocking methods are particularly useful
+ * in event oriented programs and graphical programs where invoking methods that take time
+ * to complete on the the main or GUI thread can cause problems. The non-blocking interface
+ * can also be used in blocking form.
+ *
IMqttClient provides a set of methods that block and return control to the application
+ * program once the MQTT action has completed. It is a thin layer that sits on top of the
+ * IMqttAsyncClient implementation and is provided mainly for compatibility with earlier
+ * versions of the MQTT client. In most circumstances it is recommended to use IMqttAsyncClient
+ * based clients which allow an application to mix both non-blocking and blocking calls.
+ *
+ *
+ *
+ * An application is not restricted to using one style if an IMqttAsyncClient based client is used
+ * as both blocking and non-blocking methods can be used in the same application. If an IMqttClient
+ * based client is used then only blocking methods are available to the application.
+ * For more details on the blocking client see {@link IMqttClient}
In this form the method returns a token that can be used to track the
+ * progress of the action (method). The method provides a waitForCompletion()
+ * method that once invoked will block until the action completes. Once
+ * completed there are method on the token that can be used to check if the
+ * action completed successfully or not. For example
+ * to wait until a connect completes:
+ *
+ * IMqttToken conToken;
+ * conToken = asyncClient.client.connect(conToken);
+ * ... do some work...
+ * conToken.waitForCompletion();
+ *
+ *
+ *
To turn a method into a blocking invocation the following form can be used:
+ *
In this form a callback is registered with the method. The callback will be
+ * notified when the action succeeds or fails. The callback is invoked on the thread
+ * managed by the MQTT client so it is important that processing is minimised in the
+ * callback. If not the operation of the MQTT client will be inhibited. For example
+ * to be notified (called back) when a connect completes:
+ *
+ * An optional context object can be passed into the method which will then be made
+ * available in the callback. The context is stored by the MQTT client) in the token
+ * which is then returned to the invoker. The token is provided to the callback methods
+ * where the context can then be accessed.
+ *
+ *
+ *
+ *
To understand when the delivery of a message is complete either of the two methods above
+ * can be used to either wait on or be notified when the publish completes. An alternative is to
+ * use the {@link MqttCallback#deliveryComplete(IMqttDeliveryToken)} method which will
+ * also be notified when a message has been delivered to the requested quality of service.
+ *
+ */
+public interface IMqttAsyncClient {
+ /**
+ * Connects to an MQTT server using the default options.
+ *
The default options are specified in {@link MqttConnectOptions} class.
+ *
+ *
+ * @throws MqttSecurityException for security related problems
+ * @throws MqttException for non security related problems
+ * @return token used to track and wait for the connect to complete. The token
+ * will be passed to the callback methods if a callback is set.
+ * @see #connect(MqttConnectOptions, Object, IMqttActionListener)
+ */
+ public IMqttToken connect() throws MqttException, MqttSecurityException;
+
+ /**
+ * Connects to an MQTT server using the provided connect options.
+ *
The connection will be established using the options specified in the
+ * {@link MqttConnectOptions} parameter.
+ *
+ *
+ * @param options a set of connection parameters that override the defaults.
+ * @throws MqttSecurityException for security related problems
+ * @throws MqttException for non security related problems
+ * @return token used to track and wait for the connect to complete. The token
+ * will be passed to any callback that has been set.
+ * @see #connect(MqttConnectOptions, Object, IMqttActionListener)
+ */
+ public IMqttToken connect(MqttConnectOptions options) throws MqttException, MqttSecurityException ;
+ /**
+ * Connects to an MQTT server using the default options.
+ *
The default options are specified in {@link MqttConnectOptions} class.
+ *
+ *
+ * @param userContext optional object used to pass context to the callback. Use
+ * null if not required.
+ * @param callback optional listener that will be notified when the connect completes. Use
+ * null if not required.
+ * @throws MqttSecurityException for security related problems
+ * @throws MqttException for non security related problems
+ * @return token used to track and wait for the connect to complete. The token
+ * will be passed to any callback that has been set.
+ * @see #connect(MqttConnectOptions, Object, IMqttActionListener)
+ */
+ public IMqttToken connect(Object userContext, IMqttActionListener callback) throws MqttException, MqttSecurityException;
+
+
+ /**
+ * Connects to an MQTT server using the specified options.
+ *
The server to connect to is specified on the constructor.
+ * It is recommended to call {@link #setCallback(MqttCallback)} prior to
+ * connecting in order that messages destined for the client can be accepted
+ * as soon as the client is connected.
+ *
+ *
The method returns control before the connect completes. Completion can
+ * be tracked by:
+ *
+ *
Waiting on the returned token {@link IMqttToken#waitForCompletion()} or
+ *
Passing in a callback {@link IMqttActionListener}
+ *
+ *
+ *
+ * @param options a set of connection parameters that override the defaults.
+ * @param userContext optional object for used to pass context to the callback. Use
+ * null if not required.
+ * @param callback optional listener that will be notified when the connect completes. Use
+ * null if not required.
+ * @return token used to track and wait for the connect to complete. The token
+ * will be passed to any callback that has been set.
+ * @throws MqttSecurityException for security related problems
+ * @throws MqttException for non security related problems including communication errors
+ */
+ public IMqttToken connect(MqttConnectOptions options, Object userContext, IMqttActionListener callback) throws MqttException, MqttSecurityException;
+
+ /**
+ * Disconnects from the server.
+ *
An attempt is made to quiesce the client allowing outstanding
+ * work to complete before disconnecting. It will wait
+ * for a maximum of 30 seconds for work to quiesce before disconnecting.
+ * This method must not be called from inside {@link MqttCallback} methods.
+ *
+ *
+ * @return token used to track and wait for disconnect to complete. The token
+ * will be passed to any callback that has been set.
+ * @throws MqttException for problems encountered while disconnecting
+ * @see #disconnect(long, Object, IMqttActionListener)
+ */
+ public IMqttToken disconnect( ) throws MqttException;
+
+ /**
+ * Disconnects from the server.
+ *
An attempt is made to quiesce the client allowing outstanding
+ * work to complete before disconnecting. It will wait
+ * for a maximum of the specified quiesce time for work to complete before disconnecting.
+ * This method must not be called from inside {@link MqttCallback} methods.
+ *
+ * @param quiesceTimeout the amount of time in milliseconds to allow for
+ * existing work to finish before disconnecting. A value of zero or less
+ * means the client will not quiesce.
+ * @return token used to track and wait for disconnect to complete. The token
+ * will be passed to the callback methods if a callback is set.
+ * @throws MqttException for problems encountered while disconnecting
+ * @see #disconnect(long, Object, IMqttActionListener)
+ */
+ public IMqttToken disconnect(long quiesceTimeout) throws MqttException;
+
+ /**
+ * Disconnects from the server.
+ *
An attempt is made to quiesce the client allowing outstanding
+ * work to complete before disconnecting. It will wait
+ * for a maximum of 30 seconds for work to quiesce before disconnecting.
+ * This method must not be called from inside {@link MqttCallback} methods.
+ *
+ *
+ * @param userContext optional object used to pass context to the callback. Use
+ * null if not required.
+ * @param callback optional listener that will be notified when the disconnect completes. Use
+ * null if not required.
+ * @return token used to track and wait for the disconnect to complete. The token
+ * will be passed to any callback that has been set.
+ * @throws MqttException for problems encountered while disconnecting
+ * @see #disconnect(long, Object, IMqttActionListener)
+ */
+ public IMqttToken disconnect( Object userContext, IMqttActionListener callback) throws MqttException;
+
+ /**
+ * Disconnects from the server.
+ *
+ * The client will wait for {@link MqttCallback} methods to
+ * complete. It will then wait for up to the quiesce timeout to allow for
+ * work which has already been initiated to complete. For instance when a QoS 2
+ * message has started flowing to the server but the QoS 2 flow has not completed.It
+ * prevents new messages being accepted and does not send any messages that have
+ * been accepted but not yet started delivery across the network to the server. When
+ * work has completed or after the quiesce timeout, the client will disconnect from
+ * the server. If the cleanSession flag was set to false and is set to false the
+ * next time a connection is made QoS 1 and 2 messages that
+ * were not previously delivered will be delivered.
+ *
This method must not be called from inside {@link MqttCallback} methods.
+ *
The method returns control before the disconnect completes. Completion can
+ * be tracked by:
+ *
+ *
Waiting on the returned token {@link IMqttToken#waitForCompletion()} or
+ *
Passing in a callback {@link IMqttActionListener}
+ *
+ *
+ *
+ * @param quiesceTimeout the amount of time in milliseconds to allow for
+ * existing work to finish before disconnecting. A value of zero or less
+ * means the client will not quiesce.
+ * @param userContext optional object used to pass context to the callback. Use
+ * null if not required.
+ * @param callback optional listener that will be notified when the disconnect completes. Use
+ * null if not required.
+ * @return token used to track and wait for the connect to complete. The token
+ * will be passed to any callback that has been set.
+ * @throws MqttException for problems encountered while disconnecting
+ */
+ public IMqttToken disconnect(long quiesceTimeout, Object userContext, IMqttActionListener callback) throws MqttException;
+
+ /**
+ * Disconnects from the server forcibly to reset all the states. Could be useful when disconnect attempt failed.
+ *
+ * Because the client is able to establish the TCP/IP connection to a none MQTT server and it will certainly fail to
+ * send the disconnect packet. It will wait for a maximum of 30 seconds for work to quiesce before disconnecting and
+ * wait for a maximum of 10 seconds for sending the disconnect packet to server.
+ *
+ * @throws MqttException if any unexpected error
+ * @since 0.4.1
+ */
+ public void disconnectForcibly() throws MqttException;
+
+ /**
+ * Disconnects from the server forcibly to reset all the states. Could be useful when disconnect attempt failed.
+ *
+ * Because the client is able to establish the TCP/IP connection to a none MQTT server and it will certainly fail to
+ * send the disconnect packet. It will wait for a maximum of 30 seconds for work to quiesce before disconnecting.
+ *
+ * @param disconnectTimeout the amount of time in milliseconds to allow send disconnect packet to server.
+ * @throws MqttException if any unexpected error
+ * @since 0.4.1
+ */
+ public void disconnectForcibly(long disconnectTimeout) throws MqttException;
+
+ /**
+ * Disconnects from the server forcibly to reset all the states. Could be useful when disconnect attempt failed.
+ *
+ * Because the client is able to establish the TCP/IP connection to a none MQTT server and it will certainly fail to
+ * send the disconnect packet.
+ *
+ * @param quiesceTimeout the amount of time in milliseconds to allow for existing work to finish before
+ * disconnecting. A value of zero or less means the client will not quiesce.
+ * @param disconnectTimeout the amount of time in milliseconds to allow send disconnect packet to server.
+ * @throws MqttException if any unexpected error
+ * @since 0.4.1
+ */
+ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout) throws MqttException;
+
+ /**
+ * Determines if this client is currently connected to the server.
+ *
+ * @return true if connected, false otherwise.
+ */
+ public boolean isConnected();
+
+ /**
+ * Returns the client ID used by this client.
+ *
All clients connected to the
+ * same server or server farm must have a unique ID.
+ *
+ *
+ * @return the client ID used by this client.
+ */
+ public String getClientId();
+
+ /**
+ * Returns the address of the server used by this client.
+ *
The format of the returned String is the same as that used on the constructor.
+ *
+ *
+ * @return the server's address, as a URI String.
+ * @see MqttAsyncClient#MqttAsyncClient(String, String)
+ */
+ public String getServerURI();
+
+ /**
+ * Publishes a message to a topic on the server.
+ *
A convenience method, which will
+ * create a new {@link MqttMessage} object with a byte array payload and the
+ * specified QoS, and then publish it.
+ *
+ *
+ * @param topic to deliver the message to, for example "finance/stock/ibm".
+ * @param payload the byte array to use as the payload
+ * @param qos the Quality of Service to deliver the message at. Valid values are 0, 1 or 2.
+ * @param retained whether or not this message should be retained by the server.
+ * @return token used to track and wait for the publish to complete. The token
+ * will be passed to any callback that has been set.
+ * @throws MqttPersistenceException when a problem occurs storing the message
+ * @throws IllegalArgumentException if value of QoS is not 0, 1 or 2.
+ * @throws MqttException for other errors encountered while publishing the message.
+ * For instance if too many messages are being processed.
+ * @see #publish(String, MqttMessage, Object, IMqttActionListener)
+ * @see MqttMessage#setQos(int)
+ * @see MqttMessage#setRetained(boolean)
+ */
+ public IMqttDeliveryToken publish(String topic, byte[] payload, int qos,
+ boolean retained ) throws MqttException, MqttPersistenceException;
+
+ /**
+ * Publishes a message to a topic on the server.
+ *
A convenience method, which will
+ * create a new {@link MqttMessage} object with a byte array payload and the
+ * specified QoS, and then publish it.
+ *
+ *
+ * @param topic to deliver the message to, for example "finance/stock/ibm".
+ * @param payload the byte array to use as the payload
+ * @param qos the Quality of Service to deliver the message at. Valid values are 0, 1 or 2.
+ * @param retained whether or not this message should be retained by the server.
+ * @param userContext optional object used to pass context to the callback. Use
+ * null if not required.
+ * @param callback optional listener that will be notified when message delivery
+ * hsa completed to the requested quality of service
+ * @return token used to track and wait for the publish to complete. The token
+ * will be passed to any callback that has been set.
+ * @throws MqttPersistenceException when a problem occurs storing the message
+ * @throws IllegalArgumentException if value of QoS is not 0, 1 or 2.
+ * @throws MqttException for other errors encountered while publishing the message.
+ * For instance client not connected.
+ * @see #publish(String, MqttMessage, Object, IMqttActionListener)
+ * @see MqttMessage#setQos(int)
+ * @see MqttMessage#setRetained(boolean)
+ */
+ public IMqttDeliveryToken publish(String topic, byte[] payload, int qos,
+ boolean retained, Object userContext, IMqttActionListener callback ) throws MqttException, MqttPersistenceException;
+
+ /**
+ * Publishes a message to a topic on the server.
+ * Takes an {@link MqttMessage} message and delivers it to the server at the
+ * requested quality of service.
+ *
+ * @param topic to deliver the message to, for example "finance/stock/ibm".
+ * @param message to deliver to the server
+ * @return token used to track and wait for the publish to complete. The token
+ * will be passed to any callback that has been set.
+ * @throws MqttPersistenceException when a problem occurs storing the message
+ * @throws IllegalArgumentException if value of QoS is not 0, 1 or 2.
+ * @throws MqttException for other errors encountered while publishing the message.
+ * For instance client not connected.
+ * @see #publish(String, MqttMessage, Object, IMqttActionListener)
+ */
+ public IMqttDeliveryToken publish(String topic, MqttMessage message ) throws MqttException, MqttPersistenceException;
+
+ /**
+ * Publishes a message to a topic on the server.
+ *
+ * Once this method has returned cleanly, the message has been accepted for publication by the
+ * client and will be delivered on a background thread.
+ * In the event the connection fails or the client stops. Messages will be delivered to the
+ * requested quality of service once the connection is re-established to the server on condition that:
+ *
+ *
The connection is re-established with the same clientID
+ *
The original connection was made with (@link MqttConnectOptions#setCleanSession(boolean)}
+ * set to false
+ *
The connection is re-established with (@link MqttConnectOptions#setCleanSession(boolean)}
+ * set to false
+ *
Depending when the failure occurs QoS 0 messages may not be delivered.
+ *
+ *
+ *
+ *
When building an application,
+ * the design of the topic tree should take into account the following principles
+ * of topic name syntax and semantics:
+ *
+ *
+ *
A topic must be at least one character long.
+ *
Topic names are case sensitive. For example, ACCOUNTS and Accounts are
+ * two different topics.
+ *
Topic names can include the space character. For example, Accounts
+ * payable is a valid topic.
+ *
A leading "/" creates a distinct topic. For example, /finance is
+ * different from finance. /finance matches "+/+" and "/+", but
+ * not "+".
+ *
Do not include the null character (Unicode \x0000) in
+ * any topic.
+ *
+ *
+ *
The following principles apply to the construction and content of a topic
+ * tree:
+ *
+ *
+ *
The length is limited to 64k but within that there are no limits to the
+ * number of levels in a topic tree.
+ *
There can be any number of root nodes; that is, there can be any number
+ * of topic trees.
+ *
+ *
+ *
The method returns control before the publish completes. Completion can
+ * be tracked by:
+ *
+ *
Setting an {@link IMqttAsyncClient#setCallback(MqttCallback)} where the
+ * {@link MqttCallback#deliveryComplete(IMqttDeliveryToken)}
+ * method will be called.
+ *
Waiting on the returned token {@link MqttToken#waitForCompletion()} or
+ *
Passing in a callback {@link IMqttActionListener} to this method
+ *
+ *
+ *
+ * @param topic to deliver the message to, for example "finance/stock/ibm".
+ * @param message to deliver to the server
+ * @param userContext optional object used to pass context to the callback. Use
+ * null if not required.
+ * @param callback optional listener that will be notified when message delivery
+ * has completed to the requested quality of service
+ * @return token used to track and wait for the publish to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttPersistenceException when a problem occurs storing the message
+ * @throws IllegalArgumentException if value of QoS is not 0, 1 or 2.
+ * @throws MqttException for other errors encountered while publishing the message.
+ * For instance client not connected.
+ * @see MqttMessage
+ */
+ public IMqttDeliveryToken publish(String topic, MqttMessage message,
+ Object userContext, IMqttActionListener callback) throws MqttException, MqttPersistenceException;
+
+ /**
+ * Subscribe to a topic, which may include wildcards.
+ *
+ * @see #subscribe(String[], int[], Object, IMqttActionListener)
+ *
+ * @param topicFilter the topic to subscribe to, which can include wildcards.
+ * @param qos the maximum quality of service at which to subscribe. Messages
+ * published at a lower quality of service will be received at the published
+ * QoS. Messages published at a higher quality of service will be received using
+ * the QoS specified on the subscribe.
+ * @return token used to track and wait for the subscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException if there was an error registering the subscription.
+ */
+ public IMqttToken subscribe(String topicFilter, int qos) throws MqttException;
+
+ /**
+ * Subscribe to a topic, which may include wildcards.
+ *
+ * @see #subscribe(String[], int[], Object, IMqttActionListener)
+ *
+ * @param topicFilter the topic to subscribe to, which can include wildcards.
+ * @param qos the maximum quality of service at which to subscribe. Messages
+ * published at a lower quality of service will be received at the published
+ * QoS. Messages published at a higher quality of service will be received using
+ * the QoS specified on the subscribe.
+ * @param userContext optional object used to pass context to the callback. Use
+ * null if not required.
+ * @param callback optional listener that will be notified when subscribe
+ * has completed
+ * @return token used to track and wait for the subscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException if there was an error registering the subscription.
+ */
+ public IMqttToken subscribe(String topicFilter, int qos, Object userContext, IMqttActionListener callback)
+ throws MqttException;
+
+ /**
+ * Subscribe to multiple topics, each of which may include wildcards.
+ *
+ *
Provides an optimized way to subscribe to multiple topics compared to
+ * subscribing to each one individually.
+ *
+ * @see #subscribe(String[], int[], Object, IMqttActionListener)
+ *
+ * @param topicFilters one or more topics to subscribe to, which can include wildcards
+ * @param qos the maximum quality of service at which to subscribe. Messages
+ * published at a lower quality of service will be received at the published
+ * QoS. Messages published at a higher quality of service will be received using
+ * the QoS specified on the subscribe.
+ * @return token used to track and wait for the subscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException if there was an error registering the subscription.
+ */
+ public IMqttToken subscribe(String[] topicFilters, int[] qos) throws MqttException;
+
+ /**
+ * Subscribes to multiple topics, each of which may include wildcards.
+ *
Provides an optimized way to subscribe to multiple topics compared to
+ * subscribing to each one individually.
+ *
The {@link #setCallback(MqttCallback)} method
+ * should be called before this method, otherwise any received messages
+ * will be discarded.
+ *
+ *
+ * If (@link MqttConnectOptions#setCleanSession(boolean)} was set to true
+ * when when connecting to the server then the subscription remains in place
+ * until either:
+ *
+ *
The client disconnects
+ *
An unsubscribe method is called to un-subscribe the topic
+ *
+ *
+ *
+ * If (@link MqttConnectOptions#setCleanSession(boolean)} was set to false
+ * when connecting to the server then the subscription remains in place
+ * until either:
+ *
+ *
An unsubscribe method is called to unsubscribe the topic
+ *
The next time the client connects with cleanSession set to true
+ *
+ * With cleanSession set to false the MQTT server will store messages on
+ * behalf of the client when the client is not connected. The next time the
+ * client connects with the same client ID the server will
+ * deliver the stored messages to the client.
+ *
+ *
+ *
The "topic filter" string used when subscribing
+ * may contain special characters, which allow you to subscribe to multiple topics
+ * at once.
+ *
The topic level separator is used to introduce structure into the topic, and
+ * can therefore be specified within the topic for that purpose. The multi-level
+ * wildcard and single-level wildcard can be used for subscriptions, but they
+ * cannot be used within a topic by the publisher of a message.
+ *
+ *
Topic level separator
+ *
The forward slash (/) is used to separate each level within
+ * a topic tree and provide a hierarchical structure to the topic space. The
+ * use of the topic level separator is significant when the two wildcard characters
+ * are encountered in topics specified by subscribers.
+ *
+ *
Multi-level wildcard
+ *
The number sign (#) is a wildcard character that matches
+ * any number of levels within a topic. For example, if you subscribe to
+ * finance/stock/ibm/#, you receive
+ * messages on these topics:
+ *
The multi-level wildcard
+ * can represent zero or more levels. Therefore, finance/# can also match
+ * the singular finance, where # represents zero levels. The topic
+ * level separator is meaningless in this context, because there are no levels
+ * to separate.
+ *
+ *
The multi-level wildcard can
+ * be specified only on its own or next to the topic level separator character.
+ * Therefore, # and finance/# are both valid, but finance# is
+ * not valid. The multi-level wildcard must be the last character
+ * used within the topic tree. For example, finance/# is valid but
+ * finance/#/closingprice is not valid.
+ *
+ *
Single-level wildcard
+ *
The plus sign (+) is a wildcard character that matches only one topic
+ * level. For example, finance/stock/+ matches
+ * finance/stock/ibm and finance/stock/xyz,
+ * but not finance/stock/ibm/closingprice. Also, because the single-level
+ * wildcard matches only a single level, finance/+ does not match finance.
+ *
+ *
Use
+ * the single-level wildcard at any level in the topic tree, and in conjunction
+ * with the multilevel wildcard. Specify the single-level wildcard next to the
+ * topic level separator, except when it is specified on its own. Therefore,
+ * + and finance/+ are both valid, but finance+ is
+ * not valid. The single-level wildcard can be used at the end of the
+ * topic tree or within the topic tree.
+ * For example, finance/+ and finance/+/ibm are both valid.
+ *
+ *
+ *
+ *
The method returns control before the subscribe completes. Completion can
+ * be tracked by:
+ *
+ *
Waiting on the supplied token {@link MqttToken#waitForCompletion()} or
+ *
Passing in a callback {@link IMqttActionListener} to this method
+ *
+ *
+ *
+ * @param topicFilters one or more topics to subscribe to, which can include wildcards
+ * @param qos the maximum quality of service to subscribe each topic at.Messages
+ * published at a lower quality of service will be received at the published
+ * QoS. Messages published at a higher quality of service will be received using
+ * the QoS specified on the subscribe.
+ * @param userContext optional object used to pass context to the callback. Use
+ * null if not required.
+ * @param callback optional listener that will be notified when subscribe
+ * has completed
+ * @return token used to track and wait for the subscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException if there was an error registering the subscription.
+ * @throws IllegalArgumentException if the two supplied arrays are not the same size.
+ */
+ public IMqttToken subscribe(String[] topicFilters, int[] qos, Object userContext, IMqttActionListener callback)
+ throws MqttException;
+
+ /**
+ * Requests the server unsubscribe the client from a topic.
+ *
+ * @see #unsubscribe(String[], Object, IMqttActionListener)
+ * @param topicFilter the topic to unsubscribe from. It must match a topicFilter
+ * specified on an earlier subscribe.
+ * @return token used to track and wait for the unsubscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException if there was an error unregistering the subscription.
+ */
+ public IMqttToken unsubscribe(String topicFilter) throws MqttException;
+
+ /**
+ * Requests the server unsubscribe the client from one or more topics.
+ *
+ * @see #unsubscribe(String[], Object, IMqttActionListener)
+ *
+ * @param topicFilters one or more topics to unsubscribe from. Each topicFilter
+ * must match one specified on an earlier subscribe. *
+ * @return token used to track and wait for the unsubscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException if there was an error unregistering the subscription.
+ */
+ public IMqttToken unsubscribe(String[] topicFilters) throws MqttException;
+
+ /**
+ * Requests the server unsubscribe the client from a topics.
+ *
+ * @see #unsubscribe(String[], Object, IMqttActionListener)
+ *
+ * @param topicFilter the topic to unsubscribe from. It must match a topicFilter
+ * specified on an earlier subscribe.
+ * @param userContext optional object used to pass context to the callback. Use
+ * null if not required.
+ * @param callback optional listener that will be notified when unsubscribe
+ * has completed
+ * @return token used to track and wait for the unsubscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException if there was an error unregistering the subscription.
+ */
+ public IMqttToken unsubscribe(String topicFilter, Object userContext, IMqttActionListener callback)
+ throws MqttException;
+
+ /**
+ * Requests the server unsubscribe the client from one or more topics.
+ *
+ * Unsubcribing is the opposite of subscribing. When the server receives the
+ * unsubscribe request it looks to see if it can find a matching subscription for the
+ * client and then removes it. After this point the server will send no more
+ * messages to the client for this subscription.
+ *
+ *
The topic(s) specified on the unsubscribe must match the topic(s)
+ * specified in the original subscribe request for the unsubscribe to succeed
+ *
+ *
The method returns control before the unsubscribe completes. Completion can
+ * be tracked by:
+ *
+ *
Waiting on the returned token {@link MqttToken#waitForCompletion()} or
+ *
Passing in a callback {@link IMqttActionListener} to this method
+ *
+ *
+ *
+ * @param topicFilters one or more topics to unsubscribe from. Each topicFilter
+ * must match one specified on an earlier subscribe.
+ * @param userContext optional object used to pass context to the callback. Use
+ * null if not required.
+ * @param callback optional listener that will be notified when unsubscribe
+ * has completed
+ * @return token used to track and wait for the unsubscribe to complete. The token
+ * will be passed to callback methods if set.
+ * @throws MqttException if there was an error unregistering the subscription.
+ */
+ public IMqttToken unsubscribe(String[] topicFilters, Object userContext, IMqttActionListener callback)
+ throws MqttException;
+
+
+ /**
+ * Sets a callback listener to use for events that happen asynchronously.
+ *
There are a number of events that the listener will be notified about.
+ * These include:
+ *
+ *
A new message has arrived and is ready to be processed
+ *
The connection to the server has been lost
+ *
Delivery of a message to the server has completed
+ *
+ *
+ *
Other events that track the progress of an individual operation such
+ * as connect and subscribe can be tracked using the {@link MqttToken} returned from
+ * each non-blocking method or using setting a {@link IMqttActionListener} on the
+ * non-blocking method.
+ * @see MqttCallback
+ * @param callback which will be invoked for certain asynchronous events
+ */
+ public void setCallback(MqttCallback callback);
+
+ /**
+ * Returns the delivery tokens for any outstanding publish operations.
+ *
If a client has been restarted and there are messages that were in the
+ * process of being delivered when the client stopped this method
+ * returns a token for each in-flight message enabling the delivery to be tracked
+ * Alternately the {@link MqttCallback#deliveryComplete(IMqttDeliveryToken)}
+ * callback can be used to track the delivery of outstanding messages.
+ *
+ *
If a client connects with cleanSession true then there will be no
+ * delivery tokens as the cleanSession option deletes all earlier state.
+ * For state to be remembered the client must connect with cleanSession
+ * set to false
+ * @return zero or more delivery tokens
+ */
+ public IMqttDeliveryToken[] getPendingDeliveryTokens();
+
+ /**
+ * Close the client
+ * Releases all resource associated with the client. After the client has
+ * been closed it cannot be reused. For instance attempts to connect will fail.
+ * @throws MqttException if the client is not disconnected.
+ */
+ public void close() throws MqttException;
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttClient.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttClient.java
new file mode 100644
index 00000000..adda45fb
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttClient.java
@@ -0,0 +1,534 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ * Ian Craggs - MQTT 3.1.1 support
+ */
+
+package org.eclipse.paho.client.mqttv3;
+
+/**
+ * Enables an application to communicate with an MQTT server using using blocking methods.
+ *
+ * This interface allows applications to utilize all features of the MQTT version 3.1
+ * specification including:
+ *
+ *
connect
+ *
publish
+ *
subscribe
+ *
unsubscribe
+ *
disconnect
+ *
+ *
+ *
+ * There are two styles of MQTT client, this one and {@link IMqttAsyncClient}.
+ *
+ *
IMqttClient provides a set of methods that block and return control to the application
+ * program once the MQTT action has completed.
+ *
IMqttAsyncClient provides a set of non-blocking methods that return control to the
+ * invoking application after initial validation of parameters and state. The main processing is
+ * performed in the background so as not to block the application programs thread. This non
+ * blocking approach is handy when the application wants to carry on processing while the
+ * MQTT action takes place. For instance connecting to an MQTT server can take time, using
+ * the non-blocking connect method allows an application to display a busy indicator while the
+ * connect action is occurring. Non-blocking methods are particularly useful in event-oriented
+ * programs and graphical programs where issuing methods that take time to complete on the the
+ * main or GUI thread can cause problems.
+ *
+ *
+ *
+ * The non-blocking client can also be used in a blocking form by turning a non-blocking
+ * method into a blocking invocation using the following pattern:
+ *
+ * Using the non-blocking client allows an application to use a mixture of blocking and
+ * non-blocking styles. Using the blocking client only allows an application to use one
+ * style. The blocking client provides compatibility with earlier versions
+ * of the MQTT client.
+ */
+public interface IMqttClient { //extends IMqttAsyncClient {
+ /**
+ * Connects to an MQTT server using the default options.
+ *
The default options are specified in {@link MqttConnectOptions} class.
+ *
+ *
+ * @throws MqttSecurityException when the server rejects the connect for security
+ * reasons
+ * @throws MqttException for non security related problems
+ * @see #connect(MqttConnectOptions)
+ */
+ public void connect() throws MqttSecurityException, MqttException;
+
+ /**
+ * Connects to an MQTT server using the specified options.
+ *
The server to connect to is specified on the constructor.
+ * It is recommended to call {@link #setCallback(MqttCallback)} prior to
+ * connecting in order that messages destined for the client can be accepted
+ * as soon as the client is connected.
+ *
+ *
This is a blocking method that returns once connect completes
+ *
+ * @param options a set of connection parameters that override the defaults.
+ * @throws MqttSecurityException when the server rejects the connect for security
+ * reasons
+ * @throws MqttException for non security related problems including communication errors
+ */
+ public void connect(MqttConnectOptions options) throws MqttSecurityException, MqttException;
+
+ /**
+ * Connects to an MQTT server using the specified options.
+ *
The server to connect to is specified on the constructor.
+ * It is recommended to call {@link #setCallback(MqttCallback)} prior to
+ * connecting in order that messages destined for the client can be accepted
+ * as soon as the client is connected.
+ *
+ *
This is a blocking method that returns once connect completes
+ *
+ * @param options a set of connection parameters that override the defaults.
+ * @return the MqttToken used for the call. Can be used to obtain the session present flag
+ * @throws MqttSecurityException when the server rejects the connect for security
+ * reasons
+ * @throws MqttException for non security related problems including communication errors
+ */
+public IMqttToken connectWithResult(MqttConnectOptions options) throws MqttSecurityException, MqttException;
+
+ /**
+ * Disconnects from the server.
+ *
An attempt is made to quiesce the client allowing outstanding
+ * work to complete before disconnecting. It will wait
+ * for a maximum of 30 seconds for work to quiesce before disconnecting.
+ * This method must not be called from inside {@link MqttCallback} methods.
+ *
+ *
+ * @see #disconnect(long)
+ */
+ public void disconnect() throws MqttException;
+
+ /**
+ * Disconnects from the server.
+ *
+ * The client will wait for all {@link MqttCallback} methods to
+ * complete. It will then wait for up to the quiesce timeout to allow for
+ * work which has already been initiated to complete - for example, it will
+ * wait for the QoS 2 flows from earlier publications to complete. When work has
+ * completed or after the quiesce timeout, the client will disconnect from
+ * the server. If the cleanSession flag was set to false and is set to false the
+ * next time a connection is made QoS 1 and 2 messages that
+ * were not previously delivered will be delivered.
+ *
+ *
This is a blocking method that returns once disconnect completes
+ *
+ * @param quiesceTimeout the amount of time in milliseconds to allow for
+ * existing work to finish before disconnecting. A value of zero or less
+ * means the client will not quiesce.
+ * @throws MqttException if a problem is encountered while disconnecting
+ */
+ public void disconnect(long quiesceTimeout) throws MqttException;
+
+ /**
+ * Disconnects from the server forcibly to reset all the states. Could be useful when disconnect attempt failed.
+ *
+ * Because the client is able to establish the TCP/IP connection to a none MQTT server and it will certainly fail to
+ * send the disconnect packet. It will wait for a maximum of 30 seconds for work to quiesce before disconnecting and
+ * wait for a maximum of 10 seconds for sending the disconnect packet to server.
+ *
+ * @throws MqttException if any unexpected error
+ * @since 0.4.1
+ */
+ public void disconnectForcibly() throws MqttException;
+
+ /**
+ * Disconnects from the server forcibly to reset all the states. Could be useful when disconnect attempt failed.
+ *
+ * Because the client is able to establish the TCP/IP connection to a none MQTT server and it will certainly fail to
+ * send the disconnect packet. It will wait for a maximum of 30 seconds for work to quiesce before disconnecting.
+ *
+ * @param disconnectTimeout the amount of time in milliseconds to allow send disconnect packet to server.
+ * @throws MqttException if any unexpected error
+ * @since 0.4.1
+ */
+ public void disconnectForcibly(long disconnectTimeout) throws MqttException;
+
+ /**
+ * Disconnects from the server forcibly to reset all the states. Could be useful when disconnect attempt failed.
+ *
+ * Because the client is able to establish the TCP/IP connection to a none MQTT server and it will certainly fail to
+ * send the disconnect packet.
+ *
+ * @param quiesceTimeout the amount of time in milliseconds to allow for existing work to finish before
+ * disconnecting. A value of zero or less means the client will not quiesce.
+ * @param disconnectTimeout the amount of time in milliseconds to allow send disconnect packet to server.
+ * @throws MqttException if any unexpected error
+ * @since 0.4.1
+ */
+ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout) throws MqttException;
+
+ /**
+ * Subscribe to a topic, which may include wildcards using a QoS of 1.
+ *
+ * @see #subscribe(String[], int[])
+ *
+ * @param topicFilter the topic to subscribe to, which can include wildcards.
+ * @throws MqttException if there was an error registering the subscription.
+ */
+ public void subscribe(String topicFilter) throws MqttException, MqttSecurityException;
+
+ /**
+ * Subscribes to a one or more topics, which may include wildcards using a QoS of 1.
+ *
+ * @see #subscribe(String[], int[])
+ *
+ * @param topicFilters the topic to subscribe to, which can include wildcards.
+ * @throws MqttException if there was an error registering the subscription.
+ */
+ public void subscribe(String[] topicFilters) throws MqttException;
+
+ /**
+ * Subscribe to a topic, which may include wildcards.
+ *
+ * @see #subscribe(String[], int[])
+ *
+ * @param topicFilter the topic to subscribe to, which can include wildcards.
+ * @param qos the maximum quality of service at which to subscribe. Messages
+ * published at a lower quality of service will be received at the published
+ * QoS. Messages published at a higher quality of service will be received using
+ * the QoS specified on the subscribe.
+ * @throws MqttException if there was an error registering the subscription.
+ */
+ public void subscribe(String topicFilter, int qos) throws MqttException;
+
+ /**
+ * Subscribes to multiple topics, each of which may include wildcards.
+ *
The {@link #setCallback(MqttCallback)} method
+ * should be called before this method, otherwise any received messages
+ * will be discarded.
+ *
+ *
+ * If (@link MqttConnectOptions#setCleanSession(boolean)} was set to true
+ * when when connecting to the server then the subscription remains in place
+ * until either:
+ *
+ *
The client disconnects
+ *
An unsubscribe method is called to un-subscribe the topic
+ *
+ *
+ *
+ * If (@link MqttConnectOptions#setCleanSession(boolean)} was set to false
+ * when when connecting to the server then the subscription remains in place
+ * until either:
+ *
+ *
An unsubscribe method is called to unsubscribe the topic
+ *
The client connects with cleanSession set to true
+ *
+ * With cleanSession set to false the MQTT server will store messages on
+ * behalf of the client when the client is not connected. The next time the
+ * client connects with the same client ID the server will
+ * deliver the stored messages to the client.
+ *
+ *
+ *
The "topic filter" string used when subscribing
+ * may contain special characters, which allow you to subscribe to multiple topics
+ * at once.
+ *
The topic level separator is used to introduce structure into the topic, and
+ * can therefore be specified within the topic for that purpose. The multi-level
+ * wildcard and single-level wildcard can be used for subscriptions, but they
+ * cannot be used within a topic by the publisher of a message.
+ *
+ *
Topic level separator
+ *
The forward slash (/) is used to separate each level within
+ * a topic tree and provide a hierarchical structure to the topic space. The
+ * use of the topic level separator is significant when the two wildcard characters
+ * are encountered in topics specified by subscribers.
+ *
+ *
Multi-level wildcard
+ *
The number sign (#) is a wildcard character that matches
+ * any number of levels within a topic. For example, if you subscribe to
+ * finance/stock/ibm/#, you receive
+ * messages on these topics:
+ *
The multi-level wildcard
+ * can represent zero or more levels. Therefore, finance/# can also match
+ * the singular finance, where # represents zero levels. The topic
+ * level separator is meaningless in this context, because there are no levels
+ * to separate.
+ *
+ *
The multi-level wildcard can
+ * be specified only on its own or next to the topic level separator character.
+ * Therefore, # and finance/# are both valid, but finance# is
+ * not valid. The multi-level wildcard must be the last character
+ * used within the topic tree. For example, finance/# is valid but
+ * finance/#/closingprice is not valid.
+ *
+ *
Single-level wildcard
+ *
The plus sign (+) is a wildcard character that matches only one topic
+ * level. For example, finance/stock/+ matches
+ * finance/stock/ibm and finance/stock/xyz,
+ * but not finance/stock/ibm/closingprice. Also, because the single-level
+ * wildcard matches only a single level, finance/+ does not match finance.
+ *
+ *
Use
+ * the single-level wildcard at any level in the topic tree, and in conjunction
+ * with the multilevel wildcard. Specify the single-level wildcard next to the
+ * topic level separator, except when it is specified on its own. Therefore,
+ * + and finance/+ are both valid, but finance+ is
+ * not valid. The single-level wildcard can be used at the end of the
+ * topic tree or within the topic tree.
+ * For example, finance/+ and finance/+/ibm are both valid.
+ *
+ *
+ *
+ *
+ *
This is a blocking method that returns once subscribe completes
+ *
+ * @param topicFilters one or more topics to subscribe to, which can include wildcards.
+ * @param qos the maximum quality of service to subscribe each topic at.Messages
+ * published at a lower quality of service will be received at the published
+ * QoS. Messages published at a higher quality of service will be received using
+ * the QoS specified on the subscribe.
+ * @throws MqttException if there was an error registering the subscription.
+ * @throws IllegalArgumentException if the two supplied arrays are not the same size.
+ */
+ public void subscribe(String[] topicFilters, int[] qos) throws MqttException;
+
+ /**
+ * Requests the server unsubscribe the client from a topic.
+ *
+ * @see #unsubscribe(String[])
+ * @param topicFilter the topic to unsubscribe from. It must match a topicFilter
+ * specified on the subscribe.
+ * @throws MqttException if there was an error unregistering the subscription.
+ */
+ public void unsubscribe(String topicFilter) throws MqttException;
+
+ /**
+ * Requests the server unsubscribe the client from one or more topics.
+ *
+ * Unsubcribing is the opposite of subscribing. When the server receives the
+ * unsubscribe request it looks to see if it can find a subscription for the
+ * client and then removes it. After this point the server will send no more
+ * messages to the client for this subscription.
+ *
+ *
The topic(s) specified on the unsubscribe must match the topic(s)
+ * specified in the original subscribe request for the subscribe to succeed
+ *
+ *
+ *
This is a blocking method that returns once unsubscribe completes
+ *
+ * @param topicFilters one or more topics to unsubscribe from. Each topicFilter
+ * must match one specified on a subscribe
+ * @throws MqttException if there was an error unregistering the subscription.
+ */
+ public void unsubscribe(String[] topicFilters) throws MqttException;
+
+
+ /**
+ * Publishes a message to a topic on the server and return once it is delivered.
+ *
This is a convenience method, which will
+ * create a new {@link MqttMessage} object with a byte array payload and the
+ * specified QoS, and then publish it. All other values in the
+ * message will be set to the defaults.
+ *
+ *
+ * @param topic to deliver the message to, for example "finance/stock/ibm".
+ * @param payload the byte array to use as the payload
+ * @param qos the Quality of Service to deliver the message at. Valid values are 0, 1 or 2.
+ * @param retained whether or not this message should be retained by the server.
+ * @throws MqttPersistenceException when a problem with storing the message
+ * @throws IllegalArgumentException if value of QoS is not 0, 1 or 2.
+ * @throws MqttException for other errors encountered while publishing the message.
+ * For instance client not connected.
+ * @see #publish(String, MqttMessage)
+ * @see MqttMessage#setQos(int)
+ * @see MqttMessage#setRetained(boolean)
+ */
+ public void publish(String topic, byte[] payload, int qos, boolean retained) throws MqttException, MqttPersistenceException;
+
+ /**
+ * Publishes a message to a topic on the server.
+ *
+ * Delivers a message to the server at the requested quality of service and returns control
+ * once the message has been delivered. In the event the connection fails or the client
+ * stops, any messages that are in the process of being delivered will be delivered once
+ * a connection is re-established to the server on condition that:
+ *
+ *
The connection is re-established with the same clientID
+ *
The original connection was made with (@link MqttConnectOptions#setCleanSession(boolean)}
+ * set to false
+ *
The connection is re-established with (@link MqttConnectOptions#setCleanSession(boolean)}
+ * set to false
+ *
+ *
+ *
In the event that the connection breaks or the client stops it is still possible to determine
+ * when the delivery of the message completes. Prior to re-establishing the connection to the server:
+ *
+ *
Register a {@link #setCallback(MqttCallback)} callback on the client and the delivery complete
+ * callback will be notified once a delivery of a message completes
+ *
or call {@link #getPendingDeliveryTokens()} which will return a token for each message that
+ * is in-flight. The token can be used to wait for delivery to complete.
+ *
+ *
+ *
+ *
When building an application,
+ * the design of the topic tree should take into account the following principles
+ * of topic name syntax and semantics:
+ *
+ *
+ *
A topic must be at least one character long.
+ *
Topic names are case sensitive. For example, ACCOUNTS and Accounts are
+ * two different topics.
+ *
Topic names can include the space character. For example, Accounts
+ * payable is a valid topic.
+ *
A leading "/" creates a distinct topic. For example, /finance is
+ * different from finance. /finance matches "+/+" and "/+", but
+ * not "+".
+ *
Do not include the null character (Unicode \x0000) in
+ * any topic.
+ *
+ *
+ *
The following principles apply to the construction and content of a topic
+ * tree:
+ *
+ *
+ *
The length is limited to 64k but within that there are no limits to the
+ * number of levels in a topic tree.
+ *
There can be any number of root nodes; that is, there can be any number
+ * of topic trees.
+ *
+ *
+ *
+ *
This is a blocking method that returns once publish completes
*
+ *
+ * @param topic to deliver the message to, for example "finance/stock/ibm".
+ * @param message to delivery to the server
+ * @throws MqttPersistenceException when a problem with storing the message
+ * @throws IllegalArgumentException if value of QoS is not 0, 1 or 2.
+ * @throws MqttException for other errors encountered while publishing the message.
+ * For instance client not connected.
+ */
+ public void publish(String topic, MqttMessage message) throws MqttException, MqttPersistenceException;
+
+ /**
+ * Sets the callback listener to use for events that happen asynchronously.
+ *
There are a number of events that listener will be notified about. These include
+ *
+ *
A new message has arrived and is ready to be processed
+ *
The connection to the server has been lost
+ *
Delivery of a message to the server has completed.
+ *
+ *
+ *
Other events that track the progress of an individual operation such
+ * as connect and subscribe can be tracked using the {@link MqttToken} passed to the
+ * operation
+ * @see MqttCallback
+ * @param callback the class to callback when for events related to the client
+ */
+ public void setCallback(MqttCallback callback);
+
+ /**
+ * Get a topic object which can be used to publish messages.
+ *
An alternative method that should be used in preference to this one when publishing a message is:
+ *
+ *
{@link MqttClient#publish(String, MqttMessage)} to publish a message in a blocking manner
+ *
or use publish methods on the non-blocking client like {@link IMqttAsyncClient#publish(String, MqttMessage, Object, IMqttActionListener)}
+ *
+ *
+ *
When building an application,
+ * the design of the topic tree should take into account the following principles
+ * of topic name syntax and semantics:
+ *
+ *
+ *
A topic must be at least one character long.
+ *
Topic names are case sensitive. For example, ACCOUNTS and Accounts are
+ * two different topics.
+ *
Topic names can include the space character. For example, Accounts
+ * payable is a valid topic.
+ *
A leading "/" creates a distinct topic. For example, /finance is
+ * different from finance. /finance matches "+/+" and "/+", but
+ * not "+".
+ *
Do not include the null character (Unicode \x0000) in
+ * any topic.
+ *
+ *
+ *
The following principles apply to the construction and content of a topic
+ * tree:
+ *
+ *
+ *
The length is limited to 64k but within that there are no limits to the
+ * number of levels in a topic tree.
+ *
There can be any number of root nodes; that is, there can be any number
+ * of topic trees.
+ *
+ *
+ *
+ * @param topic the topic to use, for example "finance/stock/ibm".
+ * @return an MqttTopic object, which can be used to publish messages to
+ * the topic.
+ * @throws IllegalArgumentException if the topic contains a '+' or '#'
+ * wildcard character.
+ */
+ public MqttTopic getTopic(String topic);
+
+ /**
+ * Determines if this client is currently connected to the server.
+ *
+ * @return true if connected, false otherwise.
+ */
+ public boolean isConnected();
+
+ /**
+ * Returns the client ID used by this client.
+ *
All clients connected to the
+ * same server or server farm must have a unique ID.
+ *
+ *
+ * @return the client ID used by this client.
+ */
+ public String getClientId();
+
+ /**
+ * Returns the address of the server used by this client, as a URI.
+ *
The format is the same as specified on the constructor.
+ *
+ *
+ * @return the server's address, as a URI String.
+ * @see MqttAsyncClient#MqttAsyncClient(String, String)
+ */
+ public String getServerURI();
+
+ /**
+ * Returns the delivery tokens for any outstanding publish operations.
+ *
If a client has been restarted and there are messages that were in the
+ * process of being delivered when the client stopped this method will
+ * return a token for each message enabling the delivery to be tracked
+ * Alternately the {@link MqttCallback#deliveryComplete(IMqttDeliveryToken)}
+ * callback can be used to track the delivery of outstanding messages.
+ *
+ *
If a client connects with cleanSession true then there will be no
+ * delivery tokens as the cleanSession option deletes all earlier state.
+ * For state to be remembered the client must connect with cleanSession
+ * set to false
+ * @return zero or more delivery tokens
+ */
+ public IMqttDeliveryToken[] getPendingDeliveryTokens();
+
+ /**
+ * Close the client
+ * Releases all resource associated with the client. After the client has
+ * been closed it cannot be reused. For instance attempts to connect will fail.
+ * @throws MqttException if the client is not disconnected.
+ */
+ public void close() throws MqttException;
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttDeliveryToken.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttDeliveryToken.java
new file mode 100644
index 00000000..9c806946
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttDeliveryToken.java
@@ -0,0 +1,41 @@
+package org.eclipse.paho.client.mqttv3;
+/**
+ * Provides a mechanism for tracking the delivery of a message.
+ *
+ *
A subclass of IMqttToken that allows the delivery of a message to be tracked.
+ * Unlike instances of IMqttToken delivery tokens can be used across connection
+ * and client restarts. This enables the delivery of a messages to be tracked
+ * after failures. There are two approaches
+ *
+ *
A list of delivery tokens for in-flight messages can be obtained using
+ * {@link IMqttAsyncClient#getPendingDeliveryTokens()}. The waitForCompletion
+ * method can then be used to block until the delivery is complete.
+ *
A {@link MqttCallback} can be set on the client. Once a message has been
+ * delivered the {@link MqttCallback#deliveryComplete(IMqttDeliveryToken)} method will
+ * be called withe delivery token being passed as a parameter.
+ *
+ *
+ * An action is in progress until either:
+ *
+ *
isComplete() returns true or
+ *
getException() is not null. If a client shuts down before delivery is complete.
+ * an exception is returned. As long as the Java Runtime is not stopped a delivery token
+ * is valid across a connection disconnect and reconnect. In the event the client
+ * is shut down the getPendingDeliveryTokens method can be used once the client is
+ * restarted to obtain a list of delivery tokens for inflight messages.
+ *
+ *
+ *
+ */
+
+public interface IMqttDeliveryToken extends IMqttToken {
+ /**
+ * Returns the message associated with this token.
+ *
Until the message has been delivered, the message being delivered will
+ * be returned. Once the message has been delivered null will be
+ * returned.
+ * @return the message associated with this token or null if already delivered.
+ * @throws MqttException if there was a problem completing retrieving the message
+ */
+ public MqttMessage getMessage() throws MqttException;
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttPingActionListener.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttPingActionListener.java
new file mode 100644
index 00000000..16e87342
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttPingActionListener.java
@@ -0,0 +1,10 @@
+package org.eclipse.paho.client.mqttv3;
+
+/**
+ * Created by ahmadulil on 12/18/15.
+ *
+ * Interface untuk callback ketika ping sent
+ */
+public interface IMqttPingActionListener extends IMqttActionListener {
+ void onPingSent(IMqttToken token);
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttToken.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttToken.java
new file mode 100644
index 00000000..97032694
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/IMqttToken.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2009, 2012 IBM Corp.
+ *
+ * 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
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ * Ian Craggs - MQTT 3.1.1 support
+ */
+package org.eclipse.paho.client.mqttv3;
+
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttWireMessage;
+
+/**
+ * Provides a mechanism for tracking the completion of an asynchronous task.
+ *
+ *
When using the asynchronous/non-blocking MQTT programming interface all
+ * methods/operations that take any time (and in particular those that involve
+ * any network operation) return control to the caller immediately. The operation
+ * then proceeds to run in the background so as not to block the invoking thread.
+ * An IMqttToken is used to track the state of the operation. An application can use the
+ * token to wait for an operation to complete. A token is passed to callbacks
+ * once the operation completes and provides context linking it to the original
+ * request. A token is associated with a single operation.
+ *
+ * An action is in progress until either:
+ *
+ *
isComplete() returns true or
+ *
getException() is not null.
+ *
+ *
+ *
+ */
+public interface IMqttToken {
+
+ /**
+ * Blocks the current thread until the action this token is associated with has
+ * completed.
+ *
+ * @throws MqttException if there was a problem with the action associated with the token.
+ * @see #waitForCompletion(long)
+ */
+ public void waitForCompletion() throws MqttException;
+
+ /**
+ * Blocks the current thread until the action this token is associated with has
+ * completed.
+ *
The timeout specifies the maximum time it will block for. If the action
+ * completes before the timeout then control returns immediately, if not
+ * it will block until the timeout expires.
+ *
If the action being tracked fails or the timeout expires an exception will
+ * be thrown. In the event of a timeout the action may complete after timeout.
+ *
+ *
+ * @param timeout the maximum amount of time to wait for, in milliseconds.
+ * @throws MqttException if there was a problem with the action associated with the token.
+ */
+ public void waitForCompletion(long timeout) throws MqttException;
+
+ /**
+ * Returns whether or not the action has finished.
+ *
True will be returned both in the case where the action finished successfully
+ * and in the case where it failed. If the action failed {@link #getException()} will
+ * be non null.
+ *
+ */
+ public boolean isComplete();
+
+ /**
+ * Returns an exception providing more detail if an operation failed.
+ *
While an action in in progress and when an action completes successfully
+ * null will be returned. Certain errors like timeout or shutting down will not
+ * set the exception as the action has not failed or completed at that time
+ *
+ * @return exception may return an exception if the operation failed. Null will be
+ * returned while action is in progress and if action completes successfully.
+ */
+ public MqttException getException();
+
+ /**
+ * Register a listener to be notified when an action completes.
+ *
Once a listener is registered it will be invoked when the action the token
+ * is associated with either succeeds or fails.
+ *
+ * @param listener to be invoked once the action completes
+ */
+ public void setActionCallback(IMqttActionListener listener);
+
+ /**
+ * Return the async listener for this token.
+ * @return listener that is set on the token or null if a listener is not registered.
+ */
+ public IMqttActionListener getActionCallback();
+
+ /**
+ * Returns the MQTT client that is responsible for processing the asynchronous
+ * action
+ */
+ public IMqttAsyncClient getClient();
+
+ /**
+ * Returns the topic string(s) for the action being tracked by this
+ * token. If the action has not been initiated or the action has not
+ * topic associated with it such as connect then null will be returned.
+ *
+ * @return the topic string(s) for the subscribe being tracked by this token or null
+ */
+ public String[] getTopics();
+
+ /**
+ * Store some context associated with an action.
+ *
Allows the caller of an action to store some context that can be
+ * accessed from within the ActionListener associated with the action. This
+ * can be useful when the same ActionListener is associated with multiple
+ * actions
+ * @param userContext to associate with an action
+ */
+ public void setUserContext(Object userContext);
+
+ /**
+ * Retrieve the context associated with an action.
+ *
Allows the ActionListener associated with an action to retrieve any context
+ * that was associated with the action when the action was invoked. If not
+ * context was provided null is returned.
+
+ * @return Object context associated with an action or null if there is none.
+ */
+ public Object getUserContext();
+
+ /**
+ * Returns the message ID of the message that is associated with the token.
+ * A message id of zero will be returned for tokens associated with
+ * connect, disconnect and ping operations as there can only ever
+ * be one of these outstanding at a time. For other operations
+ * the MQTT message id flowed over the network.
+ */
+ public int getMessageId();
+
+ /**
+ * Returns the granted QoS list from a suback
+ */
+ public int[] getGrantedQos();
+
+ /**
+ * Returns the session present flag from a connack
+ */
+ public boolean getSessionPresent();
+
+ /**
+ * Returns the response wire message
+ */
+ public MqttWireMessage getResponse();
+
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttAsyncClient.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttAsyncClient.java
new file mode 100644
index 00000000..4a8503f6
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttAsyncClient.java
@@ -0,0 +1,890 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ * Ian Craggs - MQTT 3.1.1 support
+ */
+package org.eclipse.paho.client.mqttv3;
+
+import java.util.Hashtable;
+import java.util.Properties;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLSocketFactory;
+
+import org.eclipse.paho.client.mqttv3.internal.ClientComms;
+import org.eclipse.paho.client.mqttv3.internal.ConnectActionListener;
+import org.eclipse.paho.client.mqttv3.internal.ExceptionHelper;
+import org.eclipse.paho.client.mqttv3.internal.LocalNetworkModule;
+import org.eclipse.paho.client.mqttv3.internal.NetworkModule;
+import org.eclipse.paho.client.mqttv3.internal.SSLNetworkModule;
+import org.eclipse.paho.client.mqttv3.internal.TCPNetworkModule;
+import org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttDisconnect;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttPublish;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttSubscribe;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttUnsubscribe;
+import org.eclipse.paho.client.mqttv3.logging.Logger;
+import org.eclipse.paho.client.mqttv3.logging.LoggerFactory;
+import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
+import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence;
+import org.eclipse.paho.client.mqttv3.util.Debug;
+
+/**
+ * Lightweight client for talking to an MQTT server using non-blocking methods
+ * that allow an operation to run in the background.
+ *
+ *
This class implements the non-blocking {@link IMqttAsyncClient} client interface
+ * allowing applications to initiate MQTT actions and then carry on working while the
+ * MQTT action completes on a background thread.
+ * This implementation is compatible with all Java SE runtimes from 1.4.2 and up.
+ *
+ *
An application can connect to an MQTT server using:
+ *
+ *
A plain TCP socket
+ *
A secure SSL/TLS socket
+ *
+ *
+ *
To enable messages to be delivered even across network and client restarts
+ * messages need to be safely stored until the message has been delivered at the requested
+ * quality of service. A pluggable persistence mechanism is provided to store the messages.
+ *
+ *
By default {@link MqttDefaultFilePersistence} is used to store messages to a file.
+ * If persistence is set to null then messages are stored in memory and hence can be lost
+ * if the client, Java runtime or device shuts down.
+ *
+ *
If connecting with {@link MqttConnectOptions#setCleanSession(boolean)} set to true it
+ * is safe to use memory persistence as all state is cleared when a client disconnects. If
+ * connecting with cleanSession set to false in order to provide reliable message delivery
+ * then a persistent message store such as the default one should be used.
+ *
+ *
The message store interface is pluggable. Different stores can be used by implementing
+ * the {@link MqttClientPersistence} interface and passing it to the clients constructor.
+ *
+ *
+ * @see IMqttAsyncClient
+ */
+public class MqttAsyncClient implements IMqttAsyncClient { // DestinationProvider {
+ private static final String CLASS_NAME = MqttAsyncClient.class.getName();
+ private static final Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT,CLASS_NAME);
+
+ private static final String CLIENT_ID_PREFIX = "paho";
+ private static final long QUIESCE_TIMEOUT = 30000; // ms
+ private static final long DISCONNECT_TIMEOUT = 10000; // ms
+ private static final char MIN_HIGH_SURROGATE = '\uD800';
+ private static final char MAX_HIGH_SURROGATE = '\uDBFF';
+ private String clientId;
+ private String serverURI;
+ protected ClientComms comms;
+ private Hashtable topics;
+ private MqttClientPersistence persistence;
+
+ /**
+ * Create an MqttAsyncClient that is used to communicate with an MQTT server.
+ *
+ * The address of a server can be specified on the constructor. Alternatively
+ * a list containing one or more servers can be specified using the
+ * {@link MqttConnectOptions#setServerURIs(String[]) setServerURIs} method
+ * on MqttConnectOptions.
+ *
+ *
The serverURI parameter is typically used with the
+ * the clientId parameter to form a key. The key
+ * is used to store and reference messages while they are being delivered.
+ * Hence the serverURI specified on the constructor must still be specified even if a list
+ * of servers is specified on an MqttConnectOptions object.
+ * The serverURI on the constructor must remain the same across
+ * restarts of the client for delivery of messages to be maintained from a given
+ * client to a given server or set of servers.
+ *
+ *
The address of the server to connect to is specified as a URI. Two types of
+ * connection are supported tcp:// for a TCP connection and
+ * ssl:// for a TCP connection secured by SSL/TLS.
+ * For example:
+ *
+ *
tcp://localhost:1883
+ *
ssl://localhost:8883
+ *
+ * If the port is not specified, it will
+ * default to 1883 for tcp://" URIs, and 8883 for ssl:// URIs.
+ *
+ *
+ *
+ * A client identifier clientId must be specified and be less that 65535 characters.
+ * It must be unique across all clients connecting to the same
+ * server. The clientId is used by the server to store data related to the client,
+ * hence it is important that the clientId remain the same when connecting to a server
+ * if durable subscriptions or reliable messaging are required.
+ *
A convenience method is provided to generate a random client id that
+ * should satisfy this criteria - {@link #generateClientId()}. As the client identifier
+ * is used by the server to identify a client when it reconnects, the client must use the
+ * same identifier between connections if durable subscriptions or reliable
+ * delivery of messages is required.
+ *
+ *
+ * In Java SE, SSL can be configured in one of several ways, which the
+ * client will use in the following order:
+ *
+ *
+ *
Supplying an SSLSocketFactory - applications can
+ * use {@link MqttConnectOptions#setSocketFactory(SocketFactory)} to supply
+ * a factory with the appropriate SSL settings.
+ *
SSL Properties - applications can supply SSL settings as a
+ * simple Java Properties using {@link MqttConnectOptions#setSSLProperties(Properties)}.
+ *
Use JVM settings - There are a number of standard
+ * Java system properties that can be used to configure key and trust stores.
+ *
+ *
+ *
In Java ME, the platform settings are used for SSL connections.
+ *
+ *
An instance of the default persistence mechanism {@link MqttDefaultFilePersistence}
+ * is used by the client. To specify a different persistence mechanism or to turn
+ * off persistence, use the {@link #MqttAsyncClient(String, String, MqttClientPersistence)}
+ * constructor.
+ *
+ * @param serverURI the address of the server to connect to, specified as a URI. Can be overridden using
+ * {@link MqttConnectOptions#setServerURIs(String[])}
+ * @param clientId a client identifier that is unique on the server being connected to
+ * @throws IllegalArgumentException if the URI does not start with
+ * "tcp://", "ssl://" or "local://".
+ * @throws IllegalArgumentException if the clientId is null or is greater than 65535 characters in length
+ * @throws MqttException if any other problem was encountered
+ */
+ public MqttAsyncClient(String serverURI, String clientId) throws MqttException {
+ this(serverURI,clientId, new MqttDefaultFilePersistence());
+ }
+
+ public MqttAsyncClient(String serverURI, String clientId, MqttClientPersistence persistence) throws MqttException {
+ this(serverURI,clientId, persistence, new TimerPingSender());
+ }
+
+ /**
+ * Create an MqttAsyncClient that is used to communicate with an MQTT server.
+ *
+ * The address of a server can be specified on the constructor. Alternatively
+ * a list containing one or more servers can be specified using the
+ * {@link MqttConnectOptions#setServerURIs(String[]) setServerURIs} method
+ * on MqttConnectOptions.
+ *
+ *
The serverURI parameter is typically used with the
+ * the clientId parameter to form a key. The key
+ * is used to store and reference messages while they are being delivered.
+ * Hence the serverURI specified on the constructor must still be specified even if a list
+ * of servers is specified on an MqttConnectOptions object.
+ * The serverURI on the constructor must remain the same across
+ * restarts of the client for delivery of messages to be maintained from a given
+ * client to a given server or set of servers.
+ *
+ *
The address of the server to connect to is specified as a URI. Two types of
+ * connection are supported tcp:// for a TCP connection and
+ * ssl:// for a TCP connection secured by SSL/TLS.
+ * For example:
+ *
+ *
tcp://localhost:1883
+ *
ssl://localhost:8883
+ *
+ * If the port is not specified, it will
+ * default to 1883 for tcp://" URIs, and 8883 for ssl:// URIs.
+ *
+ *
+ *
+ * A client identifier clientId must be specified and be less that 65535 characters.
+ * It must be unique across all clients connecting to the same
+ * server. The clientId is used by the server to store data related to the client,
+ * hence it is important that the clientId remain the same when connecting to a server
+ * if durable subscriptions or reliable messaging are required.
+ *
A convenience method is provided to generate a random client id that
+ * should satisfy this criteria - {@link #generateClientId()}. As the client identifier
+ * is used by the server to identify a client when it reconnects, the client must use the
+ * same identifier between connections if durable subscriptions or reliable
+ * delivery of messages is required.
+ *
+ *
+ * In Java SE, SSL can be configured in one of several ways, which the
+ * client will use in the following order:
+ *
+ *
+ *
Supplying an SSLSocketFactory - applications can
+ * use {@link MqttConnectOptions#setSocketFactory(SocketFactory)} to supply
+ * a factory with the appropriate SSL settings.
+ *
SSL Properties - applications can supply SSL settings as a
+ * simple Java Properties using {@link MqttConnectOptions#setSSLProperties(Properties)}.
+ *
Use JVM settings - There are a number of standard
+ * Java system properties that can be used to configure key and trust stores.
+ *
+ *
+ *
In Java ME, the platform settings are used for SSL connections.
+ *
+ * A persistence mechanism is used to enable reliable messaging.
+ * For messages sent at qualities of service (QoS) 1 or 2 to be reliably delivered,
+ * messages must be stored (on both the client and server) until the delivery of the message
+ * is complete. If messages are not safely stored when being delivered then
+ * a failure in the client or server can result in lost messages. A pluggable
+ * persistence mechanism is supported via the {@link MqttClientPersistence}
+ * interface. An implementer of this interface that safely stores messages
+ * must be specified in order for delivery of messages to be reliable. In
+ * addition {@link MqttConnectOptions#setCleanSession(boolean)} must be set
+ * to false. In the event that only QoS 0 messages are sent or received or
+ * cleanSession is set to true then a safe store is not needed.
+ *
+ *
An implementation of file-based persistence is provided in
+ * class {@link MqttDefaultFilePersistence} which will work in all Java SE based
+ * systems. If no persistence is needed, the persistence parameter
+ * can be explicitly set to null.
+ *
+ * @param serverURI the address of the server to connect to, specified as a URI. Can be overridden using
+ * {@link MqttConnectOptions#setServerURIs(String[])}
+ * @param clientId a client identifier that is unique on the server being connected to
+ * @param persistence the persistence class to use to store in-flight message. If null then the
+ * default persistence mechanism is used
+ * @throws IllegalArgumentException if the URI does not start with
+ * "tcp://", "ssl://" or "local://"
+ * @throws IllegalArgumentException if the clientId is null or is greater than 65535 characters in length
+ * @throws MqttException if any other problem was encountered
+ */
+ public MqttAsyncClient(String serverURI, String clientId, MqttClientPersistence persistence, MqttPingSender pingSender) throws MqttException {
+ final String methodName = "MqttAsyncClient";
+
+ log.setResourceName(clientId);
+
+ if (clientId == null) { //Support empty client Id, 3.1.1 standard
+ throw new IllegalArgumentException("Null clientId");
+ }
+ // Count characters, surrogate pairs count as one character.
+ int clientIdLength = 0;
+ for (int i = 0; i < clientId.length() - 1; i++) {
+ if (Character_isHighSurrogate(clientId.charAt(i)))
+ i++;
+ clientIdLength++;
+ }
+ if ( clientIdLength > 65535) {
+ throw new IllegalArgumentException("ClientId longer than 65535 characters");
+ }
+
+ MqttConnectOptions.validateURI(serverURI);
+
+ this.serverURI = serverURI;
+ this.clientId = clientId;
+
+ this.persistence = persistence;
+ if (this.persistence == null) {
+ this.persistence = new MemoryPersistence();
+ }
+
+ // @TRACE 101= ClientID={0} ServerURI={1} PersistenceType={2}
+ log.fine(CLASS_NAME,methodName,"101",new Object[]{clientId,serverURI,persistence});
+
+ this.persistence.open(clientId, serverURI);
+ this.comms = new ClientComms(this, this.persistence, pingSender);
+ this.persistence.close();
+ this.topics = new Hashtable();
+
+ }
+
+
+
+ /**
+ * @param ch
+ * @return returns 'true' if the character is a high-surrogate code unit
+ */
+ protected static boolean Character_isHighSurrogate(char ch) {
+ return(ch >= MIN_HIGH_SURROGATE) && (ch <= MAX_HIGH_SURROGATE);
+ }
+
+ /**
+ * Factory method to create an array of network modules, one for
+ * each of the supplied URIs
+ *
+ * @param address the URI for the server.
+ * @return a network module appropriate to the specified address.
+ */
+
+ // may need an array of these network modules
+
+ protected NetworkModule[] createNetworkModules(String address, MqttConnectOptions options) throws MqttException, MqttSecurityException {
+ final String methodName = "createNetworkModules";
+ // @TRACE 116=URI={0}
+ log.fine(CLASS_NAME, methodName, "116", new Object[]{address});
+
+ NetworkModule[] networkModules = null;
+ String[] serverURIs = options.getServerURIs();
+ String[] array = null;
+ if (serverURIs == null) {
+ array = new String[]{address};
+ } else if (serverURIs.length == 0) {
+ array = new String[]{address};
+ } else {
+ array = serverURIs;
+ }
+
+ networkModules = new NetworkModule[array.length];
+ for (int i = 0; i < array.length; i++) {
+ networkModules[i] = createNetworkModule(array[i], options);
+ }
+
+ log.fine(CLASS_NAME, methodName, "108");
+ return networkModules;
+ }
+
+ /**
+ * Factory method to create the correct network module, based on the
+ * supplied address URI.
+ *
+ * @param address the URI for the server.
+ * @param Connect options
+ * @return a network module appropriate to the specified address.
+ */
+ private NetworkModule createNetworkModule(String address, MqttConnectOptions options) throws MqttException, MqttSecurityException {
+ final String methodName = "createNetworkModule";
+ // @TRACE 115=URI={0}
+ log.fine(CLASS_NAME,methodName, "115", new Object[] {address});
+
+ NetworkModule netModule;
+ String shortAddress;
+ String host;
+ int port;
+ SocketFactory factory = options.getSocketFactory();
+
+ int serverURIType = MqttConnectOptions.validateURI(address);
+
+ switch (serverURIType) {
+ case MqttConnectOptions.URI_TYPE_TCP :
+ shortAddress = address.substring(6);
+ host = getHostName(shortAddress);
+ port = getPort(shortAddress, 1883);
+ if (factory == null) {
+ factory = SocketFactory.getDefault();
+ }
+ else if (factory instanceof SSLSocketFactory) {
+ throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_SOCKET_FACTORY_MISMATCH);
+ }
+ netModule = new TCPNetworkModule(factory, host, port, clientId);
+ ((TCPNetworkModule)netModule).setConnectTimeout(options.getConnectionTimeout());
+ break;
+ case MqttConnectOptions.URI_TYPE_SSL:
+ shortAddress = address.substring(6);
+ host = getHostName(shortAddress);
+ port = getPort(shortAddress, 8883);
+ SSLSocketFactoryFactory factoryFactory = null;
+ if (factory == null) {
+// try {
+ factoryFactory = new SSLSocketFactoryFactory();
+ Properties sslClientProps = options.getSSLProperties();
+ if (null != sslClientProps)
+ factoryFactory.initialize(sslClientProps, null);
+ factory = factoryFactory.createSocketFactory(null);
+// }
+// catch (MqttDirectException ex) {
+// throw ExceptionHelper.createMqttException(ex.getCause());
+// }
+ }
+ else if ((factory instanceof SSLSocketFactory) == false) {
+ throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_SOCKET_FACTORY_MISMATCH);
+ }
+
+ // Create the network module...
+ netModule = new SSLNetworkModule((SSLSocketFactory) factory, host, port, clientId);
+ ((SSLNetworkModule)netModule).setSSLhandshakeTimeout(options.getConnectionTimeout());
+ // Ciphers suites need to be set, if they are available
+ if (factoryFactory != null) {
+ String[] enabledCiphers = factoryFactory.getEnabledCipherSuites(null);
+ if (enabledCiphers != null) {
+ ((SSLNetworkModule) netModule).setEnabledCiphers(enabledCiphers);
+ }
+ }
+ break;
+ case MqttConnectOptions.URI_TYPE_LOCAL :
+ netModule = new LocalNetworkModule(address.substring(8));
+ break;
+ default:
+ // This shouldn't happen, as long as validateURI() has been called.
+ netModule = null;
+ }
+ return netModule;
+ }
+
+ private int getPort(String uri, int defaultPort) {
+ int port;
+ int portIndex = uri.lastIndexOf(':');
+ if (portIndex == -1) {
+ port = defaultPort;
+ }
+ else {
+ port = Integer.parseInt(uri.substring(portIndex + 1));
+ }
+ return port;
+ }
+
+ private String getHostName(String uri) {
+ int schemeIndex = uri.lastIndexOf('/');
+ int portIndex = uri.lastIndexOf(':');
+ if (portIndex == -1) {
+ portIndex = uri.length();
+ }
+ return uri.substring(schemeIndex + 1, portIndex);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#connect(java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
+ */
+ public IMqttToken connect(Object userContext, IMqttActionListener callback)
+ throws MqttException, MqttSecurityException {
+ return this.connect(new MqttConnectOptions(), userContext, callback);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#connect()
+ */
+ public IMqttToken connect() throws MqttException, MqttSecurityException {
+ return this.connect(null, null);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#connect(org.eclipse.paho.client.mqttv3.MqttConnectOptions)
+ */
+ public IMqttToken connect(MqttConnectOptions options) throws MqttException, MqttSecurityException {
+ return this.connect(options, null,null);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#connect(org.eclipse.paho.client.mqttv3.MqttConnectOptions, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
+ */
+ public IMqttToken connect(MqttConnectOptions options, Object userContext, IMqttActionListener callback)
+ throws MqttException, MqttSecurityException {
+ final String methodName = "connect";
+ if (comms.isConnected()) {
+ throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_CLIENT_CONNECTED);
+ }
+ if (comms.isConnecting()) {
+ throw new MqttException(MqttException.REASON_CODE_CONNECT_IN_PROGRESS);
+ }
+ if (comms.isDisconnecting()) {
+ throw new MqttException(MqttException.REASON_CODE_CLIENT_DISCONNECTING);
+ }
+ if (comms.isClosed()) {
+ throw new MqttException(MqttException.REASON_CODE_CLIENT_CLOSED);
+ }
+
+ // @TRACE 103=cleanSession={0} connectionTimeout={1} TimekeepAlive={2} userName={3} password={4} will={5} userContext={6} callback={7}
+ log.fine(CLASS_NAME,methodName, "103",
+ new Object[]{
+ Boolean.valueOf(options.isCleanSession()),
+ new Integer(options.getConnectionTimeout()),
+ new Integer(options.getKeepAliveInterval()),
+ options.getUserName(),
+ ((null == options.getPassword())?"[null]":"[notnull]"),
+ ((null == options.getWillMessage())?"[null]":"[notnull]"),
+ userContext,
+ callback });
+ comms.setNetworkModules(createNetworkModules(serverURI, options));
+
+ // Insert our own callback to iterate through the URIs till the connect succeeds
+ MqttToken userToken = new MqttToken(getClientId());
+ ConnectActionListener connectActionListener = new ConnectActionListener(this, persistence, comms, options, userToken, userContext, callback);
+ userToken.setActionCallback(connectActionListener);
+ userToken.setUserContext(this);
+
+ comms.setNetworkModuleIndex(0);
+ connectActionListener.connect();
+
+ return userToken;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnect(java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
+ */
+ public IMqttToken disconnect( Object userContext, IMqttActionListener callback) throws MqttException {
+ return this.disconnect(QUIESCE_TIMEOUT, userContext, callback);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnect()
+ */
+ public IMqttToken disconnect() throws MqttException {
+ return this.disconnect(null, null);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnect(long)
+ */
+ public IMqttToken disconnect(long quiesceTimeout) throws MqttException {
+ return this.disconnect(quiesceTimeout, null, null);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnect(long, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
+ */
+ public IMqttToken disconnect(long quiesceTimeout, Object userContext, IMqttActionListener callback) throws MqttException {
+ final String methodName = "disconnect";
+ // @TRACE 104=> quiesceTimeout={0} userContext={1} callback={2}
+ log.fine(CLASS_NAME,methodName, "104",new Object[]{ new Long(quiesceTimeout), userContext, callback});
+
+ MqttToken token = new MqttToken(getClientId());
+ token.setActionCallback(callback);
+ token.setUserContext(userContext);
+
+ MqttDisconnect disconnect = new MqttDisconnect();
+ try {
+ comms.disconnect(disconnect, quiesceTimeout, token);
+ } catch (MqttException ex) {
+ //@TRACE 105=< exception
+ log.fine(CLASS_NAME,methodName,"105",null,ex);
+ throw ex;
+ }
+ //@TRACE 108=<
+ log.fine(CLASS_NAME,methodName,"108");
+
+ return token;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnectForcibly()
+ */
+ public void disconnectForcibly() throws MqttException {
+ disconnectForcibly(QUIESCE_TIMEOUT, DISCONNECT_TIMEOUT);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnectForcibly(long)
+ */
+ public void disconnectForcibly(long disconnectTimeout) throws MqttException {
+ disconnectForcibly(QUIESCE_TIMEOUT, disconnectTimeout);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnectForcibly(long, long)
+ */
+ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout) throws MqttException{
+ comms.disconnectForcibly(quiesceTimeout, disconnectTimeout);
+ }
+
+ /* (non-Javadoc)
+ * @see IMqttAsyncClient#isConnected()
+ */
+ public boolean isConnected() {
+ return comms.isConnected();
+ }
+
+ /* (non-Javadoc)
+ * @see IMqttAsyncClient#getClientId()
+ */
+ public String getClientId() {
+ return clientId;
+ }
+
+ /* (non-Javadoc)
+ * @see IMqttAsyncClient#getServerURI()
+ */
+ public String getServerURI() {
+ return serverURI;
+ }
+
+ /**
+ * Get a topic object which can be used to publish messages.
+ *
There are two alternative methods that should be used in preference to this one when publishing a message:
+ *
+ *
{@link MqttAsyncClient#publish(String, MqttMessage, MqttDeliveryToken)} to publish a message in a non-blocking manner or
+ *
{@link MqttClient#publishBlock(String, MqttMessage, MqttDeliveryToken)} to publish a message in a blocking manner
+ *
+ *
+ *
When you build an application,
+ * the design of the topic tree should take into account the following principles
+ * of topic name syntax and semantics:
+ *
+ *
+ *
A topic must be at least one character long.
+ *
Topic names are case sensitive. For example, ACCOUNTS and Accounts are
+ * two different topics.
+ *
Topic names can include the space character. For example, Accounts
+ * payable is a valid topic.
+ *
A leading "/" creates a distinct topic. For example, /finance is
+ * different from finance. /finance matches "+/+" and "/+", but
+ * not "+".
+ *
Do not include the null character (Unicode \x0000) in
+ * any topic.
+ *
+ *
+ *
The following principles apply to the construction and content of a topic
+ * tree:
+ *
+ *
+ *
The length is limited to 64k but within that there are no limits to the
+ * number of levels in a topic tree.
+ *
There can be any number of root nodes; that is, there can be any number
+ * of topic trees.
+ *
+ *
+ *
+ * @param topic the topic to use, for example "finance/stock/ibm".
+ * @return an MqttTopic object, which can be used to publish messages to
+ * the topic.
+ * @throws IllegalArgumentException if the topic contains a '+' or '#'
+ * wildcard character.
+ */
+ protected MqttTopic getTopic(String topic) {
+ MqttTopic.validate(topic, false/*wildcards NOT allowed*/);
+
+ MqttTopic result = (MqttTopic)topics.get(topic);
+ if (result == null) {
+ result = new MqttTopic(topic, comms);
+ topics.put(topic,result);
+ }
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * Check and send a ping if needed.
+ *
By default, client sends PingReq to server to keep the connection to
+ * server. For some platforms which cannot use this mechanism, such as Android,
+ * developer needs to handle the ping request manually with this method.
+ *
When cleanSession is set to false, an application must ensure it uses the
+ * same client identifier when it reconnects to the server to resume state and maintain
+ * assured message delivery.
+ * @return a generated client identifier
+ * @see MqttConnectOptions#setCleanSession(boolean)
+ */
+ public static String generateClientId() {
+ //length of nanoTime = 15, so total length = 19 < 65535(defined in spec)
+ return CLIENT_ID_PREFIX + System.nanoTime();
+ }
+
+ /* (non-Javadoc)
+ * @see IMqttAsyncClient#getPendingDeliveryTokens()
+ */
+ public IMqttDeliveryToken[] getPendingDeliveryTokens() {
+ return comms.getPendingDeliveryTokens();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, byte[], int, boolean, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
+ */
+ public IMqttDeliveryToken publish(String topic, byte[] payload, int qos,
+ boolean retained, Object userContext, IMqttActionListener callback) throws MqttException,
+ MqttPersistenceException {
+ MqttMessage message = new MqttMessage(payload);
+ message.setQos(qos);
+ message.setRetained(retained);
+ return this.publish(topic, message, userContext, callback);
+ }
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, byte[], int, boolean)
+ */
+ public IMqttDeliveryToken publish(String topic, byte[] payload, int qos,
+ boolean retained) throws MqttException, MqttPersistenceException {
+ return this.publish(topic, payload, qos, retained, null, null);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, org.eclipse.paho.client.mqttv3.MqttMessage)
+ */
+ public IMqttDeliveryToken publish(String topic, MqttMessage message) throws MqttException, MqttPersistenceException {
+ return this.publish(topic, message, null, null);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, org.eclipse.paho.client.mqttv3.MqttMessage, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
+ */
+ public IMqttDeliveryToken publish(String topic, MqttMessage message, Object userContext, IMqttActionListener callback) throws MqttException,
+ MqttPersistenceException {
+ final String methodName = "publish";
+ //@TRACE 111=< topic={0} message={1}userContext={1} callback={2}
+ log.fine(CLASS_NAME,methodName,"111", new Object[] {topic, userContext, callback});
+
+ //Checks if a topic is valid when publishing a message.
+ MqttTopic.validate(topic, false/*wildcards NOT allowed*/);
+
+ MqttDeliveryToken token = new MqttDeliveryToken(getClientId());
+ token.setActionCallback(callback);
+ token.setUserContext(userContext);
+ token.setMessage(message);
+ token.internalTok.setTopics(new String[] {topic});
+
+ MqttPublish pubMsg = new MqttPublish(topic, message);
+ comms.sendNoWait(pubMsg, token);
+
+ //@TRACE 112=<
+ log.fine(CLASS_NAME,methodName,"112");
+
+ return token;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#close()
+ */
+ public void close() throws MqttException {
+ final String methodName = "close";
+ //@TRACE 113=<
+ log.fine(CLASS_NAME,methodName,"113");
+ comms.close();
+ //@TRACE 114=>
+ log.fine(CLASS_NAME,methodName,"114");
+
+ }
+
+ /**
+ * Return a debug object that can be used to help solve problems.
+ */
+ public Debug getDebug() {
+ return new Debug(clientId,comms);
+ }
+
+ public void recycleConnection() {
+ comms.shutdownConnection(null, new MqttException(MqttException.REASON_CODE_CONNECTION_LOST,
+ new Exception("Recycle connection karena kirim message tidak sampai2")));
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttCallback.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttCallback.java
similarity index 55%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttCallback.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttCallback.java
index 2bdb418e..21d9c107 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttCallback.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttCallback.java
@@ -1,71 +1,79 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3;
-
-
-/**
- * Asynchronous message listener. Classes implementing this interface
- * can be passed to {@link MqttClient#setCallback(MqttCallback)},
- * which will create a call back on this interface.
- */
-public interface MqttCallback {
- /**
- * This method is called when the connection to the server is lost.
- *
- * @param cause the reason behind the loss of connection.
- */
- public void connectionLost(Throwable cause);
-
- /**
- * This method is called when a message arrives from the server.
- *
- *
- * This method is invoked synchronously by the MQTT client. An
- * acknowledgment on the network is not sent back to the server until this
- * method returns cleanly.
- *
- * If an implementation of this method throws an Exception, then the
- * client will be shut down. When the client is next re-connected, any QoS
- * 1 or 2 messages will be redelivered.
- *
- * Any additional messages which arrive while an
- * implementation of this method is running, will build up in memory, and
- * will then back up on the network.
- *
- * If an application needs to persist data, then it
- * should ensure the data is persisted prior to returning from this method, as
- * after returning from this method, the message is considered to have been
- * delivered, and will not be reproducable.
- *
- * It is possible to send a new message within an implementation of this callback
- * (for example, a response to this message), but the implementation must not
- * disconnect the client, as it will be impossible to send an acknowledgement for
- * the message being processed, and a deadlock will occur.
- *
- * @param topic the topic on which the message arrived.
- * @param message the actual message.
- * @throws Exception if a terminal error has occurred, and the client should be
- * shut down.
- */
- public void messageArrived(MqttTopic topic, MqttMessage message) throws Exception;
-
- /**
- * Called when delivery for a message has been completed, and all
- * acknowledgements have been received. The supplied token will be same
- * token which was returned when the message was first sent, if it was
- * sent using {@link MqttTopic#publish(MqttMessage)}.
- *
- * @param token the delivery token associated with the message.
- */
- public void deliveryComplete(MqttDeliveryToken token);
-
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3;
+
+
+/**
+ * Enables an application to be notified when asynchronous
+ * events related to the client occur.
+ * Classes implementing this interface
+ * can be registered on both types of client: {@link IMqttClient#setCallback(MqttCallback)}
+ * and {@link IMqttAsyncClient#setCallback(MqttCallback)}
+ */
+public interface MqttCallback {
+ /**
+ * This method is called when the connection to the server is lost.
+ *
+ * @param cause the reason behind the loss of connection.
+ */
+ public void connectionLost(Throwable cause);
+
+ /**
+ * This method is called when a message arrives from the server.
+ *
+ *
+ * This method is invoked synchronously by the MQTT client. An
+ * acknowledgment is not sent back to the server until this
+ * method returns cleanly.
+ *
+ * If an implementation of this method throws an Exception, then the
+ * client will be shut down. When the client is next re-connected, any QoS
+ * 1 or 2 messages will be redelivered by the server.
+ *
+ * Any additional messages which arrive while an
+ * implementation of this method is running, will build up in memory, and
+ * will then back up on the network.
+ *
+ * If an application needs to persist data, then it
+ * should ensure the data is persisted prior to returning from this method, as
+ * after returning from this method, the message is considered to have been
+ * delivered, and will not be reproducible.
+ *
+ * It is possible to send a new message within an implementation of this callback
+ * (for example, a response to this message), but the implementation must not
+ * disconnect the client, as it will be impossible to send an acknowledgment for
+ * the message being processed, and a deadlock will occur.
+ *
+ * @param topic name of the topic on the message was published to
+ * @param message the actual message.
+ * @throws Exception if a terminal error has occurred, and the client should be
+ * shut down.
+ */
+ public void messageArrived(String topic, MqttMessage message) throws Exception;
+
+ /**
+ * Called when delivery for a message has been completed, and all
+ * acknowledgments have been received. For QoS 0 messages it is
+ * called once the message has been handed to the network for
+ * delivery. For QoS 1 it is called when PUBACK is received and
+ * for QoS 2 when PUBCOMP is received. The token will be the same
+ * token as that returned when the message was published.
+ *
+ * @param token the delivery token associated with the message.
+ */
+ public void deliveryComplete(IMqttDeliveryToken token);
+
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttClient.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttClient.java
new file mode 100644
index 00000000..f33123af
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttClient.java
@@ -0,0 +1,466 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ * Ian Craggs - MQTT 3.1.1 support
+ */
+package org.eclipse.paho.client.mqttv3;
+
+import java.util.Properties;
+
+import javax.net.SocketFactory;
+
+import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence;
+import org.eclipse.paho.client.mqttv3.util.Debug;
+
+/**
+ * Lightweight client for talking to an MQTT server using methods that block
+ * until an operation completes.
+ *
+ *
This class implements the blocking {@link IMqttClient} client interface where all
+ * actions block until they have completed (or timed out).
+ * This implementation is compatible with all Java SE runtimes from 1.4.2 and up.
+ *
+ *
An application can connect to an MQTT server using:
+ *
+ *
A plain TCP socket
+ *
An secure SSL/TLS socket
+ *
+ *
+ *
To enable messages to be delivered even across network and client restarts
+ * messages need to be safely stored until the message has been delivered at the requested
+ * quality of service. A pluggable persistence mechanism is provided to store the messages.
+ *
+ *
By default {@link MqttDefaultFilePersistence} is used to store messages to a file.
+ * If persistence is set to null then messages are stored in memory and hence can be lost
+ * if the client, Java runtime or device shuts down.
+ *
+ *
If connecting with {@link MqttConnectOptions#setCleanSession(boolean)} set to true it
+ * is safe to use memory persistence as all state it cleared when a client disconnects. If
+ * connecting with cleanSession set to false, to provide reliable message delivery
+ * then a persistent message store should be used such as the default one.
+ *
The message store interface is pluggable. Different stores can be used by implementing
+ * the {@link MqttClientPersistence} interface and passing it to the clients constructor.
+ *
+ *
+ * @see IMqttClient
+ */
+public class MqttClient implements IMqttClient { //), DestinationProvider {
+ //private static final String CLASS_NAME = MqttClient.class.getName();
+ //private static final Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT,CLASS_NAME);
+
+ protected MqttAsyncClient aClient = null; // Delegate implementation to MqttAsyncClient
+ protected long timeToWait = -1; // How long each method should wait for action to complete
+
+ /**
+ * Create an MqttClient that can be used to communicate with an MQTT server.
+ *
+ * The address of a server can be specified on the constructor. Alternatively
+ * a list containing one or more servers can be specified using the
+ * {@link MqttConnectOptions#setServerURIs(String[]) setServerURIs} method
+ * on MqttConnectOptions.
+ *
+ *
The serverURI parameter is typically used with the
+ * the clientId parameter to form a key. The key
+ * is used to store and reference messages while they are being delivered.
+ * Hence the serverURI specified on the constructor must still be specified even if a list
+ * of servers is specified on an MqttConnectOptions object.
+ * The serverURI on the constructor must remain the same across
+ * restarts of the client for delivery of messages to be maintained from a given
+ * client to a given server or set of servers.
+ *
+ *
The address of the server to connect to is specified as a URI. Two types of
+ * connection are supported tcp:// for a TCP connection and
+ * ssl:// for a TCP connection secured by SSL/TLS.
+ * For example:
+ *
+ *
tcp://localhost:1883
+ *
ssl://localhost:8883
+ *
+ * If the port is not specified, it will
+ * default to 1883 for tcp://" URIs, and 8883 for ssl:// URIs.
+ *
+ *
+ *
+ * A client identifier clientId must be specified and be less that 65535 characters.
+ * It must be unique across all clients connecting to the same
+ * server. The clientId is used by the server to store data related to the client,
+ * hence it is important that the clientId remain the same when connecting to a server
+ * if durable subscriptions or reliable messaging are required.
+ *
A convenience method is provided to generate a random client id that
+ * should satisfy this criteria - {@link #generateClientId()}. As the client identifier
+ * is used by the server to identify a client when it reconnects, the client must use the
+ * same identifier between connections if durable subscriptions or reliable
+ * delivery of messages is required.
+ *
+ *
+ * In Java SE, SSL can be configured in one of several ways, which the
+ * client will use in the following order:
+ *
+ *
+ *
Supplying an SSLSocketFactory - applications can
+ * use {@link MqttConnectOptions#setSocketFactory(SocketFactory)} to supply
+ * a factory with the appropriate SSL settings.
+ *
SSL Properties - applications can supply SSL settings as a
+ * simple Java Properties using {@link MqttConnectOptions#setSSLProperties(Properties)}.
+ *
Use JVM settings - There are a number of standard
+ * Java system properties that can be used to configure key and trust stores.
+ *
+ *
+ *
In Java ME, the platform settings are used for SSL connections.
+ *
+ *
An instance of the default persistence mechanism {@link MqttDefaultFilePersistence}
+ * is used by the client. To specify a different persistence mechanism or to turn
+ * off persistence, use the {@link #MqttClient(String, String, MqttClientPersistence)}
+ * constructor.
+ *
+ * @param serverURI the address of the server to connect to, specified as a URI. Can be overridden using
+ * {@link MqttConnectOptions#setServerURIs(String[])}
+ * @param clientId a client identifier that is unique on the server being connected to
+ * @throws IllegalArgumentException if the URI does not start with
+ * "tcp://", "ssl://" or "local://".
+ * @throws IllegalArgumentException if the clientId is null or is greater than 65535 characters in length
+ * @throws MqttException if any other problem was encountered
+ */
+ public MqttClient(String serverURI, String clientId) throws MqttException {
+ this(serverURI,clientId, new MqttDefaultFilePersistence());
+ }
+
+ /**
+ * Create an MqttClient that can be used to communicate with an MQTT server.
+ *
+ * The address of a server can be specified on the constructor. Alternatively
+ * a list containing one or more servers can be specified using the
+ * {@link MqttConnectOptions#setServerURIs(String[]) setServerURIs} method
+ * on MqttConnectOptions.
+ *
+ *
The serverURI parameter is typically used with the
+ * the clientId parameter to form a key. The key
+ * is used to store and reference messages while they are being delivered.
+ * Hence the serverURI specified on the constructor must still be specified even if a list
+ * of servers is specified on an MqttConnectOptions object.
+ * The serverURI on the constructor must remain the same across
+ * restarts of the client for delivery of messages to be maintained from a given
+ * client to a given server or set of servers.
+ *
+ *
The address of the server to connect to is specified as a URI. Two types of
+ * connection are supported tcp:// for a TCP connection and
+ * ssl:// for a TCP connection secured by SSL/TLS.
+ * For example:
+ *
+ *
tcp://localhost:1883
+ *
ssl://localhost:8883
+ *
+ * If the port is not specified, it will
+ * default to 1883 for tcp://" URIs, and 8883 for ssl:// URIs.
+ *
+ *
+ *
+ * A client identifier clientId must be specified and be less that 65535 characters.
+ * It must be unique across all clients connecting to the same
+ * server. The clientId is used by the server to store data related to the client,
+ * hence it is important that the clientId remain the same when connecting to a server
+ * if durable subscriptions or reliable messaging are required.
+ *
A convenience method is provided to generate a random client id that
+ * should satisfy this criteria - {@link #generateClientId()}. As the client identifier
+ * is used by the server to identify a client when it reconnects, the client must use the
+ * same identifier between connections if durable subscriptions or reliable
+ * delivery of messages is required.
+ *
+ *
+ * In Java SE, SSL can be configured in one of several ways, which the
+ * client will use in the following order:
+ *
+ *
+ *
Supplying an SSLSocketFactory - applications can
+ * use {@link MqttConnectOptions#setSocketFactory(SocketFactory)} to supply
+ * a factory with the appropriate SSL settings.
+ *
SSL Properties - applications can supply SSL settings as a
+ * simple Java Properties using {@link MqttConnectOptions#setSSLProperties(Properties)}.
+ *
Use JVM settings - There are a number of standard
+ * Java system properties that can be used to configure key and trust stores.
+ *
+ *
+ *
In Java ME, the platform settings are used for SSL connections.
+ *
+ * A persistence mechanism is used to enable reliable messaging.
+ * For messages sent at qualities of service (QoS) 1 or 2 to be reliably delivered,
+ * messages must be stored (on both the client and server) until the delivery of the message
+ * is complete. If messages are not safely stored when being delivered then
+ * a failure in the client or server can result in lost messages. A pluggable
+ * persistence mechanism is supported via the {@link MqttClientPersistence}
+ * interface. An implementer of this interface that safely stores messages
+ * must be specified in order for delivery of messages to be reliable. In
+ * addition {@link MqttConnectOptions#setCleanSession(boolean)} must be set
+ * to false. In the event that only QoS 0 messages are sent or received or
+ * cleanSession is set to true then a safe store is not needed.
+ *
+ *
An implementation of file-based persistence is provided in
+ * class {@link MqttDefaultFilePersistence} which will work in all Java SE based
+ * systems. If no persistence is needed, the persistence parameter
+ * can be explicitly set to null.
+ *
+ * @param serverURI the address of the server to connect to, specified as a URI. Can be overridden using
+ * {@link MqttConnectOptions#setServerURIs(String[])}
+ * @param clientId a client identifier that is unique on the server being connected to
+ * @param persistence the persistence class to use to store in-flight message. If null then the
+ * default persistence mechanism is used
+ * @throws IllegalArgumentException if the URI does not start with
+ * "tcp://", "ssl://" or "local://"
+ * @throws IllegalArgumentException if the clientId is null or is greater than 65535 characters in length
+ * @throws MqttException if any other problem was encountered
+ */
+ public MqttClient(String serverURI, String clientId, MqttClientPersistence persistence) throws MqttException {
+ aClient = new MqttAsyncClient(serverURI, clientId, persistence);
+ }
+
+ /*
+ * @see IMqttClient#connect()
+ */
+ public void connect() throws MqttSecurityException, MqttException {
+ this.connect(new MqttConnectOptions());
+ }
+
+ /*
+ * @see IMqttClient#connect(MqttConnectOptions)
+ */
+ public void connect(MqttConnectOptions options) throws MqttSecurityException, MqttException {
+ aClient.connect(options, null, null).waitForCompletion(getTimeToWait());
+ }
+
+ /*
+ * @see IMqttClient#connect(MqttConnectOptions)
+ */
+ public IMqttToken connectWithResult(MqttConnectOptions options) throws MqttSecurityException, MqttException {
+ IMqttToken tok = aClient.connect(options, null, null);
+ tok.waitForCompletion(getTimeToWait());
+ return tok;
+ }
+
+ /*
+ * @see IMqttClient#disconnect()
+ */
+ public void disconnect() throws MqttException {
+ aClient.disconnect().waitForCompletion();
+ }
+
+ /*
+ * @see IMqttClient#disconnect(long)
+ */
+ public void disconnect(long quiesceTimeout) throws MqttException {
+ aClient.disconnect(quiesceTimeout, null, null).waitForCompletion();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnectForcibly()
+ */
+ public void disconnectForcibly() throws MqttException {
+ aClient.disconnectForcibly();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnectForcibly(long)
+ */
+ public void disconnectForcibly(long disconnectTimeout) throws MqttException {
+ aClient.disconnectForcibly(disconnectTimeout);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnectForcibly(long, long)
+ */
+ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout) throws MqttException {
+ aClient.disconnectForcibly(quiesceTimeout, disconnectTimeout);
+ }
+
+ /*
+ * @see IMqttClient#subscribe(String)
+ */
+ public void subscribe(String topicFilter) throws MqttException {
+ this.subscribe(new String[] {topicFilter}, new int[] {1});
+ }
+
+ /*
+ * @see IMqttClient#subscribe(String[])
+ */
+ public void subscribe(String[] topicFilters) throws MqttException {
+ int[] qos = new int[topicFilters.length];
+ for (int i=0; iSet the maximum time to wait for an action to complete before
+ * returning control to the invoking application. Control is returned
+ * when:
+ *
+ *
the action completes
+ *
or when the timeout if exceeded
+ *
or when the client is disconnect/shutdown
+ *
+ * The default value is -1 which means the action will not timeout.
+ * In the event of a timeout the action carries on running in the
+ * background until it completes. The timeout is used on methods that
+ * block while the action is in progress.
+ *
+ * @param timeToWaitInMillis before the action times out. A value or 0 or -1 will wait until
+ * the action finishes and not timeout.
+ */
+ public void setTimeToWait(long timeToWaitInMillis) throws IllegalArgumentException{
+ if (timeToWaitInMillis < -1) {
+ throw new IllegalArgumentException();
+ }
+ this.timeToWait = timeToWaitInMillis;
+ }
+
+ /**
+ * Return the maximum time to wait for an action to complete.
+ * @see MqttClient#setTimeToWait(long)
+ */
+ public long getTimeToWait() {
+ return this.timeToWait;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttClient#close()
+ */
+ public void close() throws MqttException {
+ aClient.close();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttClient#getClientId()
+ */
+ public String getClientId() {
+ return aClient.getClientId();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttClient#getPendingDeliveryTokens()
+ */
+ public IMqttDeliveryToken[] getPendingDeliveryTokens() {
+ return aClient.getPendingDeliveryTokens();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttClient#getServerURI()
+ */
+ public String getServerURI() {
+ return aClient.getServerURI();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttClient#getTopic(java.lang.String)
+ */
+ public MqttTopic getTopic(String topic) {
+ return aClient.getTopic(topic);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttClient#isConnected()
+ */
+ public boolean isConnected() {
+ return aClient.isConnected();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.IMqttClient#setCallback(org.eclipse.paho.client.mqttv3.MqttCallback)
+ */
+ public void setCallback(MqttCallback callback) {
+ aClient.setCallback(callback);
+ }
+
+ /**
+ * Returns a randomly generated client identifier based on the current user's login
+ * name and the system time.
+ *
When cleanSession is set to false, an application must ensure it uses the
+ * same client identifier when it reconnects to the server to resume state and maintain
+ * assured message delivery.
+ * @return a generated client identifier
+ * @see MqttConnectOptions#setCleanSession(boolean)
+ */
+ public static String generateClientId() {
+ return MqttAsyncClient.generateClientId();
+ }
+
+ /**
+ * Return a debug object that can be used to help solve problems.
+ */
+ public Debug getDebug() {
+ return (aClient.getDebug());
+ }
+
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttClientPersistence.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttClientPersistence.java
similarity index 83%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttClientPersistence.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttClientPersistence.java
index 1574bae3..5e75dd53 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttClientPersistence.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttClientPersistence.java
@@ -1,92 +1,96 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3;
-
-import java.util.Enumeration;
-
-/**
- * Represents a persistent data store, used to store outbound and inbound
- * messages, in order to achieve reliable messaging. You can specify an implementation
- * of this interface using {@link MqttConnectOptions#setPersistence(MqttClientPersistence)},
- * which the {@link MqttClient} will use to persist QoS 1 and 2 messages.
- *
- * If the methods defined throw the MqttPersistenceException then the state of the data persisted
- * should remain as prior to the method being called. For example, if {@link #put(String, MqttPersistable)}
- * throws an exception at any point then the data will be assumed to not be in the persistent store.
- * Similarly if {@link #remove(String)} throws an exception then the data will be
- * asssumed to still be held in the persistent store.
- *
- * It is up to the persistence interface to log any exceptions or error information
- * which may be required when diagnosing a persistence failure.
- */
-public interface MqttClientPersistence {
- /**
- * Initialise the persistent store.
- * If a persistent store exists for this client ID then open it, otherwise
- * create a new one. If the persistent store is already open then just return.
- * An application may use the same client ID to connect to many different
- * servers, so the client ID in conjunction with the
- * connection will uniquely identify the persistence store required.
- *
- * @param clientId The client for which the persistent store should be opened.
- * @param serverURI The connection string as specified when the MQTT client instance was created.
- * @throws MqttPersistenceException if there was a problem opening the persistent store.
- */
- public void open(String clientId, String serverURI) throws MqttPersistenceException;
-
- /**
- * Close the persistent store that was previously opened.
- * This will be called when a client application disconnects from the broker.
- * @throws MqttPersistenceException
- */
- public void close() throws MqttPersistenceException;
-
- /**
- * Puts the specified data into the persistent store.
- * @param key the key for the data, which will be used later to retrieve it.
- * @param persistable the data to persist
- * @throws MqttPersistenceException if there was a problem putting the data
- * into the persistent store.
- */
- public void put(String key, MqttPersistable persistable) throws MqttPersistenceException;
-
- /**
- * Gets the specified data out of the persistent store.
- * @param key the key for the data, which was used when originally saving it.
- * @return the un-persisted data
- * @throws MqttPersistenceException if there was a problem getting the data
- * from the persistent store.
- */
- public MqttPersistable get(String key) throws MqttPersistenceException;
-
- /**
- * Remove the data for the specified key.
- */
- public void remove(String key) throws MqttPersistenceException;
-
- /**
- * Returns an Enumeration over the keys in this persistent data store.
- * @return an enumeration of {@link String} objects.
- */
- public Enumeration keys() throws MqttPersistenceException;
-
- /**
- * Clears persistence, so that it no longer contains any persisted data.
- */
- public void clear() throws MqttPersistenceException;
-
- /**
- * Returns whether or not data is persisted using the specified key.
- * @param key the key for data, which was used when originally saving it.
- */
- public boolean containsKey(String key) throws MqttPersistenceException;
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3;
+
+import java.util.Enumeration;
+
+/**
+ * Represents a persistent data store, used to store outbound and inbound messages while they
+ * are in flight, enabling delivery to the QoS specified. You can specify an implementation
+ * of this interface using {@link MqttClient#MqttClient(String, String, MqttClientPersistence)},
+ * which the {@link MqttClient} will use to persist QoS 1 and 2 messages.
+ *
+ * If the methods defined throw the MqttPersistenceException then the state of the data persisted
+ * should remain as prior to the method being called. For example, if {@link #put(String, MqttPersistable)}
+ * throws an exception at any point then the data will be assumed to not be in the persistent store.
+ * Similarly if {@link #remove(String)} throws an exception then the data will be
+ * assumed to still be held in the persistent store.
+ *
+ * It is up to the persistence interface to log any exceptions or error information
+ * which may be required when diagnosing a persistence failure.
+ */
+public interface MqttClientPersistence {
+ /**
+ * Initialise the persistent store.
+ * If a persistent store exists for this client ID then open it, otherwise
+ * create a new one. If the persistent store is already open then just return.
+ * An application may use the same client ID to connect to many different
+ * servers, so the client ID in conjunction with the
+ * connection will uniquely identify the persistence store required.
+ *
+ * @param clientId The client for which the persistent store should be opened.
+ * @param serverURI The connection string as specified when the MQTT client instance was created.
+ * @throws MqttPersistenceException if there was a problem opening the persistent store.
+ */
+ public void open(String clientId, String serverURI) throws MqttPersistenceException;
+
+ /**
+ * Close the persistent store that was previously opened.
+ * This will be called when a client application disconnects from the broker.
+ * @throws MqttPersistenceException
+ */
+ public void close() throws MqttPersistenceException;
+
+ /**
+ * Puts the specified data into the persistent store.
+ * @param key the key for the data, which will be used later to retrieve it.
+ * @param persistable the data to persist
+ * @throws MqttPersistenceException if there was a problem putting the data
+ * into the persistent store.
+ */
+ public void put(String key, MqttPersistable persistable) throws MqttPersistenceException;
+
+ /**
+ * Gets the specified data out of the persistent store.
+ * @param key the key for the data, which was used when originally saving it.
+ * @return the un-persisted data
+ * @throws MqttPersistenceException if there was a problem getting the data
+ * from the persistent store.
+ */
+ public MqttPersistable get(String key) throws MqttPersistenceException;
+
+ /**
+ * Remove the data for the specified key.
+ */
+ public void remove(String key) throws MqttPersistenceException;
+
+ /**
+ * Returns an Enumeration over the keys in this persistent data store.
+ * @return an enumeration of {@link String} objects.
+ */
+ public Enumeration keys() throws MqttPersistenceException;
+
+ /**
+ * Clears persistence, so that it no longer contains any persisted data.
+ */
+ public void clear() throws MqttPersistenceException;
+
+ /**
+ * Returns whether or not data is persisted using the specified key.
+ * @param key the key for data, which was used when originally saving it.
+ */
+ public boolean containsKey(String key) throws MqttPersistenceException;
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttConnectOptions.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttConnectOptions.java
new file mode 100644
index 00000000..01589168
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttConnectOptions.java
@@ -0,0 +1,520 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ * Ian Craggs - MQTT 3.1.1 support
+ */
+package org.eclipse.paho.client.mqttv3;
+
+import java.util.Properties;
+
+import javax.net.SocketFactory;
+
+import org.eclipse.paho.client.mqttv3.util.Debug;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * Holds the set of options that control how the client connects to a server.
+ */
+public class MqttConnectOptions {
+ /**
+ * The default keep alive interval in seconds if one is not specified
+ */
+ public static final int KEEP_ALIVE_INTERVAL_DEFAULT = 60;
+ /**
+ * The default connection timeout in seconds if one is not specified
+ */
+ public static final int CONNECTION_TIMEOUT_DEFAULT = 30;
+ /**
+ * The default clean session setting if one is not specified
+ */
+ public static final boolean CLEAN_SESSION_DEFAULT = true;
+ /**
+ * The default MqttVersion is 3.1.1 first, dropping back to 3.1 if that fails
+ */
+ public static final int MQTT_VERSION_DEFAULT = 0;
+ public static final int MQTT_VERSION_3_1 = 3;
+ public static final int MQTT_VERSION_3_1_1 = 4;
+
+ protected static final int URI_TYPE_TCP = 0;
+ protected static final int URI_TYPE_SSL = 1;
+ protected static final int URI_TYPE_LOCAL = 2;
+
+ private int keepAliveInterval = KEEP_ALIVE_INTERVAL_DEFAULT;
+ private String willDestination = null;
+ private MqttMessage willMessage = null;
+ private String userName;
+ private char[] password;
+ private SocketFactory socketFactory;
+ private Properties sslClientProps = null;
+ private boolean cleanSession = CLEAN_SESSION_DEFAULT;
+ private int connectionTimeout = CONNECTION_TIMEOUT_DEFAULT;
+ private String[] serverURIs = null;
+ private int MqttVersion = MQTT_VERSION_DEFAULT;
+
+ /**
+ * Constructs a new MqttConnectOptions object using the
+ * default values.
+ *
+ * The defaults are:
+ *
+ *
The keepalive interval is 60 seconds
+ *
Clean Session is true
+ *
The message delivery retry interval is 15 seconds
+ *
The connection timeout period is 30 seconds
+ *
No Will message is set
+ *
A standard SocketFactory is used
+ *
+ * More information about these values can be found in the setter methods.
+ */
+ public MqttConnectOptions() {
+ }
+
+ /**
+ * Returns the password to use for the connection.
+ * @return the password to use for the connection.
+ */
+ public char[] getPassword() {
+ return password;
+ }
+
+ /**
+ * Sets the password to use for the connection.
+ */
+ public void setPassword(char[] password) {
+ this.password = password;
+ }
+
+ /**
+ * Returns the user name to use for the connection.
+ * @return the user name to use for the connection.
+ */
+ public String getUserName() {
+ return userName;
+ }
+
+ /**
+ * Sets the user name to use for the connection.
+ * @throws IllegalArgumentException if the user name is blank or only
+ * contains whitespace characters.
+ */
+ public void setUserName(String userName) {
+ if ((userName != null) && (userName.trim().equals(""))) {
+ throw new IllegalArgumentException();
+ }
+ this.userName = userName;
+ }
+
+ /**
+ * Sets the "Last Will and Testament" (LWT) for the connection.
+ * In the event that this client unexpectedly loses its connection to the
+ * server, the server will publish a message to itself using the supplied
+ * details.
+ *
+ * @param topic the topic to publish to.
+ * @param payload the byte payload for the message.
+ * @param qos the quality of service to publish the message at (0, 1 or 2).
+ * @param retained whether or not the message should be retained.
+ */
+ public void setWill(MqttTopic topic, byte[] payload, int qos, boolean retained) {
+ String topicS = topic.getName();
+ validateWill(topicS, payload);
+ this.setWill(topicS, new MqttMessage(payload), qos, retained);
+ }
+
+ /**
+ * Sets the "Last Will and Testament" (LWT) for the connection.
+ * In the event that this client unexpectedly loses its connection to the
+ * server, the server will publish a message to itself using the supplied
+ * details.
+ *
+ * @param topic the topic to publish to.
+ * @param payload the byte payload for the message.
+ * @param qos the quality of service to publish the message at (0, 1 or 2).
+ * @param retained whether or not the message should be retained.
+ */
+ public void setWill(String topic, byte[] payload, int qos, boolean retained) {
+ validateWill(topic, payload);
+ this.setWill(topic, new MqttMessage(payload), qos, retained);
+ }
+
+
+ /**
+ * Validates the will fields.
+ */
+ private void validateWill(String dest, Object payload) {
+ if ((dest == null) || (payload == null)) {
+ throw new IllegalArgumentException();
+ }
+
+ MqttTopic.validate(dest, false/*wildcards NOT allowed*/);
+ }
+
+ /**
+ * Sets up the will information, based on the supplied parameters.
+ */
+ protected void setWill(String topic, MqttMessage msg, int qos, boolean retained) {
+ willDestination = topic;
+ willMessage = msg;
+ willMessage.setQos(qos);
+ willMessage.setRetained(retained);
+ // Prevent any more changes to the will message
+ willMessage.setMutable(false);
+ }
+
+ /**
+ * Returns the "keep alive" interval.
+ * @see #setKeepAliveInterval(int)
+ * @return the keep alive interval.
+ */
+ public int getKeepAliveInterval() {
+ return keepAliveInterval;
+ }
+
+ /**
+ * Returns the MQTT version.
+ * @see #setMqttVersion(int)
+ * @return the MQTT version.
+ */
+ public int getMqttVersion() {
+ return MqttVersion;
+ }
+
+ /**
+ * Sets the "keep alive" interval.
+ * This value, measured in seconds, defines the maximum time interval
+ * between messages sent or received. It enables the client to
+ * detect if the server is no longer available, without
+ * having to wait for the TCP/IP timeout. The client will ensure
+ * that at least one message travels across the network within each
+ * keep alive period. In the absence of a data-related message during
+ * the time period, the client sends a very small "ping" message, which
+ * the server will acknowledge.
+ * A value of 0 disables keepalive processing in the client.
+ *
The default value is 60 seconds
+ *
+ * @param keepAliveInterval the interval, measured in seconds, must be >= 0.
+ */
+ public void setKeepAliveInterval(int keepAliveInterval)throws IllegalArgumentException {
+ if (keepAliveInterval <0 ) {
+ throw new IllegalArgumentException();
+ }
+ this.keepAliveInterval = keepAliveInterval;
+ }
+
+ /**
+ * Returns the connection timeout value.
+ * @see #setConnectionTimeout(int)
+ * @return the connection timeout value.
+ */
+ public int getConnectionTimeout() {
+ return connectionTimeout;
+ }
+
+ /**
+ * Sets the connection timeout value.
+ * This value, measured in seconds, defines the maximum time interval
+ * the client will wait for the network connection to the MQTT server to be established.
+ * The default timeout is 30 seconds.
+ * A value of 0 disables timeout processing meaning the client will wait until the
+ * network connection is made successfully or fails.
+ * @param connectionTimeout the timeout value, measured in seconds. It must be >0;
+ */
+ public void setConnectionTimeout(int connectionTimeout) {
+ if (connectionTimeout <0 ) {
+ throw new IllegalArgumentException();
+ }
+ this.connectionTimeout = connectionTimeout;
+ }
+
+ /**
+ * Returns the socket factory that will be used when connecting, or
+ * null if one has not been set.
+ */
+ public SocketFactory getSocketFactory() {
+ return socketFactory;
+ }
+
+ /**
+ * Sets the SocketFactory to use. This allows an application
+ * to apply its own policies around the creation of network sockets. If
+ * using an SSL connection, an SSLSocketFactory can be used
+ * to supply application-specific security settings.
+ * @param socketFactory the factory to use.
+ */
+ public void setSocketFactory(SocketFactory socketFactory) {
+ this.socketFactory = socketFactory;
+ }
+
+ /**
+ * Returns the topic to be used for last will and testament (LWT).
+ * @return the MqttTopic to use, or null if LWT is not set.
+ * @see #setWill(MqttTopic, byte[], int, boolean)
+ */
+ public String getWillDestination() {
+ return willDestination;
+ }
+
+ /**
+ * Returns the message to be sent as last will and testament (LWT).
+ * The returned object is "read only". Calling any "setter" methods on
+ * the returned object will result in an
+ * IllegalStateException being thrown.
+ * @return the message to use, or null if LWT is not set.
+ */
+ public MqttMessage getWillMessage() {
+ return willMessage;
+ }
+
+ /**
+ * Returns the SSL properties for the connection.
+ * @return the properties for the SSL connection
+ */
+ public Properties getSSLProperties() {
+ return sslClientProps;
+ }
+
+ /**
+ * Sets the SSL properties for the connection. Note that these
+ * properties are only valid if an implementation of the Java
+ * Secure Socket Extensions (JSSE) is available. These properties are
+ * not used if a SocketFactory has been set using
+ * {@link #setSocketFactory(SocketFactory)}.
+ * The following properties can be used:
+ *
+ *
com.ibm.ssl.protocol
+ *
One of: SSL, SSLv3, TLS, TLSv1, SSL_TLS.
+ *
com.ibm.ssl.contextProvider
+ *
Underlying JSSE provider. For example "IBMJSSE2" or "SunJSSE"
+ *
+ *
com.ibm.ssl.keyStore
+ *
The name of the file that contains the KeyStore object that you
+ * want the KeyManager to use. For example /mydir/etc/key.p12
+ *
+ *
com.ibm.ssl.keyStorePassword
+ *
The password for the KeyStore object that you want the KeyManager to use.
+ * The password can either be in plain-text,
+ * or may be obfuscated using the static method:
+ * com.ibm.micro.security.Password.obfuscate(char[] password).
+ * This obfuscates the password using a simple and insecure XOR and Base64
+ * encoding mechanism. Note that this is only a simple scrambler to
+ * obfuscate clear-text passwords.
+ *
+ *
com.ibm.ssl.keyStoreType
+ *
Type of key store, for example "PKCS12", "JKS", or "JCEKS".
+ *
+ *
com.ibm.ssl.keyStoreProvider
+ *
Key store provider, for example "IBMJCE" or "IBMJCEFIPS".
+ *
+ *
com.ibm.ssl.trustStore
+ *
The name of the file that contains the KeyStore object that you
+ * want the TrustManager to use.
+ *
+ *
com.ibm.ssl.trustStorePassword
+ *
The password for the TrustStore object that you want the
+ * TrustManager to use. The password can either be in plain-text,
+ * or may be obfuscated using the static method:
+ * com.ibm.micro.security.Password.obfuscate(char[] password).
+ * This obfuscates the password using a simple and insecure XOR and Base64
+ * encoding mechanism. Note that this is only a simple scrambler to
+ * obfuscate clear-text passwords.
+ *
+ *
com.ibm.ssl.trustStoreType
+ *
The type of KeyStore object that you want the default TrustManager to use.
+ * Same possible values as "keyStoreType".
+ *
+ *
com.ibm.ssl.trustStoreProvider
+ *
Trust store provider, for example "IBMJCE" or "IBMJCEFIPS".
+ *
+ *
com.ibm.ssl.enabledCipherSuites
+ *
A list of which ciphers are enabled. Values are dependent on the provider,
+ * for example: SSL_RSA_WITH_AES_128_CBC_SHA;SSL_RSA_WITH_3DES_EDE_CBC_SHA.
+ *
+ *
com.ibm.ssl.keyManager
+ *
Sets the algorithm that will be used to instantiate a KeyManagerFactory object
+ * instead of using the default algorithm available in the platform. Example values:
+ * "IbmX509" or "IBMJ9X509".
+ *
+ *
+ *
com.ibm.ssl.trustManager
+ *
Sets the algorithm that will be used to instantiate a TrustManagerFactory object
+ * instead of using the default algorithm available in the platform. Example values:
+ * "PKIX" or "IBMJ9X509".
+ *
+ *
+ */
+ public void setSSLProperties(Properties props) {
+ this.sslClientProps = props;
+ }
+
+ /**
+ * Returns whether the client and server should remember state for the client across reconnects.
+ * @return the clean session flag
+ */
+ public boolean isCleanSession() {
+ return this.cleanSession;
+ }
+
+ /**
+ * Sets whether the client and server should remember state across restarts and reconnects.
+ *
+ *
If set to false both the client and server will maintain state across
+ * restarts of the client, the server and the connection. As state is maintained:
+ *
+ *
Message delivery will be reliable meeting
+ * the specified QOS even if the client, server or connection are restarted.
+ *
The server will treat a subscription as durable.
+ *
+ *
If set to true the client and server will not maintain state across
+ * restarts of the client, the server or the connection. This means
+ *
+ *
Message delivery to the specified QOS cannot be maintained if the
+ * client, server or connection are restarted
+ *
The server will treat a subscription as non-durable
+ *
+ */
+ public void setCleanSession(boolean cleanSession) {
+ this.cleanSession = cleanSession;
+ }
+
+ /**
+ * Return a list of serverURIs the client may connect to
+ * @return the serverURIs or null if not set
+ */
+ public String[] getServerURIs() {
+ return serverURIs;
+ }
+
+ /**
+ * Set a list of one or more serverURIs the client may connect to.
+ *
+ * Each serverURI specifies the address of a server that the client may
+ * connect to. Two types of
+ * connection are supported tcp:// for a TCP connection and
+ * ssl:// for a TCP connection secured by SSL/TLS.
+ * For example:
+ *
+ *
tcp://localhost:1883
+ *
ssl://localhost:8883
+ *
+ * If the port is not specified, it will
+ * default to 1883 for tcp://" URIs, and 8883 for ssl:// URIs.
+ *
+ * If serverURIs is set then it overrides the serverURI parameter passed in on the
+ * constructor of the MQTT client.
+ *
+ * When an attempt to connect is initiated the client will start with the first
+ * serverURI in the list and work through
+ * the list until a connection is established with a server. If a connection cannot be made to
+ * any of the servers then the connect attempt fails.
+ *
+ * Specifying a list of servers that a client may connect to has several uses:
+ *
+ *
High Availability and reliable message delivery
+ *
Some MQTT servers support a high availability feature where two or more
+ * "equal" MQTT servers share state. An MQTT client can connect to any of the "equal"
+ * servers and be assured that messages are reliably delivered and durable subscriptions
+ * are maintained no matter which server the client connects to.
+ *
The cleansession flag must be set to false if durable subscriptions and/or reliable
+ * message delivery is required.
+ *
Hunt List
+ *
A set of servers may be specified that are not "equal" (as in the high availability
+ * option). As no state is shared across the servers reliable message delivery and
+ * durable subscriptions are not valid. The cleansession flag must be set to true if the
+ * hunt list mode is used
+ *
+ *
+ * @param array of serverURIs
+ */
+ public void setServerURIs(String[] array) {
+ for (int i = 0; i < array.length; i++) {
+ validateURI(array[i]);
+ }
+ this.serverURIs = array;
+ }
+
+ /**
+ * Validate a URI
+ * @param srvURI
+ * @return the URI type
+ */
+
+ protected static int validateURI(String srvURI) {
+ try {
+ URI vURI = new URI(srvURI);
+ if (!vURI.getPath().equals("")) {
+ throw new IllegalArgumentException(srvURI);
+ }
+ if (vURI.getScheme().equals("tcp")) {
+ return URI_TYPE_TCP;
+ }
+ else if (vURI.getScheme().equals("ssl")) {
+ return URI_TYPE_SSL;
+ }
+ else if (vURI.getScheme().equals("local")) {
+ return URI_TYPE_LOCAL;
+ }
+ else {
+ throw new IllegalArgumentException(srvURI);
+ }
+ } catch (URISyntaxException ex) {
+ throw new IllegalArgumentException(srvURI);
+ }
+ }
+
+ /**
+ * Sets the MQTT version.
+ * The default action is to connect with version 3.1.1,
+ * and to fall back to 3.1 if that fails.
+ * Version 3.1.1 or 3.1 can be selected specifically, with no fall back,
+ * by using the MQTT_VERSION_3_1_1 or MQTT_VERSION_3_1 options respectively.
+ *
+ * @param MqttVersion the version of the MQTT protocol.
+ */
+ public void setMqttVersion(int MqttVersion)throws IllegalArgumentException {
+ if (MqttVersion != MQTT_VERSION_DEFAULT &&
+ MqttVersion != MQTT_VERSION_3_1 &&
+ MqttVersion != MQTT_VERSION_3_1_1) {
+ throw new IllegalArgumentException();
+ }
+ this.MqttVersion = MqttVersion;
+ }
+
+ public Properties getDebug() {
+ final String strNull="null";
+ Properties p = new Properties();
+ p.put("MqttVersion", new Integer(getMqttVersion()));
+ p.put("CleanSession", Boolean.valueOf(isCleanSession()));
+ p.put("ConTimeout", new Integer(getConnectionTimeout()));
+ p.put("KeepAliveInterval", new Integer(getKeepAliveInterval()));
+ p.put("UserName", (getUserName() == null) ? strNull : getUserName());
+ p.put("WillDestination", (getWillDestination() == null) ? strNull : getWillDestination());
+ if (getSocketFactory()==null) {
+ p.put("SocketFactory", strNull);
+ } else {
+ p.put("SocketFactory", getSocketFactory());
+ }
+ if (getSSLProperties()==null) {
+ p.put("SSLProperties", strNull);
+ } else {
+ p.put("SSLProperties", getSSLProperties());
+ }
+ return p;
+ }
+
+ public String toString() {
+ return Debug.dumpProperties(getDebug(), "Connection options");
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttDeliveryToken.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttDeliveryToken.java
new file mode 100644
index 00000000..f2bb574e
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttDeliveryToken.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3;
+
+/**
+ * Provides a mechanism to track the delivery progress of a message.
+ *
+ *
+ * Used to track the the delivery progress of a message when a publish is
+ * executed in a non-blocking manner (run in the background)
+ *
+ * @see MqttToken
+ */
+public class MqttDeliveryToken extends MqttToken implements IMqttDeliveryToken {
+
+
+ public MqttDeliveryToken() {
+ super();
+ }
+
+ public MqttDeliveryToken(String logContext) {
+ super(logContext);
+ }
+
+ /**
+ * Returns the message associated with this token.
+ *
Until the message has been delivered, the message being delivered will
+ * be returned. Once the message has been delivered null will be
+ * returned.
+ * @return the message associated with this token or null if already delivered.
+ * @throws MqttException if there was a problem completing retrieving the message
+ */
+ public MqttMessage getMessage() throws MqttException {
+ return internalTok.getMessage();
+ }
+
+ protected void setMessage(MqttMessage msg) {
+ internalTok.setMessage(msg);
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttException.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttException.java
similarity index 76%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttException.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttException.java
index de480903..8ce6a49c 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttException.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttException.java
@@ -1,189 +1,229 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3;
-
-import org.eclipse.paho.client.mqttv3.internal.MessageCatalog;
-
-/**
- * Thrown if an error occurs communicating with the server.
- */
-public class MqttException extends Exception {
- private static final long serialVersionUID = 300L;
-
- /**
- * Client encountered an exception. Use the {@link #getCause()}
- * method to get the underlying reason.
- */
- public static final short REASON_CODE_CLIENT_EXCEPTION = 0x00;
-
- // CONNACK return codes
- /** The protocol version requested is not supported by the server. */
- public static final short REASON_CODE_INVALID_PROTOCOL_VERSION = 0x01;
- /** The server has rejected the supplied client ID */
- public static final short REASON_CODE_INVALID_CLIENT_ID = 0x02;
- /** The broker was not available to handle the request. */
- public static final short REASON_CODE_BROKER_UNAVAILABLE = 0x03;
- /** Authentication with the server has failed, due to a bad user name or password. */
- public static final short REASON_CODE_FAILED_AUTHENTICATION = 0x04;
- /** Not authorized to perform the requested operation */
- public static final short REASON_CODE_NOT_AUTHORIZED = 0x05;
-
-
- /** An unexpected error has occurred. */
- public static final short REASON_CODE_UNEXPECTED_ERROR = 0x06;
-
- /**
- * Client timed out while waiting for a response from the server.
- * The server is no longer responding to keep-alive messages.
- */
- public static final short REASON_CODE_CLIENT_TIMEOUT = 32000;
- /**
- * Internal error, caused by no new message IDs being available.
- */
- public static final short REASON_CODE_NO_MESSAGE_IDS_AVAILABLE = 32001;
-
- /**
- * The client is already connected.
- */
- public static final short REASON_CODE_CLIENT_ALREADY_CONNECTED = 32100;
- /**
- * The client is already disconnected.
- */
- public static final short REASON_CODE_CLIENT_ALREADY_DISCONNECTED = 32101;
- /**
- * The client is currently disconnecting and cannot accept any new work.
- * This can occur when waiting on a token, and then disconnecting the client.
- * If the message delivery does not complete within the quiesce timeout
- * period, then the waiting token will be notified with an exception.
- */
- public static final short REASON_CODE_CLIENT_DISCONNECTING = 32102;
-
- /** Unable to connect to server */
- public static final short REASON_CODE_SERVER_CONNECT_ERROR = 32103;
-
- /**
- * The client is not connected to the server. The {@link MqttClient#connect()}
- * or {@link MqttClient#connect(MqttConnectOptions)} method must be called
- * first. It is also possible that the connection was lost - see
- * {@link MqttClient#setCallback(MqttCallback)} for a way to track lost
- * connections.
- */
- public static final short REASON_CODE_CLIENT_NOT_CONNECTED = 32104;
-
- /**
- * Server URI and supplied SocketFactory do not match.
- * URIs beginning tcp:// must use a javax.net.SocketFactory,
- * and URIs beginning ssl:// must use a javax.net.ssl.SSLSocketFactory.
- */
- public static final short REASON_CODE_SOCKET_FACTORY_MISMATCH = 32105;
-
- /**
- * SSL configuration error.
- */
- public static final short REASON_CODE_SSL_CONFIG_ERROR = 32106;
-
- /**
- * Thrown when an attempt to call {@link MqttClient#disconnect()} has been
- * made from within a method on {@link MqttCallback}. These methods are invoked
- * by the client's thread, and must not be used to control disconnection.
- *
- * @see MqttCallback#messageArrived(MqttDestination, MqttMessage)
- */
- public static final short REASON_CODE_CLIENT_DISCONNECT_PROHIBITED = 32107;
-
- /**
- * Protocol error: the message was not recognized as a valid MQTT packet.
- * Possible reasons for this include connecting to a non-MQTT server, or
- * connecting to an SSL server port when the client isn't using SSL.
- */
- public static final short REASON_CODE_INVALID_MESSAGE = 32108;
-
- /**
- * The client has been unexpectedly disconnected from the server. The {@link #getCause() cause}
- * will provide more details.
- */
- public static final short REASON_CODE_CONNECTION_LOST = 32109;
-
- private int reasonCode;
- private Throwable cause;
-
- /**
- * Constructs a new MqttException with the specified code
- * as the underlying reason.
- * @param reasonCode the reason code for the exception.
- */
- public MqttException(int reasonCode) {
- super();
- this.reasonCode = reasonCode;
- }
-
- /**
- * Constructs a new MqttException with the specified
- * Throwable as the underlying reason.
- * @param cause the underlying cause of the exception.
- */
- public MqttException(Throwable cause) {
- super();
- this.reasonCode = REASON_CODE_CLIENT_EXCEPTION;
- this.cause = cause;
- }
-
- /**
- * Constructs a new MqttException with the specified
- * Throwable as the underlying reason.
- * @param reasonCode the reason code for the exception.
- * @param cause the underlying cause of the exception.
- */
- public MqttException(int reason, Throwable cause) {
- super();
- this.reasonCode = reason;
- this.cause = cause;
- }
-
-
- /**
- * Returns the reason code for this exception.
- * @return the code representing the reason for this exception.
- */
- public int getReasonCode() {
- return reasonCode;
- }
-
- /**
- * Returns the underlying cause of this exception, if available.
- * @return the Throwable that was the root cause of this exception,
- * which may be null.
- */
- public Throwable getCause() {
- return cause;
- }
-
- /**
- * Returns the detail message for this exception.
- * @return the detail message, which may be null.
- */
- public String getMessage() {
- return MessageCatalog.getMessage(reasonCode);
- }
-
- /**
- * Returns a String representation of this exception.
- * @return a String representation of this exception.
- */
- public String toString() {
- String result = getMessage() + " (" + reasonCode + ")";
- if (cause != null) {
- result = result + " - " + cause.toString();
- }
- return result;
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ * Ian Craggs - MQTT 3.1.1 support
+ */
+package org.eclipse.paho.client.mqttv3;
+
+import org.eclipse.paho.client.mqttv3.internal.MessageCatalog;
+
+/**
+ * Thrown if an error occurs communicating with the server.
+ */
+public class MqttException extends Exception {
+ private static final long serialVersionUID = 300L;
+
+ /**
+ * Client encountered an exception. Use the {@link #getCause()}
+ * method to get the underlying reason.
+ */
+ public static final short REASON_CODE_CLIENT_EXCEPTION = 0x00;
+
+ // CONNACK return codes
+ /** The protocol version requested is not supported by the server. */
+ public static final short REASON_CODE_INVALID_PROTOCOL_VERSION = 0x01;
+ /** The server has rejected the supplied client ID */
+ public static final short REASON_CODE_INVALID_CLIENT_ID = 0x02;
+ /** The broker was not available to handle the request. */
+ public static final short REASON_CODE_BROKER_UNAVAILABLE = 0x03;
+ /** Authentication with the server has failed, due to a bad user name or password. */
+ public static final short REASON_CODE_FAILED_AUTHENTICATION = 0x04;
+ /** Not authorized to perform the requested operation */
+ public static final short REASON_CODE_NOT_AUTHORIZED = 0x05;
+
+ /** An unexpected error has occurred. */
+ public static final short REASON_CODE_UNEXPECTED_ERROR = 0x06;
+
+ /** Error from subscribe - returned from the server. */
+ public static final short REASON_CODE_SUBSCRIBE_FAILED = 0x80;
+
+ /**
+ * Client timed out while waiting for a response from the server.
+ * The server is no longer responding to keep-alive messages.
+ */
+ public static final short REASON_CODE_CLIENT_TIMEOUT = 32000;
+
+ /**
+ * Internal error, caused by no new message IDs being available.
+ */
+ public static final short REASON_CODE_NO_MESSAGE_IDS_AVAILABLE = 32001;
+
+ /**
+ * Client timed out while waiting to write messages to the server.
+ */
+ public static final short REASON_CODE_WRITE_TIMEOUT = 32002;
+
+ /**
+ * The client is already connected.
+ */
+ public static final short REASON_CODE_CLIENT_CONNECTED = 32100;
+
+ /**
+ * The client is already disconnected.
+ */
+ public static final short REASON_CODE_CLIENT_ALREADY_DISCONNECTED = 32101;
+ /**
+ * The client is currently disconnecting and cannot accept any new work.
+ * This can occur when waiting on a token, and then disconnecting the client.
+ * If the message delivery does not complete within the quiesce timeout
+ * period, then the waiting token will be notified with an exception.
+ */
+ public static final short REASON_CODE_CLIENT_DISCONNECTING = 32102;
+
+ /** Unable to connect to server */
+ public static final short REASON_CODE_SERVER_CONNECT_ERROR = 32103;
+
+ /**
+ * The client is not connected to the server. The {@link MqttClient#connect()}
+ * or {@link MqttClient#connect(MqttConnectOptions)} method must be called
+ * first. It is also possible that the connection was lost - see
+ * {@link MqttClient#setCallback(MqttCallback)} for a way to track lost
+ * connections.
+ */
+ public static final short REASON_CODE_CLIENT_NOT_CONNECTED = 32104;
+
+ /**
+ * Server URI and supplied SocketFactory do not match.
+ * URIs beginning tcp:// must use a javax.net.SocketFactory,
+ * and URIs beginning ssl:// must use a javax.net.ssl.SSLSocketFactory.
+ */
+ public static final short REASON_CODE_SOCKET_FACTORY_MISMATCH = 32105;
+
+ /**
+ * SSL configuration error.
+ */
+ public static final short REASON_CODE_SSL_CONFIG_ERROR = 32106;
+
+ /**
+ * Thrown when an attempt to call {@link MqttClient#disconnect()} has been
+ * made from within a method on {@link MqttCallback}. These methods are invoked
+ * by the client's thread, and must not be used to control disconnection.
+ *
+ * @see MqttCallback#messageArrived(String, MqttMessage)
+ */
+ public static final short REASON_CODE_CLIENT_DISCONNECT_PROHIBITED = 32107;
+
+ /**
+ * Protocol error: the message was not recognized as a valid MQTT packet.
+ * Possible reasons for this include connecting to a non-MQTT server, or
+ * connecting to an SSL server port when the client isn't using SSL.
+ */
+ public static final short REASON_CODE_INVALID_MESSAGE = 32108;
+
+ /**
+ * The client has been unexpectedly disconnected from the server. The {@link #getCause() cause}
+ * will provide more details.
+ */
+ public static final short REASON_CODE_CONNECTION_LOST = 32109;
+
+ /**
+ * A connect operation in already in progress, only one connect can happen
+ * at a time.
+ */
+ public static final short REASON_CODE_CONNECT_IN_PROGRESS = 32110;
+
+ /**
+ * The client is closed - no operations are permitted on the client in this
+ * state. New up a new client to continue.
+ */
+ public static final short REASON_CODE_CLIENT_CLOSED = 32111;
+
+ /**
+ * A request has been made to use a token that is already associated with
+ * another action. If the action is complete the reset() can ve called on the
+ * token to allow it to be reused.
+ */
+ public static final short REASON_CODE_TOKEN_INUSE = 32201;
+
+ /**
+ * A request has been made to send a message but the maximum number of inflight
+ * messages has already been reached. Once one or more messages have been moved
+ * then new messages can be sent.
+ */
+ public static final short REASON_CODE_MAX_INFLIGHT = 32202;
+
+ private int reasonCode;
+ private Throwable cause;
+
+ /**
+ * Constructs a new MqttException with the specified code
+ * as the underlying reason.
+ * @param reasonCode the reason code for the exception.
+ */
+ public MqttException(int reasonCode) {
+ super();
+ this.reasonCode = reasonCode;
+ }
+
+ /**
+ * Constructs a new MqttException with the specified
+ * Throwable as the underlying reason.
+ * @param cause the underlying cause of the exception.
+ */
+ public MqttException(Throwable cause) {
+ super();
+ this.reasonCode = REASON_CODE_CLIENT_EXCEPTION;
+ this.cause = cause;
+ }
+
+ /**
+ * Constructs a new MqttException with the specified
+ * Throwable as the underlying reason.
+ * @param reason the reason code for the exception.
+ * @param cause the underlying cause of the exception.
+ */
+ public MqttException(int reason, Throwable cause) {
+ super();
+ this.reasonCode = reason;
+ this.cause = cause;
+ }
+
+
+ /**
+ * Returns the reason code for this exception.
+ * @return the code representing the reason for this exception.
+ */
+ public int getReasonCode() {
+ return reasonCode;
+ }
+
+ /**
+ * Returns the underlying cause of this exception, if available.
+ * @return the Throwable that was the root cause of this exception,
+ * which may be null.
+ */
+ public Throwable getCause() {
+ return cause;
+ }
+
+ /**
+ * Returns the detail message for this exception.
+ * @return the detail message, which may be null.
+ */
+ public String getMessage() {
+ return MessageCatalog.getMessage(reasonCode);
+ }
+
+ /**
+ * Returns a String representation of this exception.
+ * @return a String representation of this exception.
+ */
+ public String toString() {
+ String result = getMessage() + " (" + reasonCode + ")";
+ if (cause != null) {
+ result = result + " - " + cause.toString();
+ }
+ return result;
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttMessage.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttMessage.java
similarity index 74%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttMessage.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttMessage.java
index 32a611f9..a68c2034 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttMessage.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttMessage.java
@@ -1,202 +1,219 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3;
-
-/**
- * An MQTT message. The message includes a "payload" (the body of the message)
- * represented as a byte[].
- */
-public class MqttMessage {
-
- private boolean mutable = true;
- private byte[] payload;
- private int qos = 1;
- private boolean retained = false;
- private boolean dup = false;
-
- /**
- * Utility method to validate the supplied QoS value.
- * @throws IllegalArgumentException if value of QoS is not 0, 1 or 2.
- */
- protected static void validateQos(int qos) {
- if ((qos < 0) || (qos > 2)) {
- throw new IllegalArgumentException();
- }
- }
-
- /**
- * Constructs a message with an empty payload, and all other values
- * set to defaults.
- *
- * The defaults are:
- *
- *
Message QoS set to 1
- *
Message will not be "retained" by the server
- *
- */
- public MqttMessage() {
- setPayload(new byte[]{});
- }
-
- /**
- * Constructs a message with the specified byte array as a payload,
- * and all other values set to defaults.
- */
- public MqttMessage(byte[] payload) {
- setPayload(payload);
- }
-
- /**
- * Returns the payload as a byte array.
- *
- * @return the payload as a byte array.
- */
- public byte[] getPayload() throws MqttException {
- return payload;
- }
-
- /**
- * Clears the payload, resetting it to be empty.
- * @throws IllegalStateException if this message cannot be edited
- */
- public void clearPayload() {
- checkMutable();
- this.payload = new byte[] {};
- }
-
- /**
- * Sets the payload of this message to be the specified byte array.
- *
- * @param payload the payload for this message.
- * @throws IllegalStateException if this message cannot be edited
- */
- public void setPayload(byte[] payload) {
- checkMutable();
- this.payload = payload;
- }
-
- /**
- * Returns whether or not this message should be/was retained by the server.
- * For messages received from the server, this method returns whether or not
- * the message was from a current publisher, or was "retained" by the server as
- * the last message published on the topic.
- *
- * @return true if the message should be, or was, retained by
- * the server.
- * @see #setRetained(boolean)
- */
- public boolean isRetained() {
- return retained;
- }
-
- /**
- * Whether or not the publish message should be retained by the messaging engine.
- * Sending a message with the retained set to false will clear the
- * retained message from the server. The default value is false
- *
- * @param retained whether or not the messaging engine should retain the message.
- * @throws IllegalStateException if this message cannot be edited
- */
- public void setRetained(boolean retained) {
- checkMutable();
- this.retained = retained;
- }
-
- /**
- * Returns the quality of service for this message.
- * @return the quality of service to use, either 0, 1, or 2.
- * @see #setQos(int)
- */
- public int getQos() {
- return qos;
- }
-
- /**
- * Sets the quality of service for this message.
- *
- *
Quality of service 0 - indicates that a message should
- * be delivered at most once (zero or one times). The message will not be persisted to disk,
- * and will not be acknowledged across the network. This QoS is the fastest,
- * but should only be used for messages which are not valuable - note that
- * if the server cannot process the message (for example, there
- * is an authorization problem), then an
- * exception will not be thrown, nor will a call be made to
- * {@link MqttCallback#deliveryFailed(MqttDeliveryToken, MqttException)} or
- * {@link MqttCallback#deliveryComplete(MqttDeliveryToken)}.
- * Also known as "fire and forget".
- *
- *
Quality of service 1 - indicates that a message should
- * be delivered at least once (one or more times). The message can only be delivered safely if
- * it can be persisted, so the application must supply a means of
- * persistence using MqttConnectOptions.
- * If a persistence mechanism is not specified, the message will not be
- * delivered in the event of a client failure.
- * The message will be acknowledged across the network.
- * This is the default QoS.
- *
- *
Quality of service 2 - indicates that a message should
- * be delivered once. The message will be persisted to disk, and will
- * be subject to a two-phase acknowledgement across the network.
- * The message can only be delivered safely if
- * it can be persisted, so the application must supply a means of
- * persistence using MqttConnectOptions.
- * If a persistence mechanism is not specified, the message will not be
- * delivered in the event of a client failure.
- *
- * @param qos the "quality of service" to use. Set to 0, 1, 2.
- * @throws IllegalArgumentException if value of QoS is not 0, 1 or 2.
- * @throws IllegalStateException if this message cannot be edited
- */
- public void setQos(int qos) {
- checkMutable();
- validateQos(qos);
- this.qos = qos;
- }
-
- /**
- * Returns a string representation of this message.
- * @return a string representation of this message.
- */
- public String toString() {
- return new String(payload);
- }
-
- /**
- * Sets the mutability of this object (whether or not its values can be
- * changed.
- * @param mutable true if the values can be changed,
- * false to prevent them from being changed.
- */
- protected void setMutable(boolean mutable) {
- this.mutable = mutable;
- }
-
- protected void checkMutable() throws IllegalStateException {
- if (!mutable) {
- throw new IllegalStateException();
- }
- }
-
- protected void setDuplicate(boolean dup) {
- this.dup = dup;
- }
-
- /**
- * Returns whether or not this message might be a duplicate of one which has
- * already been received. This will only be set on messages received from
- * the server.
- * @return true if the message might be a duplicate.
- */
- public boolean isDuplicate() {
- return this.dup;
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3;
+
+/**
+ * An MQTT message holds the application payload and options
+ * specifying how the message is to be delivered
+ * The message includes a "payload" (the body of the message)
+ * represented as a byte[].
+ */
+public class MqttMessage {
+
+ private boolean mutable = true;
+ private byte[] payload;
+ private int qos = 1;
+ private boolean retained = false;
+ private boolean dup = false;
+
+ /**
+ * Utility method to validate the supplied QoS value.
+ * @throws IllegalArgumentException if value of QoS is not 0, 1 or 2.
+ */
+ public static void validateQos(int qos) {
+ if ((qos < 0) || (qos > 2)) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Constructs a message with an empty payload, and all other values
+ * set to defaults.
+ *
+ * The defaults are:
+ *
+ *
Message QoS set to 1
+ *
Message will not be "retained" by the server
+ *
+ */
+ public MqttMessage() {
+ setPayload(new byte[]{});
+ }
+
+ /**
+ * Constructs a message with the specified byte array as a payload,
+ * and all other values set to defaults.
+ */
+ public MqttMessage(byte[] payload) {
+ setPayload(payload);
+ }
+
+ /**
+ * Returns the payload as a byte array.
+ *
+ * @return the payload as a byte array.
+ */
+ public byte[] getPayload() {
+ return payload;
+ }
+
+ /**
+ * Clears the payload, resetting it to be empty.
+ * @throws IllegalStateException if this message cannot be edited
+ */
+ public void clearPayload() {
+ checkMutable();
+ this.payload = new byte[] {};
+ }
+
+ /**
+ * Sets the payload of this message to be the specified byte array.
+ *
+ * @param payload the payload for this message.
+ * @throws IllegalStateException if this message cannot be edited
+ * @throws NullPointerException if no payload is provided
+ */
+ public void setPayload(byte[] payload) {
+ checkMutable();
+ if (payload == null) {
+ throw new NullPointerException();
+ }
+ this.payload = payload;
+ }
+
+ /**
+ * Returns whether or not this message should be/was retained by the server.
+ * For messages received from the server, this method returns whether or not
+ * the message was from a current publisher, or was "retained" by the server as
+ * the last message published on the topic.
+ *
+ * @return true if the message should be, or was, retained by
+ * the server.
+ * @see #setRetained(boolean)
+ */
+ public boolean isRetained() {
+ return retained;
+ }
+
+ /**
+ * Whether or not the publish message should be retained by the messaging engine.
+ * Sending a message with the retained set to false will clear the
+ * retained message from the server. The default value is false
+ *
+ * @param retained whether or not the messaging engine should retain the message.
+ * @throws IllegalStateException if this message cannot be edited
+ */
+ public void setRetained(boolean retained) {
+ checkMutable();
+ this.retained = retained;
+ }
+
+ /**
+ * Returns the quality of service for this message.
+ * @return the quality of service to use, either 0, 1, or 2.
+ * @see #setQos(int)
+ */
+ public int getQos() {
+ return qos;
+ }
+
+ /**
+ * Sets the quality of service for this message.
+ *
+ *
Quality of Service 0 - indicates that a message should
+ * be delivered at most once (zero or one times). The message will not be persisted to disk,
+ * and will not be acknowledged across the network. This QoS is the fastest,
+ * but should only be used for messages which are not valuable - note that
+ * if the server cannot process the message (for example, there
+ * is an authorization problem), then an
+ * {@link MqttCallback#deliveryComplete(IMqttDeliveryToken)}.
+ * Also known as "fire and forget".
+ *
+ *
Quality of Service 1 - indicates that a message should
+ * be delivered at least once (one or more times). The message can only be delivered safely if
+ * it can be persisted, so the application must supply a means of
+ * persistence using MqttConnectOptions.
+ * If a persistence mechanism is not specified, the message will not be
+ * delivered in the event of a client failure.
+ * The message will be acknowledged across the network.
+ * This is the default QoS.
+ *
+ *
Quality of Service 2 - indicates that a message should
+ * be delivered once. The message will be persisted to disk, and will
+ * be subject to a two-phase acknowledgement across the network.
+ * The message can only be delivered safely if
+ * it can be persisted, so the application must supply a means of
+ * persistence using MqttConnectOptions.
+ * If a persistence mechanism is not specified, the message will not be
+ * delivered in the event of a client failure.
+ *
+ * If persistence is not configured, QoS 1 and 2 messages will still be delivered
+ * in the event of a network or server problem as the client will hold state in memory.
+ * If the MQTT client is shutdown or fails and persistence is not configured then
+ * delivery of QoS 1 and 2 messages can not be maintained as client-side state will
+ * be lost.
+ *
+ * @param qos the "quality of service" to use. Set to 0, 1, 2.
+ * @throws IllegalArgumentException if value of QoS is not 0, 1 or 2.
+ * @throws IllegalStateException if this message cannot be edited
+ */
+ public void setQos(int qos) {
+ checkMutable();
+ validateQos(qos);
+ this.qos = qos;
+ }
+
+ /**
+ * Returns a string representation of this message's payload.
+ * Makes an attempt to return the payload as a string. As the
+ * MQTT client has no control over the content of the payload
+ * it may fail.
+ * @return a string representation of this message.
+ */
+ public String toString() {
+ return new String(payload);
+ }
+
+ /**
+ * Sets the mutability of this object (whether or not its values can be
+ * changed.
+ * @param mutable true if the values can be changed,
+ * false to prevent them from being changed.
+ */
+ protected void setMutable(boolean mutable) {
+ this.mutable = mutable;
+ }
+
+ protected void checkMutable() throws IllegalStateException {
+ if (!mutable) {
+ throw new IllegalStateException();
+ }
+ }
+
+ protected void setDuplicate(boolean dup) {
+ this.dup = dup;
+ }
+
+ /**
+ * Returns whether or not this message might be a duplicate of one which has
+ * already been received. This will only be set on messages received from
+ * the server.
+ * @return true if the message might be a duplicate.
+ */
+ public boolean isDuplicate() {
+ return this.dup;
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttPersistable.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttPersistable.java
similarity index 89%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttPersistable.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttPersistable.java
index 58f67c33..5c8b70cc 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttPersistable.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttPersistable.java
@@ -1,93 +1,97 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3;
-
-/**
- * Represents an object used to pass data to be persisted across the
- * {@link org.eclipse.paho.client.mqttv3.MqttClientPersistence MqttClientPersistence}
- * interface.
- *
- * When data is passed across the interface the header and payload are
- * separated, so that unnecessary message copies may be avoided.
- * For example, if a 10 MB payload was published it would be inefficient
- * to create a byte array a few bytes larger than 10 MB and copy the
- * MQTT message header and payload into a contiguous byte array.
- *
- * When the request to persist data is made a separate byte array and offset
- * is passed for the header and payload. Only the data between
- * offset and length need be persisted.
- * So for example, a message to be persisted consists of a header byte
- * array starting at offset 1 and length 4, plus a payload byte array
- * starting at offset 30 and length 40000. There are three ways in which
- * the persistence implementation may return data to the client on
- * recovery:
- *
- *
It could return the data as it was passed in
- * originally, with the same byte arrays and offsets.
- *
It could safely just persist and return the bytes from the offset
- * for the specified length. For example, return a header byte array with
- * offset 0 and length 4, plus a payload byte array with offset 0 and length
- * 40000
- *
It could return the header and payload as a contiguous byte array
- * with the header bytes preceeding the payload. The contiguous byte array
- * should be set as the header byte array, with the payload byte array being
- * null. For example, return a single byte array with offset 0
- * and length 40004.
- * This is useful when recovering from a file where the header and payload
- * could be written as a contiguous stream of bytes.
- *
- *
- */
-public interface MqttPersistable {
-
- /**
- * Returns the header bytes in an array.
- * The bytes start at {@link #getHeaderOffset()}
- * and continue for {@link #getHeaderLength()}.
- * @return the header bytes.
- */
- public byte[] getHeaderBytes() throws MqttPersistenceException;
-
- /**
- * Returns the length of the header.
- * @return the header length
- */
- public int getHeaderLength() throws MqttPersistenceException;
-
- /**
- * Returns the offset of the header within the byte array returned by {@link #getHeaderBytes()}.
- * @return the header offset.
- *
- */
- public int getHeaderOffset() throws MqttPersistenceException;
-
- /**
- * Returns the payload bytes in an array.
- * The bytes start at {@link #getPayloadOffset()}
- * and continue for {@link #getPayloadLength()}.
- * @return the payload bytes.
- */
- public byte[] getPayloadBytes() throws MqttPersistenceException;
-
- /**
- * Returns the length of the payload.
- * @return the payload length.
- */
- public int getPayloadLength() throws MqttPersistenceException;
-
- /**
- * Returns the offset of the payload within the byte array returned by {@link #getPayloadBytes()}.
- * @return the payload offset.
- *
- */
- public int getPayloadOffset() throws MqttPersistenceException;
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3;
+
+/**
+ * Represents an object used to pass data to be persisted across the
+ * {@link org.eclipse.paho.client.mqttv3.MqttClientPersistence MqttClientPersistence}
+ * interface.
+ *
+ * When data is passed across the interface the header and payload are
+ * separated, so that unnecessary message copies may be avoided.
+ * For example, if a 10 MB payload was published it would be inefficient
+ * to create a byte array a few bytes larger than 10 MB and copy the
+ * MQTT message header and payload into a contiguous byte array.
+ *
+ * When the request to persist data is made a separate byte array and offset
+ * is passed for the header and payload. Only the data between
+ * offset and length need be persisted.
+ * So for example, a message to be persisted consists of a header byte
+ * array starting at offset 1 and length 4, plus a payload byte array
+ * starting at offset 30 and length 40000. There are three ways in which
+ * the persistence implementation may return data to the client on
+ * recovery:
+ *
+ *
It could return the data as it was passed in
+ * originally, with the same byte arrays and offsets.
+ *
It could safely just persist and return the bytes from the offset
+ * for the specified length. For example, return a header byte array with
+ * offset 0 and length 4, plus a payload byte array with offset 0 and length
+ * 40000
+ *
It could return the header and payload as a contiguous byte array
+ * with the header bytes preceeding the payload. The contiguous byte array
+ * should be set as the header byte array, with the payload byte array being
+ * null. For example, return a single byte array with offset 0
+ * and length 40004.
+ * This is useful when recovering from a file where the header and payload
+ * could be written as a contiguous stream of bytes.
+ *
+ *
+ */
+public interface MqttPersistable {
+
+ /**
+ * Returns the header bytes in an array.
+ * The bytes start at {@link #getHeaderOffset()}
+ * and continue for {@link #getHeaderLength()}.
+ * @return the header bytes.
+ */
+ public byte[] getHeaderBytes() throws MqttPersistenceException;
+
+ /**
+ * Returns the length of the header.
+ * @return the header length
+ */
+ public int getHeaderLength() throws MqttPersistenceException;
+
+ /**
+ * Returns the offset of the header within the byte array returned by {@link #getHeaderBytes()}.
+ * @return the header offset.
+ *
+ */
+ public int getHeaderOffset() throws MqttPersistenceException;
+
+ /**
+ * Returns the payload bytes in an array.
+ * The bytes start at {@link #getPayloadOffset()}
+ * and continue for {@link #getPayloadLength()}.
+ * @return the payload bytes.
+ */
+ public byte[] getPayloadBytes() throws MqttPersistenceException;
+
+ /**
+ * Returns the length of the payload.
+ * @return the payload length.
+ */
+ public int getPayloadLength() throws MqttPersistenceException;
+
+ /**
+ * Returns the offset of the payload within the byte array returned by {@link #getPayloadBytes()}.
+ * @return the payload offset.
+ *
+ */
+ public int getPayloadOffset() throws MqttPersistenceException;
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttPersistenceException.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttPersistenceException.java
similarity index 61%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttPersistenceException.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttPersistenceException.java
index 504bc145..e5c142ac 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttPersistenceException.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttPersistenceException.java
@@ -1,48 +1,60 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3;
-
-/**
- * This exception should be thrown by the implementor of the persistence
- * interface if there is a problem reading or writing persistent data.
- */
-public class MqttPersistenceException extends MqttException {
- private static final long serialVersionUID = 300L;
-
- /** Persistence is already being used by another client. */
- public static final short REASON_CODE_PERSISTENCE_IN_USE = 32200;
-
- /**
- * Constructs a new MqttPersistenceException
- */
- public MqttPersistenceException() {
- super(REASON_CODE_CLIENT_EXCEPTION);
- }
-
- /**
- * Constructs a new MqttPersistenceException with the specified code
- * as the underlying reason.
- * @param reasonCode the reason code for the exception.
- */
- public MqttPersistenceException(int reasonCode) {
- super(reasonCode);
- }
- /**
- * Constructs a new MqttPersistenceException with the specified
- * Throwable as the underlying reason.
- * @param cause the underlying cause of the exception.
- */
- public MqttPersistenceException(Throwable cause) {
- super(cause);
- }
-
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3;
+
+/**
+ * This exception is thrown by the implementor of the persistence
+ * interface if there is a problem reading or writing persistent data.
+ */
+public class MqttPersistenceException extends MqttException {
+ private static final long serialVersionUID = 300L;
+
+ /** Persistence is already being used by another client. */
+ public static final short REASON_CODE_PERSISTENCE_IN_USE = 32200;
+
+ /**
+ * Constructs a new MqttPersistenceException
+ */
+ public MqttPersistenceException() {
+ super(REASON_CODE_CLIENT_EXCEPTION);
+ }
+
+ /**
+ * Constructs a new MqttPersistenceException with the specified code
+ * as the underlying reason.
+ * @param reasonCode the reason code for the exception.
+ */
+ public MqttPersistenceException(int reasonCode) {
+ super(reasonCode);
+ }
+ /**
+ * Constructs a new MqttPersistenceException with the specified
+ * Throwable as the underlying reason.
+ * @param cause the underlying cause of the exception.
+ */
+ public MqttPersistenceException(Throwable cause) {
+ super(cause);
+ }
+ /**
+ * Constructs a new MqttPersistenceException with the specified
+ * Throwable as the underlying reason.
+ * @param reason the reason code for the exception.
+ * @param cause the underlying cause of the exception.
+ */
+ public MqttPersistenceException(int reason, Throwable cause) {
+ super(reason, cause);
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttPingSender.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttPingSender.java
new file mode 100644
index 00000000..0735e858
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttPingSender.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+
+package org.eclipse.paho.client.mqttv3;
+
+import org.eclipse.paho.client.mqttv3.internal.ClientComms;
+
+/**
+ * Represents an object used to send ping packet to MQTT broker
+ * every keep alive interval.
+ */
+public interface MqttPingSender {
+
+ /**
+ * Initial method. Pass interal state of current client in.
+ * @param The core of the client, which holds the state information for pending and in-flight messages.
+ */
+ public void init(ClientComms comms);
+
+ /**
+ * Start ping sender. It will be called after connection is success.
+ */
+ public void start();
+
+ /**
+ * Stop ping sender. It is called if there is any errors or connection shutdowns.
+ */
+ public void stop();
+
+ /**
+ * Schedule next ping in certain delay.
+ * @param delay in milliseconds.
+ */
+ public void schedule(long delayInMilliseconds);
+
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttSecurityException.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttSecurityException.java
similarity index 57%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttSecurityException.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttSecurityException.java
index 8ff9eca7..a5791ce7 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttSecurityException.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttSecurityException.java
@@ -1,38 +1,51 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3;
-
-/**
- * Thrown when a client is not authorized to perform an operation, or
- * if there is a problem with the security configuration.
- */
-public class MqttSecurityException extends MqttException {
- private static final long serialVersionUID = 300L;
-
- /**
- * Constructs a new MqttSecurityException with the specified code
- * as the underlying reason.
- * @param reasonCode the reason code for the exception.
- */
- public MqttSecurityException(int reasonCode) {
- super(reasonCode);
- }
-
- /**
- * Constructs a new MqttSecurityException with the specified
- * Throwable as the underlying reason.
- * @param cause the underlying cause of the exception.
- */
- public MqttSecurityException(Throwable cause) {
- super(cause);
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3;
+
+/**
+ * Thrown when a client is not authorized to perform an operation, or
+ * if there is a problem with the security configuration.
+ */
+public class MqttSecurityException extends MqttException {
+ private static final long serialVersionUID = 300L;
+
+ /**
+ * Constructs a new MqttSecurityException with the specified code
+ * as the underlying reason.
+ * @param reasonCode the reason code for the exception.
+ */
+ public MqttSecurityException(int reasonCode) {
+ super(reasonCode);
+ }
+
+ /**
+ * Constructs a new MqttSecurityException with the specified
+ * Throwable as the underlying reason.
+ * @param cause the underlying cause of the exception.
+ */
+ public MqttSecurityException(Throwable cause) {
+ super(cause);
+ }
+ /**
+ * Constructs a new MqttSecurityException with the specified
+ * code and Throwable as the underlying reason.
+ * @param reasonCode the reason code for the exception.
+ * @param cause the underlying cause of the exception.
+ */
+ public MqttSecurityException(int reasonCode, Throwable cause) {
+ super(reasonCode, cause);
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttToken.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttToken.java
new file mode 100644
index 00000000..99b85a32
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttToken.java
@@ -0,0 +1,118 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributions:
+ * Ian Craggs - MQTT 3.1.1 support
+ */
+
+package org.eclipse.paho.client.mqttv3;
+
+import org.eclipse.paho.client.mqttv3.internal.Token;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttWireMessage;
+
+/**
+ * Provides a mechanism for tracking the completion of an asynchronous action.
+ *
+ * A token that implements the ImqttToken interface is returned from all non-blocking
+ * method with the exception of publish.
+ *
+ *
+ * @see IMqttToken
+ */
+
+public class MqttToken implements IMqttToken {
+ /**
+ * A reference to the the class that provides most of the implementation of the
+ * MqttToken. MQTT application programs must not use the internal class.
+ */
+ public Token internalTok = null;
+
+ /*
+ * callback ketika selesai kirim ping
+ */
+ private IMqttPingActionListener pingSentCallback;
+
+ public MqttToken() {
+ }
+
+ public MqttToken(String logContext) {
+ internalTok = new Token(logContext);
+ }
+
+ public MqttException getException() {
+ return internalTok.getException();
+ }
+
+ public boolean isComplete() {
+ return internalTok.isComplete();
+ }
+
+ public void setActionCallback(IMqttActionListener listener) {
+ internalTok.setActionCallback(listener);
+
+ }
+ public IMqttActionListener getActionCallback() {
+ return internalTok.getActionCallback();
+ }
+
+ public void waitForCompletion() throws MqttException {
+ internalTok.waitForCompletion(-1);
+ }
+
+ public void waitForCompletion(long timeout) throws MqttException {
+ internalTok.waitForCompletion(timeout);
+ }
+
+ public IMqttAsyncClient getClient() {
+ return internalTok.getClient();
+ }
+
+ public String[] getTopics() {
+ return internalTok.getTopics();
+ }
+
+ public Object getUserContext() {
+ return internalTok.getUserContext();
+ }
+
+ public void setUserContext(Object userContext) {
+ internalTok.setUserContext(userContext);
+ }
+
+ public int getMessageId() {
+ return internalTok.getMessageID();
+ }
+
+ public int[] getGrantedQos() {
+ return internalTok.getGrantedQos();
+ }
+
+ public boolean getSessionPresent() {
+ return internalTok.getSessionPresent();
+ }
+
+ public MqttWireMessage getResponse() {
+ return internalTok.getResponse();
+ }
+
+ /*
+ * callback untuk ping event
+ */
+ public void setPingCallback(IMqttPingActionListener callback) {
+ internalTok.setActionCallback(callback);
+ this.pingSentCallback = callback;
+ }
+
+ public IMqttPingActionListener getPingSentCallback() {
+ return pingSentCallback;
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttTopic.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttTopic.java
new file mode 100644
index 00000000..a1c28c69
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttTopic.java
@@ -0,0 +1,229 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3;
+
+import java.io.UnsupportedEncodingException;
+
+import org.eclipse.paho.client.mqttv3.internal.ClientComms;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttPublish;
+import org.eclipse.paho.client.mqttv3.util.Strings;
+
+/**
+ * Represents a topic destination, used for publish/subscribe messaging.
+ */
+public class MqttTopic {
+
+ /**
+ * The forward slash (/) is used to separate each level within a topic tree
+ * and provide a hierarchical structure to the topic space. The use of the
+ * topic level separator is significant when the two wildcard characters are
+ * encountered in topics specified by subscribers.
+ */
+ public static final String TOPIC_LEVEL_SEPARATOR = "/";
+
+ /**
+ * Multi-level wildcard The number sign (#) is a wildcard character that
+ * matches any number of levels within a topic.
+ */
+ public static final String MULTI_LEVEL_WILDCARD = "#";
+
+ /**
+ * Single-level wildcard The plus sign (+) is a wildcard character that
+ * matches only one topic level.
+ */
+ public static final String SINGLE_LEVEL_WILDCARD = "+";
+
+ /**
+ * Multi-level wildcard pattern(/#)
+ */
+ public static final String MULTI_LEVEL_WILDCARD_PATTERN = TOPIC_LEVEL_SEPARATOR + MULTI_LEVEL_WILDCARD;
+
+ /**
+ * Topic wildcards (#+)
+ */
+ public static final String TOPIC_WILDCARDS = MULTI_LEVEL_WILDCARD + SINGLE_LEVEL_WILDCARD;
+
+ //topic name and topic filter length range defined in the spec
+ private static final int MIN_TOPIC_LEN = 1;
+ private static final int MAX_TOPIC_LEN = 65535;
+ private static final char NUL = '\u0000';
+
+ private ClientComms comms;
+ private String name;
+
+ public MqttTopic(String name, ClientComms comms) {
+ this.comms = comms;
+ this.name = name;
+ }
+
+ /**
+ * Publishes a message on the topic. This is a convenience method, which will
+ * create a new {@link MqttMessage} object with a byte array payload and the
+ * specified QoS, and then publish it. All other values in the
+ * message will be set to the defaults.
+
+ * @param payload the byte array to use as the payload
+ * @param qos the Quality of Service. Valid values are 0, 1 or 2.
+ * @param retained whether or not this message should be retained by the server.
+ * @throws IllegalArgumentException if value of QoS is not 0, 1 or 2.
+ * @see #publish(MqttMessage)
+ * @see MqttMessage#setQos(int)
+ * @see MqttMessage#setRetained(boolean)
+ */
+ public MqttDeliveryToken publish(byte[] payload, int qos, boolean retained) throws MqttException, MqttPersistenceException {
+ MqttMessage message = new MqttMessage(payload);
+ message.setQos(qos);
+ message.setRetained(retained);
+ return this.publish(message);
+ }
+
+ /**
+ * Publishes the specified message to this topic, but does not wait for delivery
+ * of the message to complete. The returned {@link MqttDeliveryToken token} can be used
+ * to track the delivery status of the message. Once this method has
+ * returned cleanly, the message has been accepted for publication by the
+ * client. Message delivery will be completed in the background when a connection
+ * is available.
+ *
+ * @param message the message to publish
+ * @return an MqttDeliveryToken for tracking the delivery of the message
+ */
+ public MqttDeliveryToken publish(MqttMessage message) throws MqttException, MqttPersistenceException {
+ MqttDeliveryToken token = new MqttDeliveryToken(comms.getClient().getClientId());
+ token.setMessage(message);
+ comms.sendNoWait(createPublish(message), token);
+ token.internalTok.waitUntilSent();
+ return token;
+ }
+
+ /**
+ * Returns the name of the queue or topic.
+ *
+ * @return the name of this destination.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Create a PUBLISH packet from the specified message.
+ */
+ private MqttPublish createPublish(MqttMessage message) {
+ return new MqttPublish(this.getName(), message);
+ }
+
+ /**
+ * Returns a string representation of this topic.
+ * @return a string representation of this topic.
+ */
+ public String toString() {
+ return getName();
+ }
+
+ /**
+ * Validate the topic name or topic filter
+ *
+ * @param topicString topic name or filter
+ * @param wildcardAllowed true if validate topic filter, false otherwise
+ * @throws IllegalArgumentException if the topic is invalid
+ */
+ public static void validate(String topicString, boolean wildcardAllowed) {
+ int topicLen = 0;
+ try {
+ topicLen = topicString.getBytes("UTF-8").length;
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException(e);
+ }
+
+ // Spec: length check
+ // - All Topic Names and Topic Filters MUST be at least one character
+ // long
+ // - Topic Names and Topic Filters are UTF-8 encoded strings, they MUST
+ // NOT encode to more than 65535 bytes
+ if (topicLen < MIN_TOPIC_LEN || topicLen > MAX_TOPIC_LEN) {
+ throw new IllegalArgumentException(String.format("Invalid topic length, should be in range[%d, %d]!",
+ new Object[] { new Integer(MIN_TOPIC_LEN), new Integer(MAX_TOPIC_LEN) }));
+ }
+
+ // *******************************************************************************
+ // 1) This is a topic filter string that can contain wildcard characters
+ // *******************************************************************************
+ if (wildcardAllowed) {
+ // Only # or +
+ if (Strings.equalsAny(topicString, new String[] { MULTI_LEVEL_WILDCARD, SINGLE_LEVEL_WILDCARD })) {
+ return;
+ }
+
+ // 1) Check multi-level wildcard
+ // Rule:
+ // The multi-level wildcard can be specified only on its own or next
+ // to the topic level separator character.
+
+ // - Can only contains one multi-level wildcard character
+ // - The multi-level wildcard must be the last character used within
+ // the topic tree
+ if (Strings.countMatches(topicString, MULTI_LEVEL_WILDCARD) > 1
+ || (topicString.contains(MULTI_LEVEL_WILDCARD) && !topicString
+ .endsWith(MULTI_LEVEL_WILDCARD_PATTERN))) {
+ throw new IllegalArgumentException(
+ "Invalid usage of multi-level wildcard in topic string: "
+ + topicString);
+ }
+
+ // 2) Check single-level wildcard
+ // Rule:
+ // The single-level wildcard can be used at any level in the topic
+ // tree, and in conjunction with the
+ // multilevel wildcard. It must be used next to the topic level
+ // separator, except when it is specified on
+ // its own.
+ validateSingleLevelWildcard(topicString);
+
+ return;
+ }
+
+ // *******************************************************************************
+ // 2) This is a topic name string that MUST NOT contains any wildcard characters
+ // *******************************************************************************
+ if (Strings.containsAny(topicString, TOPIC_WILDCARDS)) {
+ throw new IllegalArgumentException(
+ "The topic name MUST NOT contain any wildcard characters (#+)");
+ }
+ }
+
+ private static void validateSingleLevelWildcard(String topicString) {
+ char singleLevelWildcardChar = SINGLE_LEVEL_WILDCARD.charAt(0);
+ char topicLevelSeparatorChar = TOPIC_LEVEL_SEPARATOR.charAt(0);
+
+ char[] chars = topicString.toCharArray();
+ int length = chars.length;
+ char prev = NUL, next = NUL;
+ for (int i = 0; i < length; i++) {
+ prev = (i - 1 >= 0) ? chars[i - 1] : NUL;
+ next = (i + 1 < length) ? chars[i + 1] : NUL;
+
+ if (chars[i] == singleLevelWildcardChar) {
+ // prev and next can be only '/' or none
+ if (prev != topicLevelSeparatorChar && prev != NUL || next != topicLevelSeparatorChar && next != NUL) {
+ throw new IllegalArgumentException(String.format(
+ "Invalid usage of single-level wildcard in topic string '%s'!",
+ new Object[] { topicString }));
+ }
+ }
+ }
+ }
+
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/TimerPingSender.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/TimerPingSender.java
new file mode 100644
index 00000000..3c8879aa
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/TimerPingSender.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+
+package org.eclipse.paho.client.mqttv3;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+import org.eclipse.paho.client.mqttv3.internal.ClientComms;
+import org.eclipse.paho.client.mqttv3.logging.Logger;
+import org.eclipse.paho.client.mqttv3.logging.LoggerFactory;
+
+/**
+ * Default ping sender implementation
+ *
+ *
This class implements the {@link IMqttPingSender} pinger interface
+ * allowing applications to send ping packet to server every keep alive interval.
+ *
+ *
+ * @see MqttPingSender
+ */
+public class TimerPingSender implements MqttPingSender {
+ private static final String CLASS_NAME = TimerPingSender.class.getName();
+ private static final Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT,CLASS_NAME);
+
+ private ClientComms comms;
+ private Timer timer;
+
+ public void init(ClientComms comms) {
+ if (comms == null) {
+ throw new IllegalArgumentException("ClientComms cannot be null.");
+ }
+ this.comms = comms;
+ }
+
+ public void start() {
+ final String methodName = "start";
+ String clientid = comms.getClient().getClientId();
+
+ //@Trace 659=start timer for client:{0}
+ log.fine(CLASS_NAME, methodName, "659", new Object[]{clientid});
+
+ timer = new Timer("MQTT Ping: " + clientid);
+ //Check ping after first keep alive interval.
+ timer.schedule(new PingTask(), comms.getKeepAlive());
+ }
+
+ public void stop() {
+ final String methodName = "stop";
+ //@Trace 661=stop
+ log.fine(CLASS_NAME, methodName, "661", null);
+ if(timer != null){
+ timer.cancel();
+ }
+ }
+
+ public void schedule(long delayInMilliseconds) {
+ timer.schedule(new PingTask(), delayInMilliseconds);
+ }
+
+ private class PingTask extends TimerTask {
+ private static final String methodName = "PingTask.run";
+
+ public void run() {
+ //@Trace 660=Check schedule at {0}
+ log.fine(CLASS_NAME, methodName, "660", new Object[]{new Long(System.currentTimeMillis())});
+ comms.checkForActivity();
+ }
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ClientDefaults.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ClientDefaults.java
new file mode 100644
index 00000000..9b876c70
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ClientDefaults.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal;
+
+public class ClientDefaults {
+ public static final int MAX_MSG_SIZE = 1024 * 1024 * 256; // 256 MB
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ClientState.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ClientState.java
new file mode 100644
index 00000000..78ed5ccd
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ClientState.java
@@ -0,0 +1,1381 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal;
+
+import java.io.EOFException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.Vector;
+
+import org.eclipse.paho.client.mqttv3.IMqttActionListener;
+import org.eclipse.paho.client.mqttv3.IMqttPingActionListener;
+import org.eclipse.paho.client.mqttv3.MqttClientPersistence;
+import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.eclipse.paho.client.mqttv3.MqttPersistable;
+import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
+import org.eclipse.paho.client.mqttv3.MqttPingSender;
+import org.eclipse.paho.client.mqttv3.MqttToken;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttAck;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttConnack;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttConnect;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttPingReq;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttPingResp;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttPubAck;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttPubComp;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttPubRec;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttPubRel;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttPublish;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttWireMessage;
+import org.eclipse.paho.client.mqttv3.logging.Logger;
+import org.eclipse.paho.client.mqttv3.logging.LoggerFactory;
+
+/**
+ * The core of the client, which holds the state information for pending and
+ * in-flight messages.
+ *
+ * Messages that have been accepted for delivery are moved between several objects
+ * while being delivered.
+ *
+ * 1) When the client is not running messages are stored in a persistent store that
+ * implements the MqttClientPersistent Interface. The default is MqttDefaultFilePersistencew
+ * which stores messages safely across failures and system restarts. If no persistence
+ * is specified there is a fall back to MemoryPersistence which will maintain the messages
+ * while the Mqtt client is instantiated.
+ *
+ * 2) When the client or specifically ClientState is instantiated the messages are
+ * read from the persistent store into:
+ * - outboundqos2 hashtable if a QoS 2 PUBLISH or PUBREL
+ * - outboundqos1 hashtable if a QoS 1 PUBLISH
+ * (see restoreState)
+ *
+ * 3) On Connect, copy messages from the outbound hashtables to the pendingMessages or
+ * pendingFlows vector in messageid order.
+ * - Initial message publish goes onto the pendingmessages buffer.
+ * - PUBREL goes onto the pendingflows buffer
+ * (see restoreInflightMessages)
+ *
+ * 4) Sender thread reads messages from the pendingflows and pendingmessages buffer
+ * one at a time. The message is removed from the pendingbuffer but remains on the
+ * outbound* hashtable. The hashtable is the place where the full set of outstanding
+ * messages are stored in memory. (Persistence is only used at start up)
+ *
+ * 5) Receiver thread - receives wire messages:
+ * - if QoS 1 then remove from persistence and outboundqos1
+ * - if QoS 2 PUBREC send PUBREL. Updating the outboundqos2 entry with the PUBREL
+ * and update persistence.
+ * - if QoS 2 PUBCOMP remove from persistence and outboundqos2
+ *
+ * Notes:
+ * because of the multithreaded nature of the client it is vital that any changes to this
+ * class take concurrency into account. For instance as soon as a flow / message is put on
+ * the wire it is possible for the receiving thread to receive the ack and to be processing
+ * the response before the sending side has finished processing. For instance a connect may
+ * be sent, the conack received before the connect notify send has been processed!
+ *
+ */
+public class ClientState {
+ private static final String CLASS_NAME = ClientState.class.getName();
+ private static final Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT,CLASS_NAME);
+ private static final String PERSISTENCE_SENT_PREFIX = "s-";
+ private static final String PERSISTENCE_CONFIRMED_PREFIX = "sc-";
+ private static final String PERSISTENCE_RECEIVED_PREFIX = "r-";
+
+ private static final int DEFAULT_MAX_INFLIGHT = 20; //Di Rubah dari 10 menjadi 20 karena ada kebutuhan pada client netzme yg memungkinkan pengiriman message lebih dari 10 sekaligus secara bersamaan
+ private static final int MIN_MSG_ID = 1; // Lowest possible MQTT message ID to use
+ private static final int MAX_MSG_ID = 65535; // Highest possible MQTT message ID to use
+ private int nextMsgId = MIN_MSG_ID - 1; // The next available message ID to use
+ private Hashtable inUseMsgIds; // Used to store a set of in-use message IDs
+
+ volatile private Vector pendingMessages;
+ volatile private Vector pendingFlows;
+
+ private CommsTokenStore tokenStore;
+ private ClientComms clientComms = null;
+ private CommsCallback callback = null;
+ private long keepAlive;
+ private boolean cleanSession;
+ private MqttClientPersistence persistence;
+
+ private int maxInflight = DEFAULT_MAX_INFLIGHT;
+ private int actualInFlight = 0;
+ private int inFlightPubRels = 0;
+
+ private Object queueLock = new Object();
+ private Object quiesceLock = new Object();
+ private boolean quiescing = false;
+
+ private long lastOutboundActivity = 0;
+ private long lastInboundActivity = 0;
+ private long lastPing = 0;
+ private MqttWireMessage pingCommand;
+ private Object pingOutstandingLock = new Object();
+ private int pingOutstanding = 0;
+
+ private boolean connected = false;
+
+ private Hashtable outboundQoS2 = null;
+ private Hashtable outboundQoS1 = null;
+ private Hashtable inboundQoS2 = null;
+
+ private MqttPingSender pingSender = null;
+
+ protected ClientState(MqttClientPersistence persistence, CommsTokenStore tokenStore,
+ CommsCallback callback, ClientComms clientComms, MqttPingSender pingSender) throws MqttException {
+
+ log.setResourceName(clientComms.getClient().getClientId());
+ log.finer(CLASS_NAME, "", "" );
+
+ inUseMsgIds = new Hashtable();
+ pendingMessages = new Vector(this.maxInflight);
+ pendingFlows = new Vector();
+ outboundQoS2 = new Hashtable();
+ outboundQoS1 = new Hashtable();
+ inboundQoS2 = new Hashtable();
+ pingCommand = new MqttPingReq();
+ inFlightPubRels = 0;
+ actualInFlight = 0;
+
+ this.persistence = persistence;
+ this.callback = callback;
+ this.tokenStore = tokenStore;
+ this.clientComms = clientComms;
+ this.pingSender = pingSender;
+
+ restoreState();
+ }
+
+ protected void setKeepAliveSecs(long keepAliveSecs) {
+ this.keepAlive = keepAliveSecs*1000;
+ }
+ protected long getKeepAlive() {
+ return this.keepAlive;
+ }
+ protected void setCleanSession(boolean cleanSession) {
+ this.cleanSession = cleanSession;
+ }
+
+ private String getSendPersistenceKey(MqttWireMessage message) {
+ return PERSISTENCE_SENT_PREFIX + message.getMessageId();
+ }
+
+ private String getSendConfirmPersistenceKey(MqttWireMessage message) {
+ return PERSISTENCE_CONFIRMED_PREFIX + message.getMessageId();
+ }
+
+ private String getReceivedPersistenceKey(MqttWireMessage message) {
+ return PERSISTENCE_RECEIVED_PREFIX + message.getMessageId();
+ }
+
+ protected void clearState() throws MqttException {
+ final String methodName = "clearState";
+ //@TRACE 603=clearState
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, ">");
+ }
+
+ persistence.clear();
+ inUseMsgIds.clear();
+ pendingMessages.clear();
+ pendingFlows.clear();
+ outboundQoS2.clear();
+ outboundQoS1.clear();
+ inboundQoS2.clear();
+ tokenStore.clear();
+ }
+
+ private MqttWireMessage restoreMessage(String key, MqttPersistable persistable) throws MqttException {
+ final String methodName = "restoreMessage";
+ MqttWireMessage message = null;
+
+ try {
+ message = MqttWireMessage.createWireMessage(persistable);
+ }
+ catch (MqttException ex) {
+ //@TRACE 602=key={0} exception
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "602", new Object[]{key}, ex);
+ }
+ if (ex.getCause() instanceof EOFException) {
+ // Premature end-of-file means that the message is corrupted
+ if (key != null) {
+ persistence.remove(key);
+ }
+ }
+ else {
+ throw ex;
+ }
+ }
+ //@TRACE 601=key={0} message={1}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "601", new Object[]{key, message});
+ }
+ return message;
+ }
+
+ /**
+ * Inserts a new message to the list, ensuring that list is ordered from lowest to highest in terms of the message id's.
+ * @param list the list to insert the message into
+ * @param newMsg the message to insert into the list
+ */
+ private void insertInOrder(Vector list, MqttWireMessage newMsg) {
+ int newMsgId = newMsg.getMessageId();
+ for (int i = 0; i < list.size(); i++) {
+ MqttWireMessage otherMsg = (MqttWireMessage) list.elementAt(i);
+ int otherMsgId = otherMsg.getMessageId();
+ if (otherMsgId > newMsgId) {
+ list.insertElementAt(newMsg, i);
+ return;
+ }
+ }
+ list.addElement(newMsg);
+ }
+
+ /**
+ * Produces a new list with the messages properly ordered according to their message id's.
+ * @param list the list containing the messages to produce a new reordered list for
+ * - this will not be modified or replaced, i.e., be read-only to this method
+ * @return a new reordered list
+ */
+ private Vector reOrder(Vector list) {
+
+ // here up the new list
+ Vector newList = new Vector();
+
+ if (list.size() == 0) {
+ return newList; // nothing to reorder
+ }
+
+ int previousMsgId = 0;
+ int largestGap = 0;
+ int largestGapMsgIdPosInList = 0;
+ for (int i = 0; i < list.size(); i++) {
+ int currentMsgId = ((MqttWireMessage) list.elementAt(i)).getMessageId();
+ if (currentMsgId - previousMsgId > largestGap) {
+ largestGap = currentMsgId - previousMsgId;
+ largestGapMsgIdPosInList = i;
+ }
+ previousMsgId = currentMsgId;
+ }
+ int lowestMsgId = ((MqttWireMessage) list.elementAt(0)).getMessageId();
+ int highestMsgId = previousMsgId; // last in the sorted list
+
+ // we need to check that the gap after highest msg id to the lowest msg id is not beaten
+ if (MAX_MSG_ID - highestMsgId + lowestMsgId > largestGap) {
+ largestGapMsgIdPosInList = 0;
+ }
+
+ // starting message has been located, let's start from this point on
+ for (int i = largestGapMsgIdPosInList; i < list.size(); i++) {
+ newList.addElement(list.elementAt(i));
+ }
+
+ // and any wrapping back to the beginning
+ for (int i = 0; i < largestGapMsgIdPosInList; i++) {
+ newList.addElement(list.elementAt(i));
+ }
+
+ return newList;
+ }
+
+ /**
+ * Restores the state information from persistence.
+ */
+ protected void restoreState() throws MqttException {
+ final String methodName = "restoreState";
+ Enumeration messageKeys = persistence.keys();
+ MqttPersistable persistable;
+ String key;
+ int highestMsgId = nextMsgId;
+ Vector orphanedPubRels = new Vector();
+ //@TRACE 600=>
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "600");
+ }
+
+ while (messageKeys.hasMoreElements()) {
+ key = (String) messageKeys.nextElement();
+ persistable = persistence.get(key);
+ MqttWireMessage message = restoreMessage(key, persistable);
+ if (message != null) {
+ if (key.startsWith(PERSISTENCE_RECEIVED_PREFIX)) {
+ //@TRACE 604=inbound QoS 2 publish key={0} message={1}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "604", new Object[]{key, message});
+ }
+
+ // The inbound messages that we have persisted will be QoS 2
+ inboundQoS2.put(new Integer(message.getMessageId()),message);
+ } else if (key.startsWith(PERSISTENCE_SENT_PREFIX)) {
+ MqttPublish sendMessage = (MqttPublish) message;
+ highestMsgId = Math.max(sendMessage.getMessageId(), highestMsgId);
+ if (persistence.containsKey(getSendConfirmPersistenceKey(sendMessage))) {
+ MqttPersistable persistedConfirm = persistence.get(getSendConfirmPersistenceKey(sendMessage));
+ // QoS 2, and CONFIRM has already been sent...
+ // NO DUP flag is allowed for 3.1.1 spec while it's not clear for 3.1 spec
+ // So we just remove DUP
+ MqttPubRel confirmMessage = (MqttPubRel) restoreMessage(key, persistedConfirm);
+ if (confirmMessage != null) {
+ // confirmMessage.setDuplicate(true); // REMOVED
+ //@TRACE 605=outbound QoS 2 pubrel key={0} message={1}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "605", new Object[]{key, message});
+ }
+
+ outboundQoS2.put(new Integer(confirmMessage.getMessageId()), confirmMessage);
+ } else {
+ //@TRACE 606=outbound QoS 2 completed key={0} message={1}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "606", new Object[]{key, message});
+ }
+ }
+ } else {
+ // QoS 1 or 2, with no CONFIRM sent...
+ // Put the SEND to the list of pending messages, ensuring message ID ordering...
+ sendMessage.setDuplicate(true);
+ if (sendMessage.getMessage().getQos() == 2) {
+ //@TRACE 607=outbound QoS 2 publish key={0} message={1}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "607", new Object[]{key, message});
+ }
+
+ outboundQoS2.put(new Integer(sendMessage.getMessageId()),sendMessage);
+ } else {
+ //@TRACE 608=outbound QoS 1 publish key={0} message={1}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "608", new Object[]{key, message});
+ }
+
+ outboundQoS1.put(new Integer(sendMessage.getMessageId()),sendMessage);
+ }
+ }
+ MqttDeliveryToken tok = tokenStore.restoreToken(sendMessage);
+ tok.internalTok.setClient(clientComms.getClient());
+ inUseMsgIds.put(new Integer(sendMessage.getMessageId()),new Integer(sendMessage.getMessageId()));
+ }
+ else if (key.startsWith(PERSISTENCE_CONFIRMED_PREFIX)) {
+ MqttPubRel pubRelMessage = (MqttPubRel) message;
+ if (!persistence.containsKey(getSendPersistenceKey(pubRelMessage))) {
+ orphanedPubRels.addElement(key);
+ }
+ }
+ }
+ }
+
+ messageKeys = orphanedPubRels.elements();
+ while(messageKeys.hasMoreElements()) {
+ key = (String) messageKeys.nextElement();
+ //@TRACE 609=removing orphaned pubrel key={0}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "609", new Object[]{key});
+ }
+
+ persistence.remove(key);
+ }
+
+ nextMsgId = highestMsgId;
+ }
+
+ private void restoreInflightMessages() {
+ final String methodName = "restoreInflightMessages";
+ pendingMessages = new Vector(this.maxInflight);
+ pendingFlows = new Vector();
+
+ Enumeration keys = outboundQoS2.keys();
+ while (keys.hasMoreElements()) {
+ Object key = keys.nextElement();
+ MqttWireMessage msg = (MqttWireMessage) outboundQoS2.get(key);
+ if (msg instanceof MqttPublish) {
+ //@TRACE 610=QoS 2 publish key={0}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "610", new Object[]{key});
+ }
+ // set DUP flag only for PUBLISH, but NOT for PUBREL (spec 3.1.1)
+ msg.setDuplicate(true);
+ insertInOrder(pendingMessages, (MqttPublish)msg);
+ } else if (msg instanceof MqttPubRel) {
+ //@TRACE 611=QoS 2 pubrel key={0}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "611", new Object[]{key});
+ }
+
+ insertInOrder(pendingFlows, (MqttPubRel)msg);
+ }
+ }
+ keys = outboundQoS1.keys();
+ while (keys.hasMoreElements()) {
+ Object key = keys.nextElement();
+ MqttPublish msg = (MqttPublish)outboundQoS1.get(key);
+ msg.setDuplicate(true);
+ //@TRACE 612=QoS 1 publish key={0}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "612", new Object[]{key});
+ }
+
+ insertInOrder(pendingMessages, msg);
+ }
+
+ this.pendingFlows = reOrder(pendingFlows);
+ this.pendingMessages = reOrder(pendingMessages);
+ }
+
+ /**
+ * Submits a message for delivery. This method will block until there is
+ * room in the inFlightWindow for the message. The message is put into
+ * persistence before returning.
+ *
+ * @param message the message to send
+ * @param token the token that can be used to track delivery of the message
+ * @throws MqttException
+ */
+ public void send(MqttWireMessage message, MqttToken token) throws MqttException {
+ final String methodName = "send";
+ if (message.isMessageIdRequired() && (message.getMessageId() == 0)) {
+ message.setMessageId(getNextMessageId());
+ }
+ if (token != null ) {
+ try {
+ token.internalTok.setMessageID(message.getMessageId());
+ } catch (Exception e) {
+ }
+ }
+
+ if (message instanceof MqttPublish) {
+ synchronized (queueLock) {
+ if (actualInFlight >= this.maxInflight) {
+ //@TRACE 613= sending {0} msgs at max inflight window
+ log.fine(CLASS_NAME, methodName, "613", new Object[]{new Integer(actualInFlight)});
+
+ throw new MqttException(MqttException.REASON_CODE_MAX_INFLIGHT);
+ }
+
+ MqttMessage innerMessage = ((MqttPublish) message).getMessage();
+ //@TRACE 628=pending publish key={0} qos={1} message={2}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "628", new Object[]{new Integer(message.getMessageId()), new Integer(innerMessage.getQos()), message});
+ }
+
+ switch(innerMessage.getQos()) {
+ case 2:
+ outboundQoS2.put(new Integer(message.getMessageId()), message);
+ persistence.put(getSendPersistenceKey(message), (MqttPublish) message);
+ break;
+ case 1:
+ outboundQoS1.put(new Integer(message.getMessageId()), message);
+ persistence.put(getSendPersistenceKey(message), (MqttPublish) message);
+ break;
+ }
+ tokenStore.saveToken(token, message);
+ pendingMessages.addElement(message);
+ queueLock.notifyAll();
+ }
+ } else {
+ //@TRACE 615=pending send key={0} message {1}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "615", new Object[]{new Integer(message.getMessageId()), message});
+ }
+
+ if (message instanceof MqttConnect) {
+ synchronized (queueLock) {
+ // Add the connect action at the head of the pending queue ensuring it jumps
+ // ahead of any of other pending actions.
+ tokenStore.saveToken(token, message);
+ pendingFlows.insertElementAt(message,0);
+ queueLock.notifyAll();
+ }
+ } else {
+ if (message instanceof MqttPingReq) {
+ this.pingCommand = message;
+ }
+ else if (message instanceof MqttPubRel) {
+ outboundQoS2.put(new Integer(message.getMessageId()), message);
+ persistence.put(getSendConfirmPersistenceKey(message), (MqttPubRel) message);
+ }
+ else if (message instanceof MqttPubComp) {
+ persistence.remove(getReceivedPersistenceKey(message));
+ }
+
+ synchronized (queueLock) {
+ if ( !(message instanceof MqttAck )) {
+ tokenStore.saveToken(token, message);
+ }
+ pendingFlows.addElement(message);
+ queueLock.notifyAll();
+ }
+ }
+ }
+ }
+
+ /**
+ * This removes the MqttSend message from the outbound queue and persistence.
+ * @param message
+ * @throws MqttPersistenceException
+ */
+ protected void undo(MqttPublish message) throws MqttPersistenceException {
+ final String methodName = "undo";
+ synchronized (queueLock) {
+ //@TRACE 618=key={0} QoS={1}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "618", new Object[]{new Integer(message.getMessageId()), new Integer(message.getMessage().getQos())});
+ }
+
+ if (message.getMessage().getQos() == 1) {
+ outboundQoS1.remove(new Integer(message.getMessageId()));
+ } else {
+ outboundQoS2.remove(new Integer(message.getMessageId()));
+ }
+ pendingMessages.removeElement(message);
+ persistence.remove(getSendPersistenceKey(message));
+ tokenStore.removeToken(message);
+ checkQuiesceLock();
+ }
+ }
+
+ /**
+ * Check and send a ping if needed and check for ping timeout.
+ * Need to send a ping if nothing has been sent or received
+ * in the last keepalive interval. It is important to check for
+ * both sent and received packets in order to catch the case where an
+ * app is solely sending QoS 0 messages or receiving QoS 0 messages.
+ * QoS 0 message are not good enough for checking a connection is
+ * alive as they are one way messages.
+ *
+ * If a ping has been sent but no data has been received in the
+ * last keepalive interval then the connection is deamed to be broken.
+ *
+ * @return token of ping command, null if no ping command has been sent.
+ */
+ public MqttToken checkForActivity(IMqttPingActionListener actionListener) throws MqttException {
+ final String methodName = "checkForActivity";
+ //@TRACE 616=checkForActivity entered
+ log.fine(CLASS_NAME,methodName,"616", new Object[]{});
+
+ synchronized (quiesceLock) {
+ // ref bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=440698
+ // No ping while quiescing
+ if (quiescing) {
+ return null;
+ }
+ }
+
+ MqttToken token = null;
+ long nextPingTime = getKeepAlive();
+
+ if (connected && this.keepAlive > 0) {
+ long time = System.currentTimeMillis();
+ //Reduce schedule frequency since System.currentTimeMillis is no accurate, add a buffer
+ //It is 1/10 in minimum keepalive unit.
+ int delta = 100;
+
+ // ref bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=446663
+ synchronized (pingOutstandingLock) {
+
+ // Is the broker connection lost because the broker did not reply to my ping?
+ if (pingOutstanding > 0 && (time - lastInboundActivity >= keepAlive + delta)) {
+ // lastInboundActivity will be updated once receiving is done.
+ // Add a delta, since the timer and System.currentTimeMillis() is not accurate.
+ // A ping is outstanding but no packet has been received in KA so connection is deemed broken
+ //@TRACE 619=Timed out as no activity, keepAlive={0} lastOutboundActivity={1} lastInboundActivity={2} time={3} lastPing={4}
+ if(log.isLoggable(Logger.FINE)) {
+ log.severe(CLASS_NAME, methodName, "619", new Object[]{new Long(this.keepAlive), new Long(lastOutboundActivity), new Long(lastInboundActivity), new Long(time), new Long(lastPing)});
+ }
+
+ // A ping has already been sent. At this point, assume that the
+ // broker has hung and the TCP layer hasn't noticed.
+ throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_CLIENT_TIMEOUT);
+ }
+
+ // Is the broker connection lost because I could not get any successful write for 2 keepAlive intervals?
+ if (pingOutstanding == 0 && (time - lastOutboundActivity >= 2*keepAlive)) {
+
+ // I am probably blocked on a write operations as I should have been able to write at least a ping message
+ if(log.isLoggable(Logger.FINE)) {
+ log.severe(CLASS_NAME, methodName, "642", new Object[]{new Long(this.keepAlive), new Long(lastOutboundActivity), new Long(lastInboundActivity), new Long(time), new Long(lastPing)});
+ }
+
+ // A ping has not been sent but I am not progressing on the current write operation.
+ // At this point, assume that the broker has hung and the TCP layer hasn't noticed.
+ throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_WRITE_TIMEOUT);
+ }
+
+ // 1. Is a ping required by the client to verify whether the broker is down?
+ // Condition: ((pingOutstanding == 0 && (time - lastInboundActivity >= keepAlive + delta)))
+ // In this case only one ping is sent. If not confirmed, client will assume a lost connection to the broker.
+ // 2. Is a ping required by the broker to keep the client alive?
+ // Condition: (time - lastOutboundActivity >= keepAlive - delta)
+ // In this case more than one ping outstanding may be necessary.
+ // This would be the case when receiving a large message;
+ // the broker needs to keep receiving a regular ping even if the ping response are queued after the long message
+ // If lacking to do so, the broker will consider my connection lost and cut my socket.
+ if ((pingOutstanding == 0 && (time - lastInboundActivity >= keepAlive - delta)) ||
+ (time - lastOutboundActivity >= keepAlive - delta)) {
+
+ //@TRACE 620=ping needed. keepAlive={0} lastOutboundActivity={1} lastInboundActivity={2}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "620", new Object[]{new Long(this.keepAlive), new Long(lastOutboundActivity), new Long(lastInboundActivity)});
+ }
+
+ // pingOutstanding++; // it will be set after the ping has been written on the wire
+ // lastPing = time; // it will be set after the ping has been written on the wire
+ token = new MqttToken(clientComms.getClient().getClientId());
+
+ //set callback di pindah dari ping sender untuk memastikan callback selalu terpanggil
+ if(actionListener != null) {
+ token.setPingCallback(actionListener);
+ }
+
+ tokenStore.saveToken(token, pingCommand);
+ pendingFlows.insertElementAt(pingCommand, 0);
+
+ nextPingTime = getKeepAlive();
+
+ //Wake sender thread since it may be in wait state (in ClientState.get())
+ notifyQueueLock();
+ }
+ else {
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "634", null);
+ }
+ nextPingTime = Math.max(1, getKeepAlive() - (time - lastOutboundActivity));
+ }
+ }
+ //@TRACE 624=Schedule next ping at {0}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "624", new Object[]{new Long(nextPingTime)});
+ }
+ pingSender.schedule(nextPingTime);
+ }
+
+ return token;
+ }
+
+ /**
+ * This returns the next piece of work, ie message, for the CommsSender
+ * to send over the network.
+ * Calls to this method block until either:
+ * - there is a message to be sent
+ * - the keepAlive interval is exceeded, which triggers a ping message
+ * to be returned
+ * - {@link #disconnected(MqttException, boolean)} is called
+ * @return the next message to send, or null if the client is disconnected
+ */
+ protected MqttWireMessage get() throws MqttException {
+ final String methodName = "get";
+ MqttWireMessage result = null;
+
+ synchronized (queueLock) {
+ while (result == null) {
+
+ // If there is no work wait until there is work.
+ // If the inflight window is full and no flows are pending wait until space is freed.
+ // In both cases queueLock will be notified.
+ if ((pendingMessages.isEmpty() && pendingFlows.isEmpty()) ||
+ (pendingFlows.isEmpty() && actualInFlight >= this.maxInflight)) {
+ try {
+ //@TRACE 644=wait for new work or for space in the inflight window
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "644");
+ }
+
+ queueLock.wait();
+
+ //@TRACE 647=new work or ping arrived
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "647");
+ }
+ } catch (InterruptedException e) {
+ }
+ }
+
+ // Handle the case where not connected. This should only be the case if:
+ // - in the process of disconnecting / shutting down
+ // - in the process of connecting
+ if (!connected &&
+ (pendingFlows.isEmpty() || !((MqttWireMessage)pendingFlows.elementAt(0) instanceof MqttConnect))) {
+ //@TRACE 621=no outstanding flows and not connected
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "621");
+ }
+
+ return null;
+ }
+
+ // Check if there is a need to send a ping to keep the session alive.
+ // Note this check is done before processing messages. If not done first
+ // an app that only publishes QoS 0 messages will prevent keepalive processing
+ // from functioning.
+// checkForActivity(); //Use pinger, don't check here
+
+ // Now process any queued flows or messages
+ if (!pendingFlows.isEmpty()) {
+ // Process the first "flow" in the queue
+ result = (MqttWireMessage)pendingFlows.remove(0);
+ if (result instanceof MqttPubRel) {
+ inFlightPubRels++;
+
+ //@TRACE 617=+1 inflightpubrels={0}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "617", new Object[]{new Integer(inFlightPubRels)});
+ }
+ }
+
+ checkQuiesceLock();
+ } else if (!pendingMessages.isEmpty()) {
+ // If the inflight window is full then messages are not
+ // processed until the inflight window has space.
+ if (actualInFlight < this.maxInflight) {
+ // The in flight window is not full so process the
+ // first message in the queue
+ result = (MqttWireMessage)pendingMessages.elementAt(0);
+ pendingMessages.removeElementAt(0);
+ actualInFlight++;
+
+ //@TRACE 623=+1 actualInFlight={0}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "623", new Object[]{new Integer(actualInFlight)});
+ }
+ } else {
+ //@TRACE 622=inflight window full
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "622");
+ }
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ public void setKeepAliveInterval(long interval) {
+ this.keepAlive = interval;
+ }
+
+ /**
+ * COMMENTED OUT AS NO LONGER USED.
+ * Deduce how long to to wait until a ping is required.
+ *
+ * In order to keep the connection alive the server must see activity within
+ * the keepalive interval. If the application is not sending / receiving
+ * any messages then the client will send a ping. This method works out
+ * the next time that a ping must be sent in order for the server to
+ * know the client is alive.
+ * @return time before a ping needs to be sent to keep alive the connection
+ long getTimeUntilPing() {
+ long pingin = getKeepAlive();
+ // If KA is zero which means just wait for work or
+ // if a ping is outstanding return the KA value
+ if (connected && (getKeepAlive() > 0) && !pingOutstanding) {
+
+ long time = System.currentTimeMillis();
+ long timeSinceOut = (time-lastOutboundActivity);
+ long timeSinceIn = (time-lastInboundActivity);
+
+ if (timeSinceOut > timeSinceIn) {
+ pingin = (getKeepAlive()-timeSinceOut);
+ } else {
+ pingin = (getKeepAlive()-timeSinceIn);
+ }
+
+ // Unlikely to be negative or zero but in the case it is return a
+ // small value > 0 to cause a ping to occur
+ if (pingin <= 0) {
+ pingin = 10;
+ }
+ }
+ return (pingin);
+ }
+ */
+
+ public void notifySentBytes(int sentBytesCount) {
+ final String methodName = "notifySentBytes";
+ if (sentBytesCount > 0) {
+ this.lastOutboundActivity = System.currentTimeMillis();
+ }
+ // @TRACE 631=sent bytes count={0}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "631", new Object[]{
+ new Integer(sentBytesCount)});
+ }
+ }
+
+
+ /**
+ * Called by the CommsSender when a message has been sent
+ * @param message
+ */
+ protected void notifySent(MqttWireMessage message) {
+ final String methodName = "notifySent";
+
+ this.lastOutboundActivity = System.currentTimeMillis();
+ //@TRACE 625=key={0}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "625", new Object[]{message.getKey()});
+ }
+
+ MqttToken token = tokenStore.getToken(message);
+ token.internalTok.notifySent();
+ if (message instanceof MqttPingReq) {
+ synchronized (pingOutstandingLock) {
+ long time = System.currentTimeMillis();
+ synchronized (pingOutstandingLock) {
+ lastPing = time;
+ pingOutstanding++;
+ }
+ //@TRACE 635=ping sent. pingOutstanding: {0}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "635", new Object[]{new Integer(pingOutstanding)});
+ }
+
+ //notify ping terkirim untuk melepas wakelock
+ IMqttPingActionListener callback = token.getPingSentCallback();
+ if (callback != null) {
+ callback.onPingSent(token);
+ }
+ }
+ }
+ else if (message instanceof MqttPublish) {
+ if (((MqttPublish)message).getMessage().getQos() == 0) {
+ // once a QoS 0 message is sent we can clean up its records straight away as
+ // we won't be hearing about it again
+ token.internalTok.markComplete(null, null);
+ callback.asyncOperationComplete(token);
+ decrementInFlight();
+ releaseMessageId(message.getMessageId());
+ tokenStore.removeToken(message);
+ checkQuiesceLock();
+ }
+ }
+ }
+
+ private void decrementInFlight() {
+ final String methodName = "decrementInFlight";
+ synchronized (queueLock) {
+ actualInFlight--;
+ //@TRACE 646=-1 actualInFlight={0}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "646", new Object[]{new Integer(actualInFlight)});
+ }
+
+ if (!checkQuiesceLock()) {
+ queueLock.notifyAll();
+ }
+ }
+ }
+
+ protected boolean checkQuiesceLock() {
+ final String methodName = "checkQuiesceLock";
+// if (quiescing && actualInFlight == 0 && pendingFlows.size() == 0 && inFlightPubRels == 0 && callback.isQuiesced()) {
+ int tokC = tokenStore.count();
+ if (quiescing && tokC == 0 && pendingFlows.size() == 0 && callback.isQuiesced()) {
+ //@TRACE 626=quiescing={0} actualInFlight={1} pendingFlows={2} inFlightPubRels={3} callbackQuiesce={4} tokens={5}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "626", new Object[]{new Boolean(quiescing), new Integer(actualInFlight), new Integer(pendingFlows.size()), new Integer(inFlightPubRels), Boolean.valueOf(callback.isQuiesced()), new Integer(tokC)});
+ }
+
+ synchronized (quiesceLock) {
+ quiesceLock.notifyAll();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public void notifyReceivedBytes(int receivedBytesCount) {
+ final String methodName = "notifyReceivedBytes";
+ if (receivedBytesCount > 0) {
+ this.lastInboundActivity = System.currentTimeMillis();
+ }
+ // @TRACE 630=received bytes count={0}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "630", new Object[]{
+ new Integer(receivedBytesCount)});
+ }
+ }
+
+ /**
+ * Called by the CommsReceiver when an ack has arrived.
+ *
+ * @param message
+ * @throws MqttException
+ */
+ protected void notifyReceivedAck(MqttAck ack) throws MqttException {
+ final String methodName = "notifyReceivedAck";
+ this.lastInboundActivity = System.currentTimeMillis();
+
+ // @TRACE 627=received key={0} message={1}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "627", new Object[]{
+ new Integer(ack.getMessageId()), ack});
+ }
+
+ MqttToken token = tokenStore.getToken(ack);
+ MqttException mex = null;
+
+ if (ack instanceof MqttPubRec) {
+ // Complete the QoS 2 flow. Unlike all other
+ // flows, QoS is a 2 phase flow. The second phase sends a
+ // PUBREL - the operation is not complete until a PUBCOMP
+ // is received
+ MqttPubRel rel = new MqttPubRel((MqttPubRec) ack);
+ this.send(rel, token);
+ } else if (ack instanceof MqttPubAck || ack instanceof MqttPubComp) {
+ // QoS 1 & 2 notify users of result before removing from
+ // persistence
+ notifyResult(ack, token, mex);
+ // Do not remove publish / delivery token at this stage
+ // do this when the persistence is removed later
+ } else if (ack instanceof MqttPingResp) {
+ synchronized (pingOutstandingLock) {
+ pingOutstanding = Math.max(0, pingOutstanding-1);
+ notifyResult(ack, token, mex);
+ if (pingOutstanding == 0) {
+ tokenStore.removeToken(ack);
+ }
+ }
+ //@TRACE 636=ping response received. pingOutstanding: {0}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "636", new Object[]{new Integer(pingOutstanding)});
+ }
+ } else if (ack instanceof MqttConnack) {
+ int rc = ((MqttConnack) ack).getReturnCode();
+ if (rc == 0) {
+ synchronized (queueLock) {
+ if (cleanSession) {
+ clearState();
+ // Add the connect token back in so that users can be
+ // notified when connect completes.
+ tokenStore.saveToken(token,ack);
+ }
+ inFlightPubRels = 0;
+ actualInFlight = 0;
+ restoreInflightMessages();
+ connected();
+ }
+ } else {
+ mex = ExceptionHelper.createMqttException(rc);
+ throw mex;
+ }
+
+ clientComms.connectComplete((MqttConnack) ack, mex);
+ notifyResult(ack, token, mex);
+ tokenStore.removeToken(ack);
+
+ // Notify the sender thread that there maybe work for it to do now
+ synchronized (queueLock) {
+ queueLock.notifyAll();
+ }
+ } else {
+ // Sub ack or unsuback
+ notifyResult(ack, token, mex);
+ releaseMessageId(ack.getMessageId());
+ tokenStore.removeToken(ack);
+ }
+
+ checkQuiesceLock();
+ }
+
+ /**
+ * Called by the CommsReceiver when a message has been received.
+ * Handles inbound messages and other flows such as PUBREL.
+ *
+ * @param message
+ * @throws MqttException
+ */
+ protected void notifyReceivedMsg(MqttWireMessage message) throws MqttException {
+ final String methodName = "notifyReceivedMsg";
+ this.lastInboundActivity = System.currentTimeMillis();
+
+ // @TRACE 651=received key={0} message={1}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "651", new Object[]{
+ new Integer(message.getMessageId()), message});
+ }
+
+ if (!quiescing) {
+ if (message instanceof MqttPublish) {
+ MqttPublish send = (MqttPublish) message;
+ switch (send.getMessage().getQos()) {
+ case 0:
+ case 1:
+ if (callback != null) {
+ callback.messageArrived(send);
+ }
+ break;
+ case 2:
+ persistence.put(getReceivedPersistenceKey(message),
+ (MqttPublish) message);
+ inboundQoS2.put(new Integer(send.getMessageId()), send);
+ this.send(new MqttPubRec(send), null);
+ break;
+
+ default:
+ //should NOT reach here
+ }
+ } else if (message instanceof MqttPubRel) {
+ MqttPublish sendMsg = (MqttPublish) inboundQoS2
+ .get(new Integer(message.getMessageId()));
+ if (sendMsg != null) {
+ if (callback != null) {
+ callback.messageArrived(sendMsg);
+ }
+ } else {
+ // Original publish has already been delivered.
+ MqttPubComp pubComp = new MqttPubComp(message
+ .getMessageId());
+ this.send(pubComp, null);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Called when waiters and callbacks have processed the message. For
+ * messages where delivery is complete the message can be removed from
+ * persistence and counters adjusted accordingly. Also tidy up by removing
+ * token from store...
+ *
+ * @param message
+ * @throws MqttException
+ */
+ protected void notifyComplete(MqttToken token) throws MqttException {
+ final String methodName = "notifyComplete";
+
+ MqttWireMessage message = token.internalTok.getWireMessage();
+
+ if (message != null && message instanceof MqttAck) {
+ // @TRACE 629=received key={0} token={1} message={2}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "629", new Object[]{
+ new Integer(message.getMessageId()), token, message});
+ }
+
+ MqttAck ack = (MqttAck) message;
+
+ if (ack instanceof MqttPubAck) {
+ // QoS 1 - user notified now remove from persistence...
+ persistence.remove(getSendPersistenceKey(message));
+ outboundQoS1.remove(new Integer(ack.getMessageId()));
+ decrementInFlight();
+ releaseMessageId(message.getMessageId());
+ tokenStore.removeToken(message);
+ // @TRACE 650=removed Qos 1 publish. key={0}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "650",
+ new Object[]{new Integer(ack.getMessageId())});
+ }
+ } else if (ack instanceof MqttPubComp) {
+ // QoS 2 - user notified now remove from persistence...
+ persistence.remove(getSendPersistenceKey(message));
+ persistence.remove(getSendConfirmPersistenceKey(message));
+ outboundQoS2.remove(new Integer(ack.getMessageId()));
+
+ inFlightPubRels--;
+ decrementInFlight();
+ releaseMessageId(message.getMessageId());
+ tokenStore.removeToken(message);
+
+ // @TRACE 645=removed QoS 2 publish/pubrel. key={0}, -1 inFlightPubRels={1}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "645", new Object[]{
+ new Integer(ack.getMessageId()),
+ new Integer(inFlightPubRels)});
+ }
+ }
+
+ checkQuiesceLock();
+ }
+ }
+
+ protected void notifyResult(MqttWireMessage ack, MqttToken token, MqttException ex) {
+ final String methodName = "notifyResult";
+ // unblock any threads waiting on the token
+ token.internalTok.markComplete(ack, ex);
+
+ // Let the user know an async operation has completed and then remove the token
+ if (ack != null && ack instanceof MqttAck && !(ack instanceof MqttPubRec)) {
+ //@TRACE 648=key{0}, msg={1}, excep={2}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "648", new Object[]{token.internalTok.getKey(), ack, ex});
+ }
+ callback.asyncOperationComplete(token);
+ }
+ // There are cases where there is no ack as the operation failed before
+ // an ack was received
+ if (ack == null ) {
+ //@TRACE 649=key={0},excep={1}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "649", new Object[]{token.internalTok.getKey(), ex});
+ }
+ callback.asyncOperationComplete(token);
+ }
+ }
+
+ /**
+ * Called when the client has successfully connected to the broker
+ */
+ public void connected() {
+ final String methodName = "connected";
+ //@TRACE 631=connected
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "631");
+ }
+ this.connected = true;
+
+ pingSender.start(); //Start ping thread when client connected to server.
+ }
+
+ /**
+ * Called during shutdown to work out if there are any tokens still
+ * to be notified and waiters to be unblocked. Notifying and unblocking
+ * takes place after most shutdown processing has completed. The tokenstore
+ * is tidied up so it only contains outstanding delivery tokens which are
+ * valid after reconnect (if clean session is false)
+ * @param reason The root cause of the disconnection, or null if it is a clean disconnect
+ */
+ public Vector resolveOldTokens(MqttException reason) {
+ final String methodName = "resolveOldTokens";
+ //@TRACE 632=reason {0}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "632", new Object[]{reason});
+ }
+
+ // If any outstanding let the user know the reason why it is still
+ // outstanding by putting the reason shutdown is occurring into the
+ // token.
+ MqttException shutReason = reason;
+ if (reason == null) {
+ shutReason = new MqttException(MqttException.REASON_CODE_CLIENT_DISCONNECTING);
+ }
+
+ // Set the token up so it is ready to be notified after disconnect
+ // processing has completed. Do not
+ // remove the token from the store if it is a delivery token, it is
+ // valid after a reconnect.
+ Vector outT = tokenStore.getOutstandingTokens();
+ Enumeration outTE = outT.elements();
+ while (outTE.hasMoreElements()) {
+ MqttToken tok = (MqttToken)outTE.nextElement();
+ synchronized (tok) {
+ if (!tok.isComplete() && !tok.internalTok.isCompletePending() && tok.getException() == null) {
+ tok.internalTok.setException(shutReason);
+ }
+ }
+ if (!(tok instanceof MqttDeliveryToken)) {
+ // If not a delivery token it is not valid on
+ // restart so remove
+ tokenStore.removeToken(tok.internalTok.getKey());
+ }
+ }
+ return outT;
+ }
+
+ /**
+ * Called when the client has been disconnected from the broker.
+ * @param reason The root cause of the disconnection, or null if it is a clean disconnect
+ */
+ public void disconnected(MqttException reason) {
+ final String methodName = "disconnected";
+ //@TRACE 633=disconnected
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "633", new Object[]{reason});
+ }
+
+ this.connected = false;
+
+ try {
+ if (cleanSession) {
+ clearState();
+ }
+
+ pendingMessages.clear();
+ pendingFlows.clear();
+ synchronized (pingOutstandingLock) {
+ // Reset pingOutstanding to allow reconnects to assume no previous ping.
+ pingOutstanding = 0;
+ }
+ } catch (MqttException e) {
+ // Ignore as we have disconnected at this point
+ }
+ }
+
+ /**
+ * Releases a message ID back into the pool of available message IDs.
+ * If the supplied message ID is not in use, then nothing will happen.
+ *
+ * @param msgId A message ID that can be freed up for re-use.
+ */
+ private synchronized void releaseMessageId(int msgId) {
+ inUseMsgIds.remove(new Integer(msgId));
+ }
+
+ /**
+ * Get the next MQTT message ID that is not already in use, and marks
+ * it as now being in use.
+ *
+ * @return the next MQTT message ID to use
+ */
+ private synchronized int getNextMessageId() throws MqttException {
+ int startingMessageId = nextMsgId;
+ // Allow two complete passes of the message ID range. This gives
+ // any asynchronous releases a chance to occur
+ int loopCount = 0;
+ do {
+ nextMsgId++;
+ if ( nextMsgId > MAX_MSG_ID ) {
+ nextMsgId = MIN_MSG_ID;
+ }
+ if (nextMsgId == startingMessageId) {
+ loopCount++;
+ if (loopCount == 2) {
+ throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_NO_MESSAGE_IDS_AVAILABLE);
+ }
+ }
+ } while( inUseMsgIds.containsKey( new Integer(nextMsgId) ) );
+ Integer id = new Integer(nextMsgId);
+ inUseMsgIds.put(id, id);
+ return nextMsgId;
+ }
+
+ /**
+ * Quiesce the client state, preventing any new messages getting sent,
+ * and preventing the callback on any newly received messages.
+ * After the timeout expires, delete any pending messages except for
+ * outbound ACKs, and wait for those ACKs to complete.
+ */
+ public void quiesce(long timeout) {
+ final String methodName = "quiesce";
+ // If the timeout is greater than zero t
+ if (timeout > 0 ) {
+ //@TRACE 637=timeout={0}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "637", new Object[]{new Long(timeout)});
+ }
+ synchronized (queueLock) {
+ this.quiescing = true;
+ }
+ // We don't want to handle any new inbound messages
+ callback.quiesce();
+ notifyQueueLock();
+
+ synchronized (quiesceLock) {
+ try {
+ // If token count is not zero there is outbound work to process and
+ // if pending flows is not zero there is outstanding work to complete and
+ // if call back is not quiseced there it needs to complete.
+ int tokc = tokenStore.count();
+ if (tokc > 0 || pendingFlows.size() >0 || !callback.isQuiesced()) {
+ //@TRACE 639=wait for outstanding: actualInFlight={0} pendingFlows={1} inFlightPubRels={2} tokens={3}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "639", new Object[]{new Integer(actualInFlight), new Integer(pendingFlows.size()), new Integer(inFlightPubRels), new Integer(tokc)});
+ }
+
+ // wait for outstanding in flight messages to complete and
+ // any pending flows to complete
+ quiesceLock.wait(timeout);
+ }
+ }
+ catch (InterruptedException ex) {
+ // Don't care, as we're shutting down anyway
+ }
+ }
+
+ // Quiesce time up or inflight messages delivered. Ensure pending delivery
+ // vectors are cleared ready for disconnect to be sent as the final flow.
+ synchronized (queueLock) {
+ pendingMessages.clear();
+ pendingFlows.clear();
+ quiescing = false;
+ actualInFlight = 0;
+ }
+ //@TRACE 640=finished
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "640");
+ }
+ }
+ }
+
+ public void notifyQueueLock() {
+ final String methodName = "notifyQueueLock";
+ synchronized (queueLock) {
+ //@TRACE 638=notifying queueLock holders
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "638");
+ }
+ queueLock.notifyAll();
+ }
+ }
+
+ protected void deliveryComplete(MqttPublish message) throws MqttPersistenceException {
+ final String methodName = "deliveryComplete";
+
+ //@TRACE 641=remove publish from persistence. key={0}
+ if(log.isLoggable(Logger.FINE)) {
+ log.fine(CLASS_NAME, methodName, "641", new Object[]{new Integer(message.getMessageId())});
+ }
+
+ persistence.remove(getReceivedPersistenceKey(message));
+ inboundQoS2.remove(new Integer(message.getMessageId()));
+ }
+
+ /**
+ * Tidy up
+ * - ensure that tokens are released as they are maintained over a
+ * disconnect / connect cycle.
+ */
+ protected void close() {
+ inUseMsgIds.clear();
+ pendingMessages.clear();
+ pendingFlows.clear();
+ outboundQoS2.clear();
+ outboundQoS1.clear();
+ inboundQoS2.clear();
+ tokenStore.clear();
+ inUseMsgIds = null;
+ pendingMessages = null;
+ pendingFlows = null;
+ outboundQoS2 = null;
+ outboundQoS1 = null;
+ inboundQoS2 = null;
+ tokenStore = null;
+ callback = null;
+ clientComms = null;
+ persistence = null;
+ pingCommand = null;
+ }
+
+ public Properties getDebug() {
+ Properties props = new Properties();
+ props.put("In use msgids", inUseMsgIds);
+ props.put("pendingMessages", pendingMessages);
+ props.put("pendingFlows", pendingFlows);
+ props.put("maxInflight", new Integer(maxInflight));
+ props.put("nextMsgID", new Integer(nextMsgId));
+ props.put("actualInFlight", new Integer(actualInFlight));
+ props.put("inFlightPubRels", new Integer(inFlightPubRels));
+ props.put("quiescing", Boolean.valueOf(quiescing));
+ props.put("pingoutstanding", new Integer(pingOutstanding));
+ props.put("lastOutboundActivity", new Long(lastOutboundActivity));
+ props.put("lastInboundActivity", new Long(lastInboundActivity));
+ props.put("outboundQoS2", outboundQoS2);
+ props.put("outboundQoS1", outboundQoS1);
+ props.put("inboundQoS2", inboundQoS2);
+ props.put("tokens", tokenStore);
+ return props;
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsCallback.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsCallback.java
new file mode 100644
index 00000000..89c13ac7
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsCallback.java
@@ -0,0 +1,400 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal;
+
+import java.util.Vector;
+
+import org.eclipse.paho.client.mqttv3.IMqttActionListener;
+import org.eclipse.paho.client.mqttv3.MqttCallback;
+import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttToken;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttPubAck;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttPubComp;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttPublish;
+import org.eclipse.paho.client.mqttv3.logging.Logger;
+import org.eclipse.paho.client.mqttv3.logging.LoggerFactory;
+
+/**
+ * Bridge between Receiver and the external API. This class gets called by
+ * Receiver, and then converts the comms-centric MQTT message objects into ones
+ * understood by the external API.
+ */
+public class CommsCallback implements Runnable {
+ private static final String CLASS_NAME = CommsCallback.class.getName();
+ private static final Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME);
+
+ private static final int INBOUND_QUEUE_SIZE = 10;
+ private MqttCallback mqttCallback;
+ private ClientComms clientComms;
+ private Vector messageQueue;
+ private Vector completeQueue;
+ public boolean running = false;
+ private boolean quiescing = false;
+ private Object lifecycle = new Object();
+ private Thread callbackThread;
+ private Object workAvailable = new Object();
+ private Object spaceAvailable = new Object();
+ private ClientState clientState;
+
+ CommsCallback(ClientComms clientComms) {
+ this.clientComms = clientComms;
+ this.messageQueue = new Vector(INBOUND_QUEUE_SIZE);
+ this.completeQueue = new Vector(INBOUND_QUEUE_SIZE);
+ log.setResourceName(clientComms.getClient().getClientId());
+ }
+
+ public void setClientState(ClientState clientState) {
+ this.clientState = clientState;
+ }
+
+ /**
+ * Starts up the Callback thread.
+ */
+ public void start(String threadName) {
+ synchronized (lifecycle) {
+ if (!running) {
+ // Praparatory work before starting the background thread.
+ // For safety ensure any old events are cleared.
+ messageQueue.clear();
+ completeQueue.clear();
+
+ running = true;
+ quiescing = false;
+ callbackThread = new Thread(this, threadName);
+ callbackThread.start();
+ }
+ }
+ }
+
+ /**
+ * Stops the callback thread.
+ * This call will block until stop has completed.
+ */
+ public void stop() {
+ final String methodName = "stop";
+ synchronized (lifecycle) {
+ if (running) {
+ // @TRACE 700=stopping
+ log.fine(CLASS_NAME, methodName, "700");
+ running = false;
+ if (!Thread.currentThread().equals(callbackThread)) {
+ try {
+ synchronized (workAvailable) {
+ // @TRACE 701=notify workAvailable and wait for run
+ // to finish
+ log.fine(CLASS_NAME, methodName, "701");
+ workAvailable.notifyAll();
+ }
+ // Wait for the thread to finish.
+ callbackThread.join();
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ callbackThread = null;
+ // @TRACE 703=stopped
+ log.fine(CLASS_NAME, methodName, "703");
+ }
+ }
+
+ public void setCallback(MqttCallback mqttCallback) {
+ this.mqttCallback = mqttCallback;
+ }
+
+ public void run() {
+ final String methodName = "run";
+ while (running) {
+ try {
+ // If no work is currently available, then wait until there is some...
+ try {
+ synchronized (workAvailable) {
+ if (running && messageQueue.isEmpty()
+ && completeQueue.isEmpty()) {
+ // @TRACE 704=wait for workAvailable
+ log.fine(CLASS_NAME, methodName, "704");
+ workAvailable.wait();
+ }
+ }
+ } catch (InterruptedException e) {
+ }
+
+ if (running) {
+ // Check for deliveryComplete callbacks...
+ MqttToken token = null;
+ synchronized (completeQueue) {
+ if (!completeQueue.isEmpty()) {
+ // First call the delivery arrived callback if needed
+ token = (MqttToken) completeQueue.elementAt(0);
+ completeQueue.removeElementAt(0);
+ }
+ }
+ if (null != token) {
+ handleActionComplete(token);
+ }
+
+ // Check for messageArrived callbacks...
+ MqttPublish message = null;
+ synchronized (messageQueue) {
+ if (!messageQueue.isEmpty()) {
+ // Note, there is a window on connect where a publish
+ // could arrive before we've
+ // finished the connect logic.
+ message = (MqttPublish) messageQueue.elementAt(0);
+
+ messageQueue.removeElementAt(0);
+ }
+ }
+ if (null != message) {
+ handleMessage(message);
+ }
+ }
+
+ if (quiescing) {
+ clientState.checkQuiesceLock();
+ }
+
+ } catch (Throwable ex) {
+ // Users code could throw an Error or Exception e.g. in the case
+ // of class NoClassDefFoundError
+ // @TRACE 714=callback threw exception
+ log.fine(CLASS_NAME, methodName, "714", null, ex);
+ running = false;
+ clientComms.shutdownConnection(null, new MqttException(ex));
+
+ } finally {
+ synchronized (spaceAvailable) {
+ // Notify the spaceAvailable lock, to say that there's now
+ // some space on the queue...
+
+ // @TRACE 706=notify spaceAvailable
+ log.fine(CLASS_NAME, methodName, "706");
+ spaceAvailable.notifyAll();
+ }
+ }
+ }
+ }
+
+ private void handleActionComplete(MqttToken token)
+ throws MqttException {
+ final String methodName = "handleActionComplete";
+ synchronized (token) {
+ // @TRACE 705=callback and notify for key={0}
+ log.fine(CLASS_NAME, methodName, "705", new Object[] { token.internalTok.getKey() });
+
+ // Unblock any waiters and if pending complete now set completed
+ token.internalTok.notifyComplete();
+
+ if (!token.internalTok.isNotified()) {
+ // If a callback is registered and delivery has finished
+ // call delivery complete callback.
+ if ( mqttCallback != null
+ && token instanceof MqttDeliveryToken
+ && token.isComplete()) {
+ mqttCallback.deliveryComplete((MqttDeliveryToken) token);
+ }
+ // Now call async action completion callbacks
+ fireActionEvent(token);
+ }
+
+ // Set notified so we don't tell the user again about this action.
+ if ( token.isComplete() ){
+ if ( token instanceof MqttDeliveryToken || token.getActionCallback() instanceof IMqttActionListener ) {
+ token.internalTok.setNotified(true);
+ }
+ }
+
+
+ if (token.isComplete()) {
+ // Finish by doing any post processing such as delete
+ // from persistent store but only do so if the action
+ // is complete
+ clientState.notifyComplete(token);
+ }
+ }
+ }
+
+ /**
+ * This method is called when the connection to the server is lost. If there
+ * is no cause then it was a clean disconnect. The connectionLost callback
+ * will be invoked if registered and run on the thread that requested
+ * shutdown e.g. receiver or sender thread. If the request was a user
+ * initiated disconnect then the disconnect token will be notified.
+ *
+ * @param cause the reason behind the loss of connection.
+ */
+ public void connectionLost(MqttException cause) {
+ final String methodName = "connectionLost";
+ // If there was a problem and a client callback has been set inform
+ // the connection lost listener of the problem.
+ try {
+ if (mqttCallback != null && cause != null) {
+ // @TRACE 708=call connectionLost
+ log.fine(CLASS_NAME, methodName, "708", new Object[] { cause });
+ mqttCallback.connectionLost(cause);
+ }
+ } catch (java.lang.Throwable t) {
+ // Just log the fact that a throwable has caught connection lost
+ // is called during shutdown processing so no need to do anything else
+ // @TRACE 720=exception from connectionLost {0}
+ log.fine(CLASS_NAME, methodName, "720", new Object[] { t });
+ }
+ }
+
+ /**
+ * An action has completed - if a completion listener has been set on the
+ * token then invoke it with the outcome of the action.
+ *
+ * @param token
+ */
+ public void fireActionEvent(MqttToken token) {
+ final String methodName = "fireActionEvent";
+
+ if (token != null) {
+ IMqttActionListener asyncCB = token.getActionCallback();
+ if (asyncCB != null) {
+ if (token.getException() == null) {
+ // @TRACE 716=call onSuccess key={0}
+ log.fine(CLASS_NAME, methodName, "716",
+ new Object[] { token.internalTok.getKey() });
+ asyncCB.onSuccess(token);
+ } else {
+ // @TRACE 717=call onFailure key {0}
+ log.fine(CLASS_NAME, methodName, "716",
+ new Object[] { token.internalTok.getKey() });
+ asyncCB.onFailure(token, token.getException());
+ }
+ }
+ }
+ }
+
+ /**
+ * This method is called when a message arrives on a topic. Messages are
+ * only added to the queue for inbound messages if the client is not
+ * quiescing.
+ *
+ * @param sendMessage
+ * the MQTT SEND message.
+ */
+ public void messageArrived(MqttPublish sendMessage) {
+ final String methodName = "messageArrived";
+ if (mqttCallback != null) {
+ // If we already have enough messages queued up in memory, wait
+ // until some more queue space becomes available. This helps
+ // the client protect itself from getting flooded by messages
+ // from the server.
+ synchronized (spaceAvailable) {
+ while (running && !quiescing && messageQueue.size() >= INBOUND_QUEUE_SIZE) {
+ try {
+ // @TRACE 709=wait for spaceAvailable
+ log.fine(CLASS_NAME, methodName, "709");
+ spaceAvailable.wait(200);
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ if (!quiescing) {
+ messageQueue.addElement(sendMessage);
+ // Notify the CommsCallback thread that there's work to do...
+ synchronized (workAvailable) {
+ // @TRACE 710=new msg avail, notify workAvailable
+ log.fine(CLASS_NAME, methodName, "710");
+ workAvailable.notifyAll();
+ }
+ }
+ }
+ }
+
+ /**
+ * Let the call back thread quiesce. Prevent new inbound messages being
+ * added to the process queue and let existing work quiesce. (until the
+ * thread is told to shutdown).
+ */
+ public void quiesce() {
+ final String methodName = "quiesce";
+ this.quiescing = true;
+ synchronized (spaceAvailable) {
+ // @TRACE 711=quiesce notify spaceAvailable
+ log.fine(CLASS_NAME, methodName, "711");
+ // Unblock anything waiting for space...
+ spaceAvailable.notifyAll();
+ }
+ }
+
+ public boolean isQuiesced() {
+ if (quiescing && completeQueue.size() == 0 && messageQueue.size() == 0) {
+ return true;
+ }
+ return false;
+ }
+
+ private void handleMessage(MqttPublish publishMessage)
+ throws MqttException, Exception {
+ final String methodName = "handleMessage";
+ // If quisecing process any pending messages.
+ if (mqttCallback != null) {
+ String destName = publishMessage.getTopicName();
+
+ // @TRACE 713=call messageArrived key={0} topic={1}
+ log.fine(CLASS_NAME, methodName, "713", new Object[] {
+ new Integer(publishMessage.getMessageId()), destName });
+ mqttCallback.messageArrived(destName, publishMessage.getMessage());
+ if (publishMessage.getMessage().getQos() == 1) {
+ this.clientComms.internalSend(new MqttPubAck(publishMessage),
+ new MqttToken(clientComms.getClient().getClientId()));
+ } else if (publishMessage.getMessage().getQos() == 2) {
+ this.clientComms.deliveryComplete(publishMessage);
+ MqttPubComp pubComp = new MqttPubComp(publishMessage);
+ this.clientComms.internalSend(pubComp, new MqttToken(clientComms.getClient().getClientId()));
+ }
+ }
+ }
+
+ public void asyncOperationComplete(MqttToken token) {
+ final String methodName = "asyncOperationComplete";
+
+ if (running) {
+ // invoke callbacks on callback thread
+ completeQueue.addElement(token);
+ synchronized (workAvailable) {
+ // @TRACE 715=new workAvailable. key={0}
+ log.fine(CLASS_NAME, methodName, "715", new Object[] { token.internalTok.getKey() });
+ workAvailable.notifyAll();
+ }
+ } else {
+ // invoke async callback on invokers thread
+ try {
+ handleActionComplete(token);
+ } catch (Throwable ex) {
+ // Users code could throw an Error or Exception e.g. in the case
+ // of class NoClassDefFoundError
+ // @TRACE 719=callback threw ex:
+ log.fine(CLASS_NAME, methodName, "719", null, ex);
+
+ // Shutdown likely already in progress but no harm to confirm
+ clientComms.shutdownConnection(null, new MqttException(ex));
+ }
+
+ }
+ }
+
+ /**
+ * Returns the thread used by this callback.
+ */
+ protected Thread getThread() {
+ return callbackThread;
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsReceiver.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsReceiver.java
new file mode 100644
index 00000000..2fd2fe2c
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsReceiver.java
@@ -0,0 +1,170 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttToken;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttAck;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttInputStream;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttWireMessage;
+import org.eclipse.paho.client.mqttv3.logging.Logger;
+import org.eclipse.paho.client.mqttv3.logging.LoggerFactory;
+
+/**
+ * Receives MQTT packets from the server.
+ */
+public class CommsReceiver implements Runnable {
+ private static final String CLASS_NAME = CommsReceiver.class.getName();
+ private static final Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME);
+
+ private boolean running = false;
+ private Object lifecycle = new Object();
+ private ClientState clientState = null;
+ private ClientComms clientComms = null;
+ private MqttInputStream in;
+ private CommsTokenStore tokenStore = null;
+ private Thread recThread = null;
+ private volatile boolean receiving;
+
+ public CommsReceiver(ClientComms clientComms, ClientState clientState,CommsTokenStore tokenStore, InputStream in) {
+ this.in = new MqttInputStream(clientState, in);
+ this.clientComms = clientComms;
+ this.clientState = clientState;
+ this.tokenStore = tokenStore;
+ log.setResourceName(clientComms.getClient().getClientId());
+ }
+
+ /**
+ * Starts up the Receiver's thread.
+ */
+ public void start(String threadName) {
+ final String methodName = "start";
+ //@TRACE 855=starting
+ log.fine(CLASS_NAME,methodName, "855");
+ synchronized (lifecycle) {
+ if (!running) {
+ running = true;
+ recThread = new Thread(this, threadName);
+ recThread.start();
+ }
+ }
+ }
+
+ /**
+ * Stops the Receiver's thread. This call will block.
+ */
+ public void stop() {
+ final String methodName = "stop";
+ synchronized (lifecycle) {
+ //@TRACE 850=stopping
+ log.fine(CLASS_NAME,methodName, "850");
+ if (running) {
+ running = false;
+ receiving = false;
+ if (!Thread.currentThread().equals(recThread)) {
+ try {
+ // Wait for the thread to finish.
+ recThread.join();
+ }
+ catch (InterruptedException ex) {
+ }
+ }
+ }
+ }
+ recThread = null;
+ //@TRACE 851=stopped
+ log.fine(CLASS_NAME,methodName,"851");
+ }
+
+ /**
+ * Run loop to receive messages from the server.
+ */
+ public void run() {
+ final String methodName = "run";
+ MqttToken token = null;
+
+ while (running && (in != null)) {
+ try {
+ //@TRACE 852=network read message
+ log.fine(CLASS_NAME,methodName,"852");
+ receiving = in.available() > 0;
+ MqttWireMessage message = in.readMqttWireMessage();
+ receiving = false;
+
+ if (message instanceof MqttAck) {
+ token = tokenStore.getToken(message);
+ if (token!=null) {
+ synchronized (token) {
+ // Ensure the notify processing is done under a lock on the token
+ // This ensures that the send processing can complete before the
+ // receive processing starts! ( request and ack and ack processing
+ // can occur before request processing is complete if not!
+ clientState.notifyReceivedAck((MqttAck)message);
+ }
+ } else {
+ // It its an ack and there is no token then something is not right.
+ // An ack should always have a token assoicated with it.
+ throw new MqttException(MqttException.REASON_CODE_UNEXPECTED_ERROR);
+ }
+ } else {
+ // A new message has arrived
+ clientState.notifyReceivedMsg(message);
+ }
+ }
+ catch (MqttException ex) {
+ //@TRACE 856=Stopping, MQttException
+ log.fine(CLASS_NAME,methodName,"856",null,ex);
+ running = false;
+ // Token maybe null but that is handled in shutdown
+ clientComms.shutdownConnection(token, ex);
+ }
+ catch (IOException ioe) {
+ //@TRACE 853=Stopping due to IOException
+ log.fine(CLASS_NAME,methodName,"853");
+
+ running = false;
+ // An EOFException could be raised if the broker processes the
+ // DISCONNECT and ends the socket before we complete. As such,
+ // only shutdown the connection if we're not already shutting down.
+ if (!clientComms.isDisconnecting()) {
+ clientComms.shutdownConnection(token, new MqttException(MqttException.REASON_CODE_CONNECTION_LOST, ioe));
+ }
+ }
+ finally {
+ receiving = false;
+ }
+ }
+
+ //@TRACE 854=<
+ log.fine(CLASS_NAME,methodName,"854");
+ }
+
+ public boolean isRunning() {
+ return running;
+ }
+
+ /**
+ * Returns the receiving state.
+ *
+ * @return true if the receiver is receiving data, false otherwise.
+ */
+ public boolean isReceiving() {
+ return receiving;
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsSender.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsSender.java
new file mode 100644
index 00000000..0753e61f
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsSender.java
@@ -0,0 +1,159 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttToken;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttAck;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttDisconnect;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttOutputStream;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttWireMessage;
+import org.eclipse.paho.client.mqttv3.logging.Logger;
+import org.eclipse.paho.client.mqttv3.logging.LoggerFactory;
+
+
+public class CommsSender implements Runnable {
+ private static final String CLASS_NAME = CommsSender.class.getName();
+ private static final Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME);
+
+ //Sends MQTT packets to the server on its own thread
+ private boolean running = false;
+ private Object lifecycle = new Object();
+ private ClientState clientState = null;
+ private MqttOutputStream out;
+ private ClientComms clientComms = null;
+ private CommsTokenStore tokenStore = null;
+ private Thread sendThread = null;
+
+ public CommsSender(ClientComms clientComms, ClientState clientState, CommsTokenStore tokenStore, OutputStream out) {
+ this.out = new MqttOutputStream(clientState, out);
+ this.clientComms = clientComms;
+ this.clientState = clientState;
+ this.tokenStore = tokenStore;
+ log.setResourceName(clientComms.getClient().getClientId());
+ }
+
+ /**
+ * Starts up the Sender thread.
+ */
+ public void start(String threadName) {
+ synchronized (lifecycle) {
+ if (!running) {
+ running = true;
+ sendThread = new Thread(this, threadName);
+ sendThread.start();
+ }
+ }
+ }
+
+ /**
+ * Stops the Sender's thread. This call will block.
+ */
+ public void stop() {
+ final String methodName = "stop";
+
+ synchronized (lifecycle) {
+ //@TRACE 800=stopping sender
+ log.fine(CLASS_NAME,methodName,"800");
+ if (running) {
+ running = false;
+ if (!Thread.currentThread().equals(sendThread)) {
+ try {
+ // first notify get routine to finish
+ clientState.notifyQueueLock();
+ // Wait for the thread to finish.
+ sendThread.join();
+ }
+ catch (InterruptedException ex) {
+ }
+ }
+ }
+ sendThread=null;
+ //@TRACE 801=stopped
+ log.fine(CLASS_NAME,methodName,"801");
+ }
+ }
+
+ public void run() {
+ final String methodName = "run";
+ MqttWireMessage message = null;
+ while (running && (out != null)) {
+ try {
+ message = clientState.get();
+ if (message != null) {
+ //@TRACE 802=network send key={0} msg={1}
+ log.fine(CLASS_NAME,methodName,"802", new Object[] {message.getKey(),message});
+
+ if (message instanceof MqttAck) {
+ out.write(message);
+ out.flush();
+ } else {
+ MqttToken token = tokenStore.getToken(message);
+ // While quiescing the tokenstore can be cleared so need
+ // to check for null for the case where clear occurs
+ // while trying to send a message.
+ if (token != null) {
+ synchronized (token) {
+ out.write(message);
+ try {
+ out.flush();
+ } catch (IOException ex) {
+ // The flush has been seen to fail on disconnect of a SSL socket
+ // as disconnect is in progress this should not be treated as an error
+ if (!(message instanceof MqttDisconnect)) {
+ throw ex;
+ }
+ }
+ clientState.notifySent(message);
+ }
+ }
+ }
+ } else { // null message
+ //@TRACE 803=get message returned null, stopping}
+ log.fine(CLASS_NAME,methodName,"803");
+
+ running = false;
+ }
+ } catch (MqttException me) {
+ handleRunException(message, me);
+ } catch (Exception ex) {
+ handleRunException(message, ex);
+ }
+ } // end while
+
+ //@TRACE 805=<
+ log.fine(CLASS_NAME, methodName,"805");
+
+ }
+
+ private void handleRunException(MqttWireMessage message, Exception ex) {
+ final String methodName = "handleRunException";
+ //@TRACE 804=exception
+ log.fine(CLASS_NAME,methodName,"804",null, ex);
+ MqttException mex;
+ if ( !(ex instanceof MqttException)) {
+ mex = new MqttException(MqttException.REASON_CODE_CONNECTION_LOST, ex);
+ } else {
+ mex = (MqttException)ex;
+ }
+
+ running = false;
+ clientComms.shutdownConnection(null, mex);
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsTokenStore.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsTokenStore.java
new file mode 100644
index 00000000..c9cca5f2
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/CommsTokenStore.java
@@ -0,0 +1,252 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttToken;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttPublish;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttWireMessage;
+import org.eclipse.paho.client.mqttv3.logging.Logger;
+import org.eclipse.paho.client.mqttv3.logging.LoggerFactory;
+
+
+/**
+ * Provides a "token" based system for storing and tracking actions across
+ * multiple threads.
+ * When a message is sent, a token is associated with the message
+ * and saved using the {@link #saveToken(MqttToken, MqttWireMessage)} method. Anyone interested
+ * in tacking the state can call one of the wait methods on the token or using
+ * the asynchronous listener callback method on the operation.
+ * The {@link CommsReceiver} class, on another thread, reads responses back from
+ * the network. It uses the response to find the relevant token, which it can then
+ * notify.
+ *
+ * Note:
+ * Ping, connect and disconnect do not have a unique message id as
+ * only one outstanding request of each type is allowed to be outstanding
+ */
+public class CommsTokenStore {
+ private static final String CLASS_NAME = CommsTokenStore.class.getName();
+ private static final Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME);
+
+ // Maps message-specific data (usually message IDs) to tokens
+ private Hashtable tokens;
+ private String logContext;
+ private MqttException closedResponse = null;
+
+ public CommsTokenStore(String logContext) {
+ final String methodName = "";
+
+ log.setResourceName(logContext);
+ this.tokens = new Hashtable();
+ this.logContext = logContext;
+ //@TRACE 308=<>
+ log.fine(CLASS_NAME,methodName,"308");//,new Object[]{message});
+
+ }
+
+ /**
+ * Based on the message type that has just been received return the associated
+ * token from the token store or null if one does not exist.
+ * @param message whose token is to be returned
+ * @return token for the requested message
+ */
+ public MqttToken getToken(MqttWireMessage message) {
+ String key = message.getKey();
+ return (MqttToken)tokens.get(key);
+ }
+
+ public MqttToken getToken(String key) {
+ return (MqttToken)tokens.get(key);
+ }
+
+
+ public MqttToken removeToken(MqttWireMessage message) {
+ if (message != null) {
+ return removeToken(message.getKey());
+ }
+ return null;
+ }
+
+ public MqttToken removeToken(String key) {
+ final String methodName = "removeToken";
+ //@TRACE 306=key={0}
+ log.fine(CLASS_NAME,methodName,"306",new Object[]{key});
+
+ if ( null != key ){
+ return (MqttToken) tokens.remove(key);
+ }
+
+ return null;
+ }
+
+ /**
+ * Restores a token after a client restart. This method could be called
+ * for a SEND of CONFIRM, but either way, the original SEND is what's
+ * needed to re-build the token.
+ */
+ protected MqttDeliveryToken restoreToken(MqttPublish message) {
+ final String methodName = "restoreToken";
+ MqttDeliveryToken token;
+ synchronized(tokens) {
+ String key = new Integer(message.getMessageId()).toString();
+ if (this.tokens.containsKey(key)) {
+ token = (MqttDeliveryToken)this.tokens.get(key);
+ //@TRACE 302=existing key={0} message={1} token={2}
+ log.fine(CLASS_NAME,methodName, "302",new Object[]{key, message,token});
+ } else {
+ token = new MqttDeliveryToken(logContext);
+ token.internalTok.setKey(key);
+ this.tokens.put(key, token);
+ //@TRACE 303=creating new token key={0} message={1} token={2}
+ log.fine(CLASS_NAME,methodName,"303",new Object[]{key, message, token});
+ }
+ }
+ return token;
+ }
+
+ // For outbound messages store the token in the token store
+ // For pubrel use the existing publish token
+ protected void saveToken(MqttToken token, MqttWireMessage message) throws MqttException {
+ final String methodName = "saveToken";
+
+ synchronized(tokens) {
+ if (closedResponse == null) {
+ String key = message.getKey();
+ //@TRACE 300=key={0} message={1}
+ log.fine(CLASS_NAME,methodName,"300",new Object[]{key, message});
+
+ saveToken(token,key);
+ } else {
+ throw closedResponse;
+ }
+ }
+ }
+
+ protected void saveToken(MqttToken token, String key) {
+ final String methodName = "saveToken";
+
+ synchronized(tokens) {
+ //@TRACE 307=key={0} token={1}
+ log.fine(CLASS_NAME,methodName,"307",new Object[]{key,token.toString()});
+ token.internalTok.setKey(key);
+ this.tokens.put(key, token);
+ }
+ }
+
+ protected void quiesce(MqttException quiesceResponse) {
+ final String methodName = "quiesce";
+
+ synchronized(tokens) {
+ //@TRACE 309=resp={0}
+ log.fine(CLASS_NAME,methodName,"309",new Object[]{quiesceResponse});
+
+ closedResponse = quiesceResponse;
+ }
+ }
+
+ public void open() {
+ final String methodName = "open";
+
+ synchronized(tokens) {
+ //@TRACE 310=>
+ log.fine(CLASS_NAME,methodName,"310");
+
+ closedResponse = null;
+ }
+ }
+
+ public MqttDeliveryToken[] getOutstandingDelTokens() {
+ final String methodName = "getOutstandingDelTokens";
+
+ synchronized(tokens) {
+ //@TRACE 311=>
+ log.fine(CLASS_NAME,methodName,"311");
+
+ Vector list = new Vector();
+ Enumeration enumeration = tokens.elements();
+ MqttToken token;
+ while(enumeration.hasMoreElements()) {
+ token = (MqttToken)enumeration.nextElement();
+ if (token != null
+ && token instanceof MqttDeliveryToken
+ && !token.internalTok.isNotified()) {
+
+ list.addElement(token);
+ }
+ }
+
+ MqttDeliveryToken[] result = new MqttDeliveryToken[list.size()];
+ return (MqttDeliveryToken[]) list.toArray(result);
+ }
+ }
+
+ public Vector getOutstandingTokens() {
+ final String methodName = "getOutstandingTokens";
+
+ synchronized(tokens) {
+ //@TRACE 312=>
+ log.fine(CLASS_NAME,methodName,"312");
+
+ Vector list = new Vector();
+ Enumeration enumeration = tokens.elements();
+ MqttToken token;
+ while(enumeration.hasMoreElements()) {
+ token = (MqttToken)enumeration.nextElement();
+ if (token != null) {
+ list.addElement(token);
+ }
+ }
+ return list;
+ }
+ }
+
+ /**
+ * Empties the token store without notifying any of the tokens.
+ */
+ public void clear() {
+ final String methodName = "clear";
+ //@TRACE 305=> {0} tokens
+ log.fine(CLASS_NAME, methodName, "305", new Object[] {new Integer(tokens.size())});
+ synchronized(tokens) {
+ tokens.clear();
+ }
+ }
+
+ public int count() {
+ synchronized(tokens) {
+ return tokens.size();
+ }
+ }
+ public String toString() {
+ String lineSep = System.getProperty("line.separator","\n");
+ StringBuffer toks = new StringBuffer();
+ synchronized(tokens) {
+ Enumeration enumeration = tokens.elements();
+ MqttToken token;
+ while(enumeration.hasMoreElements()) {
+ token = (MqttToken)enumeration.nextElement();
+ toks.append("{"+token.internalTok+"}"+lineSep);
+ }
+ return toks.toString();
+ }
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ConnectActionListener.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ConnectActionListener.java
new file mode 100644
index 00000000..2dd56240
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ConnectActionListener.java
@@ -0,0 +1,173 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Ian Craggs - MQTT 3.1.1 support
+ */
+package org.eclipse.paho.client.mqttv3.internal;
+
+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.MqttClientPersistence;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
+import org.eclipse.paho.client.mqttv3.MqttToken;
+
+/**
+ *
This class handles the connection of the AsyncClient to one of the available URLs.
+ *
The URLs are supplied as either the singleton when the client is created, or as a list in the connect options.
+ *
This class uses its own onSuccess and onFailure callbacks in preference to the user supplied callbacks.
+ *
An attempt is made to connect to each URL in the list until either a connection attempt succeeds or all the URLs have been tried
+ *
If a connection succeeds then the users token is notified and the users onSuccess callback is called.
+ *
If a connection fails then another URL in the list is attempted, otherwise the users token is notified
+ * and the users onFailure callback is called
+ */
+public class ConnectActionListener implements IMqttActionListener {
+
+ private MqttClientPersistence persistence;
+ private MqttAsyncClient client;
+ private ClientComms comms;
+ private MqttConnectOptions options;
+ private MqttToken userToken;
+ private Object userContext;
+ private IMqttActionListener userCallback;
+ private int originalMqttVersion;
+
+ /**
+ * @param persistence
+ * @param client
+ * @param comms
+ * @param options
+ * @param userToken
+ * @param userContext
+ * @param userCallback
+ */
+ public ConnectActionListener(
+ MqttAsyncClient client,
+ MqttClientPersistence persistence,
+ ClientComms comms,
+ MqttConnectOptions options,
+ MqttToken userToken,
+ Object userContext,
+ IMqttActionListener userCallback) {
+ this.persistence = persistence;
+ this.client = client;
+ this.comms = comms;
+ this.options = options;
+ this.userToken = userToken;
+ this.userContext = userContext;
+ this.userCallback = userCallback;
+ this.originalMqttVersion = options.getMqttVersion();
+ }
+
+ /**
+ * If the connect succeeded then call the users onSuccess callback
+ *
+ * @param token
+ */
+ public void onSuccess(IMqttToken token) {
+ if (originalMqttVersion == MqttConnectOptions.MQTT_VERSION_DEFAULT) {
+ options.setMqttVersion(MqttConnectOptions.MQTT_VERSION_DEFAULT);
+ }
+ userToken.internalTok.markComplete(token.getResponse(), null);
+ userToken.internalTok.notifyComplete();
+
+ if (userCallback != null) {
+ userToken.setUserContext(userContext);
+ userCallback.onSuccess(userToken);
+ }
+ }
+
+ /**
+ * The connect failed, so try the next URI on the list.
+ * If there are no more URIs, then fail the overall connect.
+ *
+ * @param token
+ * @param exception
+ */
+ public void onFailure(IMqttToken token, Throwable exception) {
+
+ int numberOfURIs = comms.getNetworkModules().length;
+ int index = comms.getNetworkModuleIndex();
+
+ if ((index + 1) < numberOfURIs || (originalMqttVersion == MqttConnectOptions.MQTT_VERSION_DEFAULT && options.getMqttVersion() == MqttConnectOptions.MQTT_VERSION_3_1_1)) {
+
+ if (originalMqttVersion == MqttConnectOptions.MQTT_VERSION_DEFAULT) {
+ if (options.getMqttVersion() == MqttConnectOptions.MQTT_VERSION_3_1_1) {
+ options.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1);
+ }
+ else {
+ options.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1_1);
+ comms.setNetworkModuleIndex(index + 1);
+ }
+ }
+ else {
+ comms.setNetworkModuleIndex(index + 1);
+ }
+ try {
+ connect();
+ }
+ catch (MqttPersistenceException e) {
+ onFailure(token, e); // try the next URI in the list
+ }
+ }
+ else {
+ if (originalMqttVersion == MqttConnectOptions.MQTT_VERSION_DEFAULT) {
+ options.setMqttVersion(MqttConnectOptions.MQTT_VERSION_DEFAULT);
+ }
+ MqttException ex;
+ if (exception instanceof MqttException) {
+ ex = (MqttException) exception;
+ }
+ else {
+ ex = new MqttException(exception);
+ }
+ userToken.internalTok.markComplete(null, ex);
+ userToken.internalTok.notifyComplete();
+
+ if (userCallback != null) {
+ userToken.setUserContext(userContext);
+ userCallback.onFailure(userToken, exception);
+ }
+ }
+ }
+
+ /**
+ * Start the connect processing
+ * @throws MqttPersistenceException
+ */
+ public void connect() throws MqttPersistenceException {
+ MqttToken token = new MqttToken(client.getClientId());
+ token.setActionCallback(this);
+ token.setUserContext(this);
+
+ persistence.open(client.getClientId(), client.getServerURI());
+
+ if (options.isCleanSession()) {
+ persistence.clear();
+ }
+
+ if (options.getMqttVersion() == MqttConnectOptions.MQTT_VERSION_DEFAULT) {
+ options.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1_1);
+ }
+
+ try {
+ comms.connect(options, token);
+ }
+ catch (MqttException e) {
+ onFailure(token, e);
+ }
+ }
+
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/DestinationProvider.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/DestinationProvider.java
similarity index 65%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/DestinationProvider.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/DestinationProvider.java
index 63d6776a..39a90593 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/DestinationProvider.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/DestinationProvider.java
@@ -1,27 +1,31 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal;
-
-import org.eclipse.paho.client.mqttv3.MqttTopic;
-
-/**
- * This interface exists to act as a common type for
- * MqttClient and MqttMIDPClient so they can be passed to
- * ClientComms without either client class need to know
- * about the other.
- * Specifically, this allows the MIDP client to work
- * without the non-MIDP MqttClient/MqttConnectOptions
- * classes being present.
- */
-public interface DestinationProvider {
- public MqttTopic getTopic(String topic);
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal;
+
+import org.eclipse.paho.client.mqttv3.MqttTopic;
+
+/**
+ * This interface exists to act as a common type for
+ * MqttClient and MqttMIDPClient so they can be passed to
+ * ClientComms without either client class need to know
+ * about the other.
+ * Specifically, this allows the MIDP client to work
+ * without the non-MIDP MqttClient/MqttConnectOptions
+ * classes being present.
+ */
+public interface DestinationProvider {
+ public MqttTopic getTopic(String topic);
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/ExceptionHelper.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ExceptionHelper.java
similarity index 71%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/ExceptionHelper.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ExceptionHelper.java
index 3fe186e4..c93d56d8 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/ExceptionHelper.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ExceptionHelper.java
@@ -1,53 +1,60 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal;
-
-import org.eclipse.paho.client.mqttv3.MqttException;
-import org.eclipse.paho.client.mqttv3.MqttSecurityException;
-
-/**
- * Utility class to help create exceptions of the correct type.
- */
-public class ExceptionHelper {
- public static MqttException createMqttException(int reasonCode) {
- if ((reasonCode == MqttException.REASON_CODE_FAILED_AUTHENTICATION) ||
- (reasonCode == MqttException.REASON_CODE_NOT_AUTHORIZED)) {
- return new MqttSecurityException(reasonCode);
- }
- else {
- return new MqttException(reasonCode);
- }
- }
-
- public static MqttException createMqttException(Throwable cause) {
- if (cause.getClass().getName().equals("java.security.GeneralSecurityException")) {
- return new MqttSecurityException(cause);
- }
- return new MqttException(cause);
- }
-
- /**
- * Returns whether or not the specified class is available to the current
- * class loader. This is used to protect the code against using Java SE
- * APIs on Java ME.
- */
- public static boolean isClassAvailable(String className) {
- boolean result = false;
- try {
- Class.forName(className);
- result = true;
- }
- catch (ClassNotFoundException ex) {
- }
- return result;
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttSecurityException;
+
+/**
+ * Utility class to help create exceptions of the correct type.
+ */
+public class ExceptionHelper {
+ public static MqttException createMqttException(int reasonCode) {
+ if ((reasonCode == MqttException.REASON_CODE_FAILED_AUTHENTICATION) ||
+ (reasonCode == MqttException.REASON_CODE_NOT_AUTHORIZED)) {
+ return new MqttSecurityException(reasonCode);
+ }
+
+ return new MqttException(reasonCode);
+ }
+
+ public static MqttException createMqttException(Throwable cause) {
+ if (cause.getClass().getName().equals("java.security.GeneralSecurityException")) {
+ return new MqttSecurityException(cause);
+ }
+ return new MqttException(cause);
+ }
+
+ /**
+ * Returns whether or not the specified class is available to the current
+ * class loader. This is used to protect the code against using Java SE
+ * APIs on Java ME.
+ */
+ public static boolean isClassAvailable(String className) {
+ boolean result = false;
+ try {
+ Class.forName(className);
+ result = true;
+ }
+ catch (ClassNotFoundException ex) {
+ }
+ return result;
+ }
+
+ // Utility classes should not have a public or default constructor.
+ private ExceptionHelper() {
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/FileLock.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/FileLock.java
new file mode 100644
index 00000000..ebad8bbf
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/FileLock.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal;
+/**
+ * FileLock - used to obtain a lock that can be used to prevent other MQTT clients
+ * using the same persistent store. If the lock is already held then an exception
+ * is thrown.
+ *
+ * Some Java runtimes such as JME MIDP do not support file locking or even
+ * the Java classes that support locking. The class is coded to both compile
+ * and work on all Java runtimes. In Java runtimes that do not support
+ * locking it will look as though a lock has been obtained but in reality
+ * no lock has been obtained.
+ */
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.lang.reflect.Method;
+
+public class FileLock {
+ private File lockFile;
+ private RandomAccessFile file;
+ private Object fileLock;
+
+ /**
+ * Creates an NIO FileLock on the specified file if on a suitable Java runtime.
+ * @param clientDir the a File of the directory to contain the lock file.
+ * @param lockFilename name of the the file to lock
+ * @throws Exception if the lock could not be obtained for any reason
+ */
+ public FileLock(File clientDir, String lockFilename) throws Exception {
+ // Create a file to obtain a lock on.
+ lockFile = new File(clientDir,lockFilename);
+ if (ExceptionHelper.isClassAvailable("java.nio.channels.FileLock")) {
+ try {
+ this.file = new RandomAccessFile(lockFile,"rw");
+ Method m = file.getClass().getMethod("getChannel",new Class[]{});
+ Object channel = m.invoke(file,new Object[]{});
+ m = channel.getClass().getMethod("tryLock",new Class[]{});
+ this.fileLock = m.invoke(channel, new Object[]{});
+ } catch(NoSuchMethodException nsme) {
+ this.fileLock = null;
+ } catch(IllegalArgumentException iae) {
+ this.fileLock = null;
+ } catch(IllegalAccessException iae) {
+ this.fileLock = null;
+ }
+ if (fileLock == null) {
+ // Lock not obtained
+ release();
+ throw new Exception("Problem obtaining file lock");
+ }
+ }
+ }
+
+ /**
+ * Releases the lock.
+ */
+ public void release() {
+ try {
+ if (fileLock != null) {
+ Method m = fileLock.getClass().getMethod("release",new Class[]{});
+ m.invoke(fileLock, new Object[]{});
+ fileLock = null;
+ }
+ } catch (Exception e) {
+ // Ignore exceptions
+ }
+ if (file != null) {
+ try {
+ file.close();
+ } catch (IOException e) {
+ }
+ file = null;
+ }
+
+ if (lockFile != null && lockFile.exists()) {
+ lockFile.delete();
+ }
+ lockFile = null;
+ }
+
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/LocalNetworkModule.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/LocalNetworkModule.java
similarity index 56%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/LocalNetworkModule.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/LocalNetworkModule.java
index f47a7b48..b245d4b6 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/LocalNetworkModule.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/LocalNetworkModule.java
@@ -1,78 +1,91 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.lang.reflect.Method;
-
-import org.eclipse.paho.client.mqttv3.MqttException;
-
-
-/**
- * Connects directly into MicroBroker comms, within the same JVM.
- */
-public class LocalNetworkModule implements NetworkModule {
- private Class LocalListener;
- private String brokerName;
- private Object localAdapter;
-
- public LocalNetworkModule(String brokerName) {
- this.brokerName = brokerName;
- }
-
- public void start() throws IOException, MqttException{
- if (!ExceptionHelper.isClassAvailable("com.ibm.mqttdirect.modules.local.bindings.LocalListener")) {
- throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_SERVER_CONNECT_ERROR);
- }
- try {
- LocalListener = Class.forName("com.ibm.mqttdirect.modules.local.bindings.LocalListener");
- Method connect_m = LocalListener.getMethod("connect", new Class[]{ java.lang.String.class });
- localAdapter = connect_m.invoke(null,new Object[]{ brokerName });
- } catch(Exception e) {
- }
- if(localAdapter == null) {
- throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_SERVER_CONNECT_ERROR);
- }
- }
-
- public InputStream getInputStream() throws IOException {
- InputStream stream = null;
- try {
- Method m = LocalListener.getMethod("getClientInputStream",new Class[]{});
- stream = (InputStream)m.invoke(this.localAdapter,new Object[]{});
- } catch(Exception e) {
- }
- return stream;
- }
-
- public OutputStream getOutputStream() throws IOException {
- OutputStream stream = null;
- try {
- Method m = LocalListener.getMethod("getClientOutputStream",new Class[]{});
- stream = (OutputStream)m.invoke(this.localAdapter,new Object[]{});
- } catch(Exception e) {
- }
- return stream;
- }
-
- public void stop() throws IOException {
- if (localAdapter != null) {
- try {
- Method m = LocalListener.getMethod("close",new Class[]{});
- m.invoke(this.localAdapter,new Object[]{});
- } catch(Exception e) {
- }
- }
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Method;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+
+
+/**
+ * Special comms class that allows an MQTT client to use a non TCP / optimised
+ * mechanism to talk to an MQTT server when running in the same JRE instance as the
+ * MQTT server.
+ *
+ * This class checks for the existence of the optimised comms adatper class i.e. the one
+ * that provides the optimised communication mechanism. If not available the request
+ * to connect using the optimised mechanism is rejected.
+ *
+ * The only known server that implements this is the microbroker:- an MQTT server that
+ * ships with a number of IBM products.
+ */
+public class LocalNetworkModule implements NetworkModule {
+ private Class localListener;
+ private String brokerName;
+ private Object localAdapter;
+
+ public LocalNetworkModule(String brokerName) {
+ this.brokerName = brokerName;
+ }
+
+ public void start() throws IOException, MqttException{
+ if (!ExceptionHelper.isClassAvailable("com.ibm.mqttdirect.modules.local.bindings.localListener")) {
+ throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_SERVER_CONNECT_ERROR);
+ }
+ try {
+ localListener = Class.forName("com.ibm.mqttdirect.modules.local.bindings.localListener");
+ Method connect_m = localListener.getMethod("connect", new Class[]{ java.lang.String.class });
+ localAdapter = connect_m.invoke(null,new Object[]{ brokerName });
+ } catch(Exception e) {
+ }
+ if(localAdapter == null) {
+ throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_SERVER_CONNECT_ERROR);
+ }
+ }
+
+ public InputStream getInputStream() throws IOException {
+ InputStream stream = null;
+ try {
+ Method m = localListener.getMethod("getClientInputStream",new Class[]{});
+ stream = (InputStream)m.invoke(this.localAdapter,new Object[]{});
+ } catch(Exception e) {
+ }
+ return stream;
+ }
+
+ public OutputStream getOutputStream() throws IOException {
+ OutputStream stream = null;
+ try {
+ Method m = localListener.getMethod("getClientOutputStream",new Class[]{});
+ stream = (OutputStream)m.invoke(this.localAdapter,new Object[]{});
+ } catch(Exception e) {
+ }
+ return stream;
+ }
+
+ public void stop() throws IOException {
+ if (localAdapter != null) {
+ try {
+ Method m = localListener.getMethod("close",new Class[]{});
+ m.invoke(this.localAdapter,new Object[]{});
+ } catch(Exception e) {
+ }
+ }
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/MessageCatalog.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/MessageCatalog.java
similarity index 76%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/MessageCatalog.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/MessageCatalog.java
index 39d41adf..a0df46d6 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/MessageCatalog.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/MessageCatalog.java
@@ -1,42 +1,46 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal;
-
-/**
- * Catalog of human readable error messages.
- */
-public abstract class MessageCatalog {
- private static MessageCatalog INSTANCE = null;
-
- public static final String getMessage(int id) {
- if (INSTANCE == null) {
- if (ExceptionHelper.isClassAvailable("java.util.ResourceBundle")) {
- try {
- // Hide this class reference behind reflection so that the class does not need to
- // be present when compiled on midp
- INSTANCE = (MessageCatalog)Class.forName("org.eclipse.paho.client.mqttv3.internal.ResourceBundleCatalog").newInstance();
- } catch (Exception e) {
- return "";
- }
- } else if (ExceptionHelper.isClassAvailable("org.eclipse.paho.client.mqttv3.internal.MIDPCatalog")){
- try {
- INSTANCE = (MessageCatalog)Class.forName("org.eclipse.paho.client.mqttv3.internal.MIDPCatalog").newInstance();
- } catch (Exception e) {
- return "";
- }
- }
- }
- return INSTANCE.getLocalizedMessage(id);
- }
-
- protected abstract String getLocalizedMessage(int id);
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal;
+
+/**
+ * Catalog of human readable error messages.
+ */
+public abstract class MessageCatalog {
+ private static MessageCatalog INSTANCE = null;
+
+ public static final String getMessage(int id) {
+ if (INSTANCE == null) {
+ if (ExceptionHelper.isClassAvailable("java.util.ResourceBundle")) {
+ try {
+ // Hide this class reference behind reflection so that the class does not need to
+ // be present when compiled on midp
+ INSTANCE = (MessageCatalog)Class.forName("org.eclipse.paho.client.mqttv3.internal.ResourceBundleCatalog").newInstance();
+ } catch (Exception e) {
+ return "";
+ }
+ } else if (ExceptionHelper.isClassAvailable("org.eclipse.paho.client.mqttv3.internal.MIDPCatalog")){
+ try {
+ INSTANCE = (MessageCatalog)Class.forName("org.eclipse.paho.client.mqttv3.internal.MIDPCatalog").newInstance();
+ } catch (Exception e) {
+ return "";
+ }
+ }
+ }
+ return INSTANCE.getLocalizedMessage(id);
+ }
+
+ protected abstract String getLocalizedMessage(int id);
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/MqttPersistentData.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/MqttPersistentData.java
similarity index 83%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/MqttPersistentData.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/MqttPersistentData.java
index 74ef54df..8ab04c10 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/MqttPersistentData.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/MqttPersistentData.java
@@ -1,95 +1,98 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal;
-
-import org.eclipse.paho.client.mqttv3.MqttPersistable;
-
-public class MqttPersistentData implements MqttPersistable {
- // Message key
- private String key = null;
-
- // Message header
- private byte[] header = null;
- private int hOffset = 0;
- private int hLength = 0;
-
- // Message payload
- private byte[] payload = null;
- private int pOffset = 0;
- private int pLength = 0;
-
- /**
- * Construct a data object to pass across the MQTT client persistence
- * interface.
- * When this Object is passed to the persistence implementation the key is
- * used by the client to identify the persisted data to which further
- * update or deletion requests are targeted.
- * When this Object is created for returning to the client when it is
- * recovering its state from persistence the key is not required to be set.
- * The client can determine the key from the data.
- * @param key The key which identifies this data
- * @param header The message header
- * @param hOffset The start offset of the header bytes in header.
- * @param hLength The length of the header in the header bytes array.
- * @param payload The message payload
- * @param pOffset The start offset of the payload bytes in payload.
- * @param pLength The length of the payload in the payload bytes array
- * when persisting the message.
- */
- public MqttPersistentData( String key,
- byte[] header,
- int hOffset,
- int hLength,
- byte[] payload,
- int pOffset,
- int pLength) {
- this.key = key;
- this.header = header;
- this.hOffset = hOffset;
- this.hLength = hLength;
- this.payload = payload;
- this.pOffset = pOffset;
- this.pLength = pLength;
- }
-
- public String getKey() {
- return key;
- }
-
- public byte[] getHeaderBytes() {
- return header;
- }
-
- public int getHeaderLength() {
- return hLength;
- }
-
- public int getHeaderOffset() {
- return hOffset;
- }
-
- public byte[] getPayloadBytes() {
- return payload;
- }
-
- public int getPayloadLength() {
- if ( payload == null ) {
- return 0;
- } else {
- return pLength;
- }
- }
-
- public int getPayloadOffset() {
- return pOffset;
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal;
+
+import org.eclipse.paho.client.mqttv3.MqttPersistable;
+
+public class MqttPersistentData implements MqttPersistable {
+ // Message key
+ private String key = null;
+
+ // Message header
+ private byte[] header = null;
+ private int hOffset = 0;
+ private int hLength = 0;
+
+ // Message payload
+ private byte[] payload = null;
+ private int pOffset = 0;
+ private int pLength = 0;
+
+ /**
+ * Construct a data object to pass across the MQTT client persistence interface.
+ *
+ * When this Object is passed to the persistence implementation the key is
+ * used by the client to identify the persisted data to which further
+ * update or deletion requests are targeted.
+ * When this Object is created for returning to the client when it is
+ * recovering its state from persistence the key is not required to be set.
+ * The client can determine the key from the data.
+ * @param key The key which identifies this data
+ * @param header The message header
+ * @param hOffset The start offset of the header bytes in header.
+ * @param hLength The length of the header in the header bytes array.
+ * @param payload The message payload
+ * @param pOffset The start offset of the payload bytes in payload.
+ * @param pLength The length of the payload in the payload bytes array
+ * when persisting the message.
+ */
+ public MqttPersistentData( String key,
+ byte[] header,
+ int hOffset,
+ int hLength,
+ byte[] payload,
+ int pOffset,
+ int pLength) {
+ this.key = key;
+ this.header = header;
+ this.hOffset = hOffset;
+ this.hLength = hLength;
+ this.payload = payload;
+ this.pOffset = pOffset;
+ this.pLength = pLength;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public byte[] getHeaderBytes() {
+ return header;
+ }
+
+ public int getHeaderLength() {
+ return hLength;
+ }
+
+ public int getHeaderOffset() {
+ return hOffset;
+ }
+
+ public byte[] getPayloadBytes() {
+ return payload;
+ }
+
+ public int getPayloadLength() {
+ if ( payload == null ) {
+ return 0;
+ }
+ return pLength;
+ }
+
+ public int getPayloadOffset() {
+ return pOffset;
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/NetworkModule.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/NetworkModule.java
similarity index 62%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/NetworkModule.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/NetworkModule.java
index c5673d3c..5c3ff272 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/NetworkModule.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/NetworkModule.java
@@ -1,29 +1,33 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import org.eclipse.paho.client.mqttv3.MqttException;
-
-
-public interface NetworkModule {
- public void start() throws IOException, MqttException;
-
- public InputStream getInputStream() throws IOException;
-
- public OutputStream getOutputStream() throws IOException;
-
- public void stop() throws IOException;
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+
+
+public interface NetworkModule {
+ public void start() throws IOException, MqttException;
+
+ public InputStream getInputStream() throws IOException;
+
+ public OutputStream getOutputStream() throws IOException;
+
+ public void stop() throws IOException;
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/ResourceBundleCatalog.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ResourceBundleCatalog.java
similarity index 61%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/ResourceBundleCatalog.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ResourceBundleCatalog.java
index eb57a779..64160caa 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/ResourceBundleCatalog.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/ResourceBundleCatalog.java
@@ -1,32 +1,37 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal;
-
-import java.util.MissingResourceException;
-import java.util.ResourceBundle;
-
-public class ResourceBundleCatalog extends MessageCatalog {
-
- private ResourceBundle bundle;
-
- public ResourceBundleCatalog() throws MissingResourceException {
- bundle = ResourceBundle.getBundle("org.eclipse.paho.client.mqttv3.internal.nls.messages");
- }
-
- protected String getLocalizedMessage(int id) {
- try {
- return bundle.getString(Integer.toString(id));
- } catch(MissingResourceException mre) {
- return "MqttException";
- }
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal;
+
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+public class ResourceBundleCatalog extends MessageCatalog {
+
+ private ResourceBundle bundle;
+
+ public ResourceBundleCatalog() {
+ //MAY throws MissingResourceException
+ bundle = ResourceBundle.getBundle("org.eclipse.paho.client.mqttv3.internal.nls.messages");
+ }
+
+ protected String getLocalizedMessage(int id) {
+ try {
+ return bundle.getString(Integer.toString(id));
+ } catch(MissingResourceException mre) {
+ return "MqttException";
+ }
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/SSLNetworkModule.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/SSLNetworkModule.java
similarity index 64%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/SSLNetworkModule.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/SSLNetworkModule.java
index e7a3d6bc..e0582be1 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/SSLNetworkModule.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/SSLNetworkModule.java
@@ -1,83 +1,93 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal;
-
-import java.io.IOException;
-
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.SSLSocketFactory;
-
-import org.eclipse.paho.client.mqttv3.MqttException;
-import org.eclipse.paho.client.mqttv3.internal.trace.Trace;
-
-
-/**
- * A network module for connecting over SSL.
- */
-public class SSLNetworkModule extends TCPNetworkModule {
- private String[] enabledCiphers;
- private int handshakeTimeoutSecs;
-
- /**
- * Constructs a new SSLNetworkModule using the specified host and
- * port. The supplied SSLSocketFactory is used to supply the network
- * socket.
- */
- public SSLNetworkModule(Trace trace, SSLSocketFactory factory, String host, int port) {
- super(trace, factory, host, port);
- }
-
- /**
- * Returns the enabled cipher suites.
- */
- public String[] getEnabledCiphers() {
- return enabledCiphers;
- }
-
- /**
- * Sets the enabled cipher suites on the underlying network socket.
- */
- public void setEnabledCiphers(String[] enabledCiphers) {
- this.enabledCiphers = enabledCiphers;
- if ((socket != null) && (enabledCiphers != null)) {
- if (trace.isOn()) {
- String ciphers = "";
- for (int i=0;i0) {
- ciphers+=",";
- }
- ciphers+=enabledCiphers[i];
- }
- //@TRACE 260=setEnabledCiphers ciphers={0}
- trace.trace(Trace.FINE,260,new Object[]{ciphers});
- }
- ((SSLSocket) socket).setEnabledCipherSuites(enabledCiphers);
- }
- }
-
- public void setSSLhandshakeTimeout(int timeout) {
- this.handshakeTimeoutSecs = timeout;
- }
-
- public void start() throws IOException, MqttException {
- super.start();
- setEnabledCiphers(enabledCiphers);
- int soTimeout = socket.getSoTimeout();
- if ( soTimeout == 0 ) {
- // RTC 765: Set a timeout to avoid the SSL handshake being blocked indefinitely
- socket.setSoTimeout(this.handshakeTimeoutSecs*1000);
- }
- ((SSLSocket)socket).startHandshake();
- // reset timeout to default value
- socket.setSoTimeout(soTimeout);
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal;
+
+import java.io.IOException;
+
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.logging.Logger;
+import org.eclipse.paho.client.mqttv3.logging.LoggerFactory;
+
+/**
+ * A network module for connecting over SSL.
+ */
+public class SSLNetworkModule extends TCPNetworkModule {
+ private static final String CLASS_NAME = SSLNetworkModule.class.getName();
+ private static final Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT,CLASS_NAME);
+
+ private String[] enabledCiphers;
+ private int handshakeTimeoutSecs;
+
+ /**
+ * Constructs a new SSLNetworkModule using the specified host and
+ * port. The supplied SSLSocketFactory is used to supply the network
+ * socket.
+ */
+ public SSLNetworkModule(SSLSocketFactory factory, String host, int port, String resourceContext) {
+ super(factory, host, port, resourceContext);
+ log.setResourceName(resourceContext);
+ }
+
+ /**
+ * Returns the enabled cipher suites.
+ */
+ public String[] getEnabledCiphers() {
+ return enabledCiphers;
+ }
+
+ /**
+ * Sets the enabled cipher suites on the underlying network socket.
+ */
+ public void setEnabledCiphers(String[] enabledCiphers) {
+ final String methodName = "setEnabledCiphers";
+ this.enabledCiphers = enabledCiphers;
+ if ((socket != null) && (enabledCiphers != null)) {
+ if (log.isLoggable(Logger.FINE)) {
+ String ciphers = "";
+ for (int i=0;i0) {
+ ciphers+=",";
+ }
+ ciphers+=enabledCiphers[i];
+ }
+ //@TRACE 260=setEnabledCiphers ciphers={0}
+ log.fine(CLASS_NAME,methodName,"260",new Object[]{ciphers});
+ }
+ ((SSLSocket) socket).setEnabledCipherSuites(enabledCiphers);
+ }
+ }
+
+ public void setSSLhandshakeTimeout(int timeout) {
+ super.setConnectTimeout(timeout);
+ this.handshakeTimeoutSecs = timeout;
+ }
+
+ public void start() throws IOException, MqttException {
+ super.start();
+ setEnabledCiphers(enabledCiphers);
+ int soTimeout = socket.getSoTimeout();
+ if ( soTimeout == 0 ) {
+ // RTC 765: Set a timeout to avoid the SSL handshake being blocked indefinitely
+ socket.setSoTimeout(this.handshakeTimeoutSecs*1000);
+ }
+ ((SSLSocket)socket).startHandshake();
+ // reset timeout to default value
+ socket.setSoTimeout(soTimeout);
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/TCPNetworkModule.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/TCPNetworkModule.java
new file mode 100644
index 00000000..b11d808f
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/TCPNetworkModule.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ConnectException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+
+import javax.net.SocketFactory;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.logging.Logger;
+import org.eclipse.paho.client.mqttv3.logging.LoggerFactory;
+
+/**
+ * A network module for connecting over TCP.
+ */
+public class TCPNetworkModule implements NetworkModule {
+ private static final String CLASS_NAME = TCPNetworkModule.class.getName();
+ private static final Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT,CLASS_NAME);
+
+ protected Socket socket;
+ private SocketFactory factory;
+ private String host;
+ private int port;
+ private int conTimeout;
+
+ /**
+ * Constructs a new TCPNetworkModule using the specified host and
+ * port. The supplied SocketFactory is used to supply the network
+ * socket.
+ */
+ public TCPNetworkModule(SocketFactory factory, String host, int port, String resourceContext) {
+ log.setResourceName(resourceContext);
+ this.factory = factory;
+ this.host = host;
+ this.port = port;
+
+ }
+
+ /**
+ * Starts the module, by creating a TCP socket to the server.
+ */
+ public void start() throws IOException, MqttException {
+ final String methodName = "start";
+ try {
+// InetAddress localAddr = InetAddress.getLocalHost();
+// socket = factory.createSocket(host, port, localAddr, 0);
+ // @TRACE 252=connect to host {0} port {1} timeout {2}
+ log.fine(CLASS_NAME,methodName, "252", new Object[] {host, new Integer(port), new Long(conTimeout*1000)});
+ SocketAddress sockaddr = new InetSocketAddress(host, port);
+ socket = factory.createSocket();
+ socket.connect(sockaddr, conTimeout*1000);
+
+ // SetTcpNoDelay was originally set ot true disabling Nagle's algorithm.
+ // This should not be required.
+// socket.setTcpNoDelay(true); // TCP_NODELAY on, which means we do not use Nagle's algorithm
+ }
+ catch (ConnectException ex) {
+ //@TRACE 250=Failed to create TCP socket
+ log.fine(CLASS_NAME,methodName,"250",null,ex);
+ throw new MqttException(MqttException.REASON_CODE_SERVER_CONNECT_ERROR, ex);
+ }
+ }
+
+ public InputStream getInputStream() throws IOException {
+ return socket.getInputStream();
+ }
+
+ public OutputStream getOutputStream() throws IOException {
+ return socket.getOutputStream();
+ }
+
+ /**
+ * Stops the module, by closing the TCP socket.
+ */
+ public void stop() throws IOException {
+ if (socket != null) {
+ socket.close();
+ }
+ }
+
+ /**
+ * Set the maximum time to wait for a socket to be established
+ * @param timeout
+ */
+ public void setConnectTimeout(int timeout) {
+ this.conTimeout = timeout;
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/Token.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/Token.java
new file mode 100644
index 00000000..6dcf3427
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/Token.java
@@ -0,0 +1,392 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Ian Craggs - MQTT 3.1.1 support
+ */
+
+package org.eclipse.paho.client.mqttv3.internal;
+
+import org.eclipse.paho.client.mqttv3.IMqttActionListener;
+import org.eclipse.paho.client.mqttv3.IMqttAsyncClient;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttAck;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttConnack;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttSuback;
+import org.eclipse.paho.client.mqttv3.internal.wire.MqttWireMessage;
+import org.eclipse.paho.client.mqttv3.logging.Logger;
+import org.eclipse.paho.client.mqttv3.logging.LoggerFactory;
+
+public class Token {
+ private static final String CLASS_NAME = Token.class.getName();
+ private static final Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT,CLASS_NAME);
+
+ private volatile boolean completed = false;
+ private boolean pendingComplete = false;
+ private boolean sent = false;
+
+ private Object responseLock = new Object();
+ private Object sentLock = new Object();
+
+ protected MqttMessage message = null;
+ private MqttWireMessage response = null;
+ private MqttException exception = null;
+ private String[] topics = null;
+
+ private String key;
+
+ private IMqttAsyncClient client = null;
+ private IMqttActionListener callback = null;
+
+ private Object userContext = null;
+
+ private int messageID = 0;
+ private boolean notified = false;
+
+ public Token(String logContext) {
+ log.setResourceName(logContext);
+ }
+
+ public int getMessageID() {
+ return messageID;
+ }
+
+ public void setMessageID(int messageID) {
+ this.messageID = messageID;
+ }
+
+ public boolean checkResult() throws MqttException {
+ if ( getException() != null) {
+ throw getException();
+ }
+ return true;
+ }
+
+ public MqttException getException() {
+ return exception;
+ }
+
+ public boolean isComplete() {
+ return completed;
+ }
+
+ protected boolean isCompletePending() {
+ return pendingComplete;
+ }
+
+ protected boolean isInUse() {
+ return (getClient() != null && !isComplete());
+ }
+
+ public void setActionCallback(IMqttActionListener listener) {
+ this.callback = listener;
+
+ }
+ public IMqttActionListener getActionCallback() {
+ return callback;
+ }
+
+ public void waitForCompletion() throws MqttException {
+ waitForCompletion(-1);
+ }
+
+ public void waitForCompletion(long timeout) throws MqttException {
+ final String methodName = "waitForCompletion";
+ //@TRACE 407=key={0} wait max={1} token={2}
+ log.fine(CLASS_NAME,methodName, "407",new Object[]{getKey(), new Long(timeout), this});
+
+ MqttWireMessage resp = waitForResponse(timeout);
+ if (resp == null && !completed) {
+ //@TRACE 406=key={0} timed out token={1}
+ log.fine(CLASS_NAME,methodName, "406",new Object[]{getKey(), this});
+ exception = new MqttException(MqttException.REASON_CODE_CLIENT_TIMEOUT);
+ throw exception;
+ }
+ checkResult();
+ }
+
+ /**
+ * Waits for the message delivery to complete, but doesn't throw an exception
+ * in the case of a NACK. It does still throw an exception if something else
+ * goes wrong (e.g. an IOException). This is used for packets like CONNECT,
+ * which have useful information in the ACK that needs to be accessed.
+ */
+ protected MqttWireMessage waitForResponse() throws MqttException {
+ return waitForResponse(-1);
+ }
+
+ protected MqttWireMessage waitForResponse(long timeout) throws MqttException {
+ final String methodName = "waitForResponse";
+ synchronized (responseLock) {
+ //@TRACE 400=>key={0} timeout={1} sent={2} completed={3} hasException={4} response={5} token={6}
+ log.fine(CLASS_NAME, methodName, "400",new Object[]{getKey(), new Long(timeout),new Boolean(sent),new Boolean(completed),(exception==null)?"false":"true",response,this},exception);
+
+ while (!this.completed) {
+ if (this.exception == null) {
+ try {
+ //@TRACE 408=key={0} wait max={1}
+ log.fine(CLASS_NAME,methodName,"408",new Object[] {getKey(),new Long(timeout)});
+
+ if (timeout <= 0) {
+ responseLock.wait();
+ } else {
+ responseLock.wait(timeout);
+ }
+ } catch (InterruptedException e) {
+ exception = new MqttException(e);
+ }
+ }
+ if (!this.completed) {
+ if (this.exception != null) {
+ //@TRACE 401=failed with exception
+ log.fine(CLASS_NAME,methodName,"401",null,exception);
+ throw exception;
+ }
+
+ if (timeout > 0) {
+ // time up and still not completed
+ break;
+ }
+ }
+ }
+ }
+ //@TRACE 402=key={0} response={1}
+ log.fine(CLASS_NAME,methodName, "402",new Object[]{getKey(), this.response});
+ return this.response;
+ }
+
+ /**
+ * Mark the token as complete and ready for users to be notified.
+ * @param msg response message. Optional - there are no response messages for some flows
+ * @param ex if there was a problem store the exception in the token.
+ */
+ protected void markComplete(MqttWireMessage msg, MqttException ex) {
+ final String methodName = "markComplete";
+ //@TRACE 404=>key={0} response={1} excep={2}
+ log.fine(CLASS_NAME,methodName,"404",new Object[]{getKey(),msg,ex});
+
+ synchronized(responseLock) {
+ // ACK means that everything was OK, so mark the message for garbage collection.
+ if (msg instanceof MqttAck) {
+ this.message = null;
+ }
+ this.pendingComplete = true;
+ this.response = msg;
+ this.exception = ex;
+ }
+ }
+ /**
+ * Notifies this token that a response message (an ACK or NACK) has been
+ * received.
+ */
+ protected void notifyComplete() {
+ final String methodName = "notifyComplete";
+ //@TRACE 411=>key={0} response={1} excep={2}
+ log.fine(CLASS_NAME,methodName,"404",new Object[]{getKey(),this.response, this.exception});
+
+ synchronized (responseLock) {
+ // If pending complete is set then normally the token can be marked
+ // as complete and users notified. An abnormal error may have
+ // caused the client to shutdown beween pending complete being set
+ // and notifying the user. In this case - the action must be failed.
+ if (exception == null && pendingComplete) {
+ completed = true;
+ pendingComplete = false;
+ } else {
+ pendingComplete = false;
+ }
+
+ responseLock.notifyAll();
+ }
+ synchronized (sentLock) {
+ sent=true;
+ sentLock.notifyAll();
+ }
+ }
+
+// /**
+// * Notifies this token that an exception has occurred. This is only
+// * used for things like IOException, and not for MQTT NACKs.
+// */
+// protected void notifyException() {
+// final String methodName = "notifyException";
+// //@TRACE 405=token={0} excep={1}
+// log.fine(CLASS_NAME,methodName, "405",new Object[]{this,this.exception});
+// synchronized (responseLock) {
+// responseLock.notifyAll();
+// }
+// synchronized (sentLock) {
+// sentLock.notifyAll();
+// }
+// }
+
+ public void waitUntilSent() throws MqttException {
+ final String methodName = "waitUntilSent";
+ synchronized (sentLock) {
+ synchronized (responseLock) {
+ if (this.exception != null) {
+ throw this.exception;
+ }
+ }
+ while (!sent) {
+ try {
+ //@TRACE 409=wait key={0}
+ log.fine(CLASS_NAME,methodName, "409",new Object[]{getKey()});
+
+ sentLock.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ while (!sent) {
+ if (this.exception == null) {
+ throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_UNEXPECTED_ERROR);
+ }
+ throw this.exception;
+ }
+ }
+ }
+
+ /**
+ * Notifies this token that the associated message has been sent
+ * (i.e. written to the TCP/IP socket).
+ */
+ protected void notifySent() {
+ final String methodName = "notifySent";
+ //@TRACE 403=> key={0}
+ log.fine(CLASS_NAME, methodName, "403",new Object[]{getKey()});
+ synchronized (responseLock) {
+ this.response = null;
+ this.completed = false;
+ }
+ synchronized (sentLock) {
+ sent = true;
+ sentLock.notifyAll();
+ }
+ }
+
+ public IMqttAsyncClient getClient() {
+ return client;
+ }
+
+ protected void setClient(IMqttAsyncClient client) {
+ this.client = client;
+ }
+
+ public void reset() throws MqttException {
+ final String methodName = "reset";
+ if (isInUse() ) {
+ // Token is already in use - cannot reset
+ throw new MqttException(MqttException.REASON_CODE_TOKEN_INUSE);
+ }
+ //@TRACE 410=> key={0}
+ log.fine(CLASS_NAME, methodName, "410",new Object[]{getKey()});
+
+ client = null;
+ completed = false;
+ response = null;
+ sent = false;
+ exception = null;
+ userContext = null;
+ }
+
+ public MqttMessage getMessage() {
+ return message;
+ }
+
+ public MqttWireMessage getWireMessage() {
+ return response;
+ }
+
+
+ public void setMessage(MqttMessage msg) {
+ this.message = msg;
+ }
+
+ public String[] getTopics() {
+ return topics;
+ }
+
+ public void setTopics(String[] topics) {
+ this.topics = topics;
+ }
+
+ public Object getUserContext() {
+ return userContext;
+ }
+
+ public void setUserContext(Object userContext) {
+ this.userContext = userContext;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setException(MqttException exception) {
+ synchronized(responseLock) {
+ this.exception = exception;
+ }
+ }
+
+ public boolean isNotified() {
+ return notified;
+ }
+
+ public void setNotified(boolean notified) {
+ this.notified = notified;
+ }
+
+ public String toString() {
+ StringBuffer tok = new StringBuffer();
+ tok.append("key=").append(getKey());
+ tok.append(" ,topics=");
+ if (getTopics() != null) {
+ for (int i=0; i
- * The SSLSocketFactoryFactory is configured using IBM SSL properties, i.e.
- * properties of the format "com.ibm.ssl.propertyName", e.g.
- * "com.ibm.ssl.keyStore". The class supports multiple configurations, each
- * configuration is identified using a name or configuration ID. The
- * configuration ID with "null" is used as a default configuration. When a
- * socket factory is being created for a given configuration, properties of that
- * configuration are first picked. If a property is not defined there, then that
- * property is looked up in the default configuration. Finally, if a property
- * element is still not found, then the corresponding system property is
- * inspected, i.e. javax.net.ssl.keyStore. If the system property is not set
- * either, then the system's default value is used (if available) or an
- * exception is thrown.
- *
- * The SSLSocketFacotryFactory can be reconfigured at any time. A
- * reconfiguration does not affect existing socket factories.
- *
- * All properties share the same key space; i.e. the configuration ID is not
- * part of the property keys.
- *
- * The methods should be called in the following order:
- *
- *
isSupportedOnJVM(): to check whether this class is supported on
- * the runtime platform. This should be Desktop platforms (i.e. Java SE 6.0, SE
- * 5.0, SE 1.42, and Expeditor Desktop EE (jclDesktop) 6.2).
- *
SSLSocketFactoryFactory(): the constructor, which should be
- * called once per MicroBroker. Clients (in the same JVM) may share an
- * SSLSocketFactoryFactory, or have one each.
- *
initialize(properties, configID): to initialize this object with
- * the required SSL properties for a configuration. This may be called multiple
- * times, once for each required configuration (e.g. once per Listener, once per
- * Pipe, once per Client). It may be called again to change the required SSL
- * properties for a particular configuration
- *
getEnabledCipherSuites(configID): to later set the enabled
- * cipher suites on the socket [see below].
- *
- *
- *
MicroBroker Server:
- *
- *
getKeyStore(configID): Optionally, to check that if there is no
- * keystore, then that all the enabled cipher suits are anonymous.
- *
createServerSocketFactory(configID): to create an
- * SSLServerSocketFactory.
- *
getClientAuthentication(configID): to later set on the
- * SSLServerSocket (itself created from the SSLServerSocketFactory) whether
- * client authentication is needed.
- *
- *
Client:
- *
- *
createSocketFactory(configID): to create an SSLSocketFactory.
- *
- *
- */
-public class SSLSocketFactoryFactory {
- private final static String CLASS_NAME = "org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory";
- /**
- * Property keys specific to the MicroBroker (and client).
- */
- public final static String SSLPROTOCOL="com.ibm.ssl.protocol";
- public final static String JSSEPROVIDER="com.ibm.ssl.contextProvider";
- public final static String KEYSTORE="com.ibm.ssl.keyStore";
- public final static String KEYSTOREPWD="com.ibm.ssl.keyStorePassword";
- public final static String KEYSTORETYPE="com.ibm.ssl.keyStoreType";
- public final static String KEYSTOREPROVIDER="com.ibm.ssl.keyStoreProvider";
- public final static String KEYSTOREMGR="com.ibm.ssl.keyManager";
- public final static String TRUSTSTORE="com.ibm.ssl.trustStore";
- public final static String TRUSTSTOREPWD="com.ibm.ssl.trustStorePassword";
- public final static String TRUSTSTORETYPE="com.ibm.ssl.trustStoreType";
- public final static String TRUSTSTOREPROVIDER="com.ibm.ssl.trustStoreProvider";
- public final static String TRUSTSTOREMGR="com.ibm.ssl.trustManager";
- public final static String CIPHERSUITES="com.ibm.ssl.enabledCipherSuites";
- public final static String CLIENTAUTH="com.ibm.ssl.clientAuthentication";
-
- /**
- * Property keys used for java system properties
- */
- public final static String SYSKEYSTORE="javax.net.ssl.keyStore";
- public final static String SYSKEYSTORETYPE="javax.net.ssl.keyStoreType";
- public final static String SYSKEYSTOREPWD="javax.net.ssl.keyStorePassword";
- public final static String SYSTRUSTSTORE="javax.net.ssl.trustStore";
- public final static String SYSTRUSTSTORETYPE="javax.net.ssl.trustStoreType";
- public final static String SYSTRUSTSTOREPWD="javax.net.ssl.trustStorePassword";
- public final static String SYSKEYMGRALGO="ssl.KeyManagerFactory.algorithm";
- public final static String SYSTRUSTMGRALGO="ssl.TrustManagerFactory.algorithm";
-
-
- public final static String DEFAULT_PROTOCOL = "TLS"; // "SSL_TLS" is not supported by DesktopEE
-
- private final static String propertyKeys[] = { SSLPROTOCOL, JSSEPROVIDER,
- KEYSTORE, KEYSTOREPWD, KEYSTORETYPE, KEYSTOREPROVIDER, KEYSTOREMGR,
- TRUSTSTORE, TRUSTSTOREPWD, TRUSTSTORETYPE, TRUSTSTOREPROVIDER,
- TRUSTSTOREMGR, CIPHERSUITES, CLIENTAUTH};
-
- private Hashtable configs; // a hashtable that maps configIDs to properties.
-
- private Properties defaultProperties;
-
- private static final byte[] key = { (byte) 0x9d, (byte) 0xa7, (byte) 0xd9,
- (byte) 0x80, (byte) 0x05, (byte) 0xb8, (byte) 0x89, (byte) 0x9c };
-
- private static final String xorTag = "{xor}";
-
- private Logger logger = null;
-
-
- /**
- * Not all of the JVM/Platforms that MicroBroker runs on support all of its
- * security features. This allows the MicroBroker to determine whether SSL
- * is supported.
- *
- * @return whether dependent classes can be instantiated on the current
- * JVM/platform.
- *
- * @throws Error
- * if any unexpected error encountered whilst checking. Note
- * this should not be a ClassNotFoundException, which should
- * cause the method to return false.
- */
- public static boolean isSupportedOnJVM() throws LinkageError, ExceptionInInitializerError {
- String requiredClassname = "javax.net.ssl.SSLServerSocketFactory";
- try {
- Class.forName(requiredClassname);
- } catch (ClassNotFoundException e) {
- return false;
- }
- return true;
- }
-
-
- /**
- * Create new instance of class.
- * Constructor used by clients.
- */
- public SSLSocketFactoryFactory() {
- configs = new Hashtable();
- }
-
- /**
- * Create new instance of class.
- * Constructor used by the broker.
- */
- public SSLSocketFactoryFactory(Logger logger) {
- this();
- this.logger = logger;
- }
-
- /**
- * Checks whether a key belongs to the supported IBM SSL property keys.
- *
- * @param key
- * @return whether a key belongs to the supported IBM SSL property keys.
- */
- private boolean keyValid(String key) {
- int i = 0;
- while (i < propertyKeys.length) {
- if (propertyKeys[i].equals(key)) {
- break;
- }
- ++i;
- }
- return i < propertyKeys.length;
- }
-
- /**
- * Checks whether the property keys belong to the supported IBM SSL property
- * key set.
- *
- * @param properties
- * @throws IllegalArgumentException
- * if any of the properties is not a valid IBM SSL property key.
- */
- private void checkPropertyKeys(Properties properties)
- throws IllegalArgumentException {
- Set keys = properties.keySet();
- Iterator i = keys.iterator();
- while (i.hasNext()) {
- String k = (String) i.next();
- if (!keyValid(k)) {
- throw new IllegalArgumentException(k + " is not a valid IBM SSL property key.");
- }
- }
- }
-
- /**
- * Convert byte array to char array, where each char is constructed from two
- * bytes.
- *
- * @param b
- * byte array
- * @return char array
- */
- public static char[] toChar(byte[] b) {
- if(b==null) return null;
- char[] c= new char[b.length/2];
- int i=0; int j=0;
- while(i> 8)& 0xFF);
- }
- return b;
- }
-
- /**
- * Obfuscates the password using a simple and not very secure XOR mechanism.
- * This should not be used for cryptographical purpose, it's a simple
- * scrambler to obfuscate clear-text passwords.
- *
- * @see org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory#deObfuscate
- *
- * @param password
- * The password to be encrypted, as a char[] array.
- * @return An obfuscated password as a String.
- */
- public static String obfuscate(char[] password) {
- if (password == null)
- return null;
- byte[] bytes = toByte(password);
- for (int i = 0; i < bytes.length; i++) {
- bytes[i] = (byte) ((bytes[i] ^ key[i % key.length]) & 0x00ff);
- }
- String encryptedValue = xorTag
- + new String(SimpleBase64Encoder.encode(bytes));
- return encryptedValue;
- }
-
- /**
- * The inverse operation of obfuscate: returns a cleartext password that was
- * previously obfuscated using the XOR scrambler.
- *
- * @see org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory#obfuscate
- *
- * @param ePassword
- * An obfuscated password.
- * @return An array of char, containing the clear text password.
- */
- public static char[] deObfuscate(String ePassword) {
- if (ePassword == null)
- return null;
- byte[] bytes = null;
- try {
- bytes = SimpleBase64Encoder.decode(ePassword.substring(xorTag
- .length()));
- } catch (Exception e) {
- return null;
- }
-
- for (int i = 0; i < bytes.length; i++) {
- bytes[i] = (byte) ((bytes[i] ^ key[i % key.length]) & 0x00ff);
- }
- return toChar(bytes);
- }
-
- /**
- * Converts an array of ciphers into a single String.
- *
- * @param ciphers
- * The array of cipher names.
- * @return A string containing the name of the ciphers, separated by comma.
- */
- public static String packCipherSuites(String[] ciphers) {
- String cipherSet=null;
- if (ciphers != null) {
- StringBuffer buf = new StringBuffer();
- for (int i = 0; i < ciphers.length; i++) {
- buf.append(ciphers[i]);
- if (i < ciphers.length - 1) {
- buf.append(',');
- }
- }
- cipherSet = buf.toString();
- }
- return cipherSet;
- }
-
- /**
- * Inverse operation of packCipherSuites: converts a string of cipher names
- * into an array of cipher names
- *
- * @param ciphers
- * A list of ciphers, separated by comma.
- * @return An array of string, each string containing a single cipher name.
- */
- public static String[] unpackCipherSuites(String ciphers) {
- // can't use split as split is not available on all java platforms.
- if(ciphers==null) return null;
- Vector c=new Vector();
- int i=ciphers.indexOf(',');
- int j=0;
- // handle all commas.
- while(i>-1) {
- // add stuff before and up to (but not including) the comma.
- c.add(ciphers.substring(j, i));
- j=i+1; // skip the comma.
- i=ciphers.indexOf(',',j);
- }
- // add last element after the comma or only element if no comma is present.
- c.add(ciphers.substring(j));
- String[] s = new String[c.size()];
- c.toArray(s);
- return s;
- }
-
- /**
- * Obfuscate any key & trust store passwords within the given properties.
- *
- * @see org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory#obfuscate
- *
- * @param p
- * properties
- */
- private void convertPassword(Properties p) {
- String pw = p.getProperty(KEYSTOREPWD);
- if (pw != null && !pw.startsWith(xorTag)) {
- String epw = obfuscate(pw.toCharArray());
- p.put(KEYSTOREPWD, epw);
- }
- pw = p.getProperty(TRUSTSTOREPWD);
- if (pw != null && !pw.startsWith(xorTag)) {
- String epw = obfuscate(pw.toCharArray());
- p.put(TRUSTSTOREPWD, epw);
- }
- }
-
- /**
- * Returns the properties object for configuration configID or creates a new
- * one if required.
- *
- * @param configID
- * The configuration identifier for selecting a configuration or
- * null for the default configuration.
- * @return the properties object for configuration configID
- */
-// private Properties getOrCreate(String configID) {
-// Properties res = null;
-// if (configID == null) {
-// if (this.defaultProperties == null) {
-// this.defaultProperties = new Properties();
-// }
-// res = this.defaultProperties;
-// } else {
-// res = (Properties) this.configs.get(configID);
-// if (res == null) {
-// res = new Properties();
-// this.configs.put(configID, res);
-// }
-// }
-// return res;
-// }
-
- /**
- * Initializes the SSLSocketFactoryFactory with the provided properties for
- * the provided configuration.
- *
- * This may be called multiple times, once for each required configuration
- * (e.g. once per Listener, once per Pipe, once per Client). It may be
- * called again to change the required SSL properties for a particular
- * configuration
- *
- * @param props
- * A properties object containing IBM SSL properties that are
- * qualified by one or more configuration identifiers.
- * @param configID
- * The configuration identifier for selecting a configuration or
- * null for the default configuration.
- * @throws IllegalArgumentException
- * if any of the properties is not a valid IBM SSL property key.
- */
- public void initialize(Properties props, String configID)
- throws IllegalArgumentException {
- checkPropertyKeys(props);
- // copy the properties.
- Properties p = new Properties();
- p.putAll(props);
- convertPassword(p);
- if (configID != null) {
- this.configs.put(configID, p);
- } else {
- this.defaultProperties = p;
- }
- }
-
- /**
- * Merges the given IBM SSL properties into the existing configuration,
- * overwriting existing properties. This method is used to selectively
- * change properties for a given configuration. The method throws an
- * IllegalArgumentException if any of the properties is not a valid IBM SSL
- * property key.
- *
- * @param props
- * A properties object containing IBM SSL properties
- * @param configID
- * The configuration identifier for selecting a configuration or
- * null for the default configuration.
- * @throws IllegalArgumentException
- * if any of the properties is not a valid IBM SSL property key.
- */
- public void merge(Properties props, String configID)
- throws IllegalArgumentException {
- checkPropertyKeys(props);
- Properties p = this.defaultProperties;
- if (configID == null) {
- p = (Properties) this.configs.get(configID);
- }
- if (p == null) {
- p = new Properties();
- }
- convertPassword(props);
- p.putAll(props);
- if (configID != null) {
- this.configs.put(configID, p);
- } else {
- this.defaultProperties = p;
- }
-
- }
-
- /**
- * Remove the configuration of a given configuration identifier.
- *
- * @param configID
- * The configuration identifier for selecting a configuration or
- * null for the default configuration.
- * @return true, if the configuation could be removed.
- */
- public boolean remove(String configID) {
- boolean res = false;
- if (configID != null) {
- res = this.configs.remove(configID) != null;
- } else {
- if(null != this.defaultProperties) {
- res = true;
- this.defaultProperties = null;
- }
- }
- return res;
- }
-
- /**
- * Returns the configuration of the SSLSocketFactoryFactory for a given
- * configuration. Note that changes in the property are reflected in the
- * SSLSocketFactoryFactory.
- *
- * @param configID
- * The configuration identifier for selecting a configuration or
- * null for the default configuration.
- * @return A property object containing the current configuration of the
- * SSLSocketFactoryFactory. Note that it could be null.
- */
- public Properties getConfiguration(String configID) {
- return (Properties) (configID == null ? this.defaultProperties
- : this.configs.get(configID));
- }
-
- /**
- * @return Returns the set of configuration IDs that exist in the SSLSocketFactoryFactory.
- */
-// public String[] getConfigurationIDs() {
-// Set s = this.configs.keySet();
-// String[] configs = new String[s.size()];
-// configs = (String[]) s.toArray(configs);
-// return configs;
-// }
-
- /**
- * If the value is not null, then put it in the properties object using the
- * key. If the value is null, then remove the entry in the properties object
- * with the key.
- *
- * @param p
- * @param key
- * @param value
- */
-// private final void putOrRemove(Properties p, String key, String value) {
-// if (value == null) {
-// p.remove(key);
-// } else {
-// p.put(key, value);
-// }
-// }
-
- /**
- * Sets the SSL protocol variant. If protocol is NULL then an existing value
- * will be removed.
- *
- * @param configID
- * The configuration identifier for selecting a configuration or
- * null for the default configuration.
- * @param protocol
- * One of SSL, SSLv3, TLS, TLSv1, SSL_TLS
- */
-// public void setSSLProtocol(String configID, String protocol) {
-// Properties p = getOrCreate(configID);
-// putOrRemove(p, SSLPROTOCOL, protocol);
-// }
-
- /**
- * Sets the JSSE context provider. If provider is null, then an existing
- * value will be removed.
- *
- * @param configID
- * The configuration identifier for selecting a configuration or
- * null for the default configuration.
- * @param provider
- * The JSSE provider. For example "IBMJSSE2" or "SunJSSE".
- */
-// public void setJSSEProvider(String configID, String provider) {
-// Properties p = getOrCreate(configID);
-// putOrRemove(p, JSSEPROVIDER, provider);
-// }
-
- /**
- * Sets the filename of the keyStore object. A null value is ignored.
- *
- * @param configID
- * The configuration identifier for selecting a configuration or
- * null for the default configuration.
- * @param keyStore
- * A filename that points to a valid keystore.
- */
-// public void setKeyStore(String configID, String keyStore) {
-// if (keyStore == null)
-// return;
-// Properties p = getOrCreate(configID);
-// putOrRemove(p, KEYSTORE, keyStore);
-// }
-
- /**
- * Sets the password that is used for the keystore. The password must be
- * provided in plain text, but it will be stored internally in a scrambled
- * XOR format.
- *
- * @see org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory#obfuscate
- *
- * @param configID
- * The configuration identifier for selecting a configuration or
- * null for the default configuration.
- * @param password
- * The keystore password
- */
-// public void setKeyStorePassword(String configID, char[] password) {
-// if (password == null)
-// return;
-// Properties p = getOrCreate(configID);
-// // convert password, using XOR-based scrambling.
-// String ePasswd = obfuscate(password);
-// for(int i=0;i
+ * The SSLSocketFactoryFactory is configured using IBM SSL properties, i.e.
+ * properties of the format "com.ibm.ssl.propertyName", e.g.
+ * "com.ibm.ssl.keyStore". The class supports multiple configurations, each
+ * configuration is identified using a name or configuration ID. The
+ * configuration ID with "null" is used as a default configuration. When a
+ * socket factory is being created for a given configuration, properties of that
+ * configuration are first picked. If a property is not defined there, then that
+ * property is looked up in the default configuration. Finally, if a property
+ * element is still not found, then the corresponding system property is
+ * inspected, i.e. javax.net.ssl.keyStore. If the system property is not set
+ * either, then the system's default value is used (if available) or an
+ * exception is thrown.
+ *
+ * The SSLSocketFacotryFactory can be reconfigured at any time. A
+ * reconfiguration does not affect existing socket factories.
+ *
+ * All properties share the same key space; i.e. the configuration ID is not
+ * part of the property keys.
+ *
+ * The methods should be called in the following order:
+ *
+ *
isSupportedOnJVM(): to check whether this class is supported on
+ * the runtime platform. Not all runtimes support SSL/TLS.
+ *
SSLSocketFactoryFactory(): the constructor. Clients
+ * (in the same JVM) may share an SSLSocketFactoryFactory, or have one each.
+ *
initialize(properties, configID): to initialize this object with
+ * the required SSL properties for a configuration. This may be called multiple
+ * times, once for each required configuration.It may be called again to change the required SSL
+ * properties for a particular configuration
+ *
getEnabledCipherSuites(configID): to later set the enabled
+ * cipher suites on the socket [see below].
+ *
+ *
+ *
For an MQTT server:
+ *
+ *
getKeyStore(configID): Optionally, to check that if there is no
+ * keystore, then that all the enabled cipher suits are anonymous.
+ *
createServerSocketFactory(configID): to create an
+ * SSLServerSocketFactory.
+ *
getClientAuthentication(configID): to later set on the
+ * SSLServerSocket (itself created from the SSLServerSocketFactory) whether
+ * client authentication is needed.
+ *
+ *
For an MQTT client:
+ *
+ *
createSocketFactory(configID): to create an SSLSocketFactory.
+ *
+ *
+ */
+public class SSLSocketFactoryFactory {
+ private static final String CLASS_NAME = "org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory";
+ /**
+ * Property keys specific to the client).
+ */
+ public static final String SSLPROTOCOL="com.ibm.ssl.protocol";
+ public static final String JSSEPROVIDER="com.ibm.ssl.contextProvider";
+ public static final String KEYSTORE="com.ibm.ssl.keyStore";
+ public static final String KEYSTOREPWD="com.ibm.ssl.keyStorePassword";
+ public static final String KEYSTORETYPE="com.ibm.ssl.keyStoreType";
+ public static final String KEYSTOREPROVIDER="com.ibm.ssl.keyStoreProvider";
+ public static final String KEYSTOREMGR="com.ibm.ssl.keyManager";
+ public static final String TRUSTSTORE="com.ibm.ssl.trustStore";
+ public static final String TRUSTSTOREPWD="com.ibm.ssl.trustStorePassword";
+ public static final String TRUSTSTORETYPE="com.ibm.ssl.trustStoreType";
+ public static final String TRUSTSTOREPROVIDER="com.ibm.ssl.trustStoreProvider";
+ public static final String TRUSTSTOREMGR="com.ibm.ssl.trustManager";
+ public static final String CIPHERSUITES="com.ibm.ssl.enabledCipherSuites";
+ public static final String CLIENTAUTH="com.ibm.ssl.clientAuthentication";
+
+ /**
+ * Property keys used for java system properties
+ */
+ public static final String SYSKEYSTORE="javax.net.ssl.keyStore";
+ public static final String SYSKEYSTORETYPE="javax.net.ssl.keyStoreType";
+ public static final String SYSKEYSTOREPWD="javax.net.ssl.keyStorePassword";
+ public static final String SYSTRUSTSTORE="javax.net.ssl.trustStore";
+ public static final String SYSTRUSTSTORETYPE="javax.net.ssl.trustStoreType";
+ public static final String SYSTRUSTSTOREPWD="javax.net.ssl.trustStorePassword";
+ public static final String SYSKEYMGRALGO="ssl.KeyManagerFactory.algorithm";
+ public static final String SYSTRUSTMGRALGO="ssl.TrustManagerFactory.algorithm";
+
+
+ public static final String DEFAULT_PROTOCOL = "TLS"; // "SSL_TLS" is not supported by DesktopEE
+
+ private static final String propertyKeys[] = { SSLPROTOCOL, JSSEPROVIDER,
+ KEYSTORE, KEYSTOREPWD, KEYSTORETYPE, KEYSTOREPROVIDER, KEYSTOREMGR,
+ TRUSTSTORE, TRUSTSTOREPWD, TRUSTSTORETYPE, TRUSTSTOREPROVIDER,
+ TRUSTSTOREMGR, CIPHERSUITES, CLIENTAUTH};
+
+ private Hashtable configs; // a hashtable that maps configIDs to properties.
+
+ private Properties defaultProperties;
+
+ private static final byte[] key = { (byte) 0x9d, (byte) 0xa7, (byte) 0xd9,
+ (byte) 0x80, (byte) 0x05, (byte) 0xb8, (byte) 0x89, (byte) 0x9c };
+
+ private static final String xorTag = "{xor}";
+
+ private Logger logger = null;
+
+
+ /**
+ * Not all of the JVM/Platforms support all of its
+ * security features. This method determines if is supported.
+ *
+ * @return whether dependent classes can be instantiated on the current
+ * JVM/platform.
+ *
+ * @throws Error
+ * if any unexpected error encountered whilst checking. Note
+ * this should not be a ClassNotFoundException, which should
+ * cause the method to return false.
+ */
+ public static boolean isSupportedOnJVM() throws LinkageError, ExceptionInInitializerError {
+ String requiredClassname = "javax.net.ssl.SSLServerSocketFactory";
+ try {
+ Class.forName(requiredClassname);
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Create new instance of class.
+ * Constructor used by clients.
+ */
+ public SSLSocketFactoryFactory() {
+ configs = new Hashtable();
+ }
+
+ /**
+ * Create new instance of class.
+ * Constructor used by the broker.
+ */
+ public SSLSocketFactoryFactory(Logger logger) {
+ this();
+ this.logger = logger;
+ }
+
+ /**
+ * Checks whether a key belongs to the supported IBM SSL property keys.
+ *
+ * @param key
+ * @return whether a key belongs to the supported IBM SSL property keys.
+ */
+ private boolean keyValid(String key) {
+ int i = 0;
+ while (i < propertyKeys.length) {
+ if (propertyKeys[i].equals(key)) {
+ break;
+ }
+ ++i;
+ }
+ return i < propertyKeys.length;
+ }
+
+ /**
+ * Checks whether the property keys belong to the supported IBM SSL property
+ * key set.
+ *
+ * @param properties
+ * @throws IllegalArgumentException
+ * if any of the properties is not a valid IBM SSL property key.
+ */
+ private void checkPropertyKeys(Properties properties)
+ throws IllegalArgumentException {
+ Set keys = properties.keySet();
+ Iterator i = keys.iterator();
+ while (i.hasNext()) {
+ String k = (String) i.next();
+ if (!keyValid(k)) {
+ throw new IllegalArgumentException(k + " is not a valid IBM SSL property key.");
+ }
+ }
+ }
+
+ /**
+ * Convert byte array to char array, where each char is constructed from two
+ * bytes.
+ *
+ * @param b
+ * byte array
+ * @return char array
+ */
+ public static char[] toChar(byte[] b) {
+ if(b==null) return null;
+ char[] c= new char[b.length/2];
+ int i=0; int j=0;
+ while(i> 8)& 0xFF);
+ }
+ return b;
+ }
+
+ /**
+ * Obfuscates the password using a simple and not very secure XOR mechanism.
+ * This should not be used for cryptographical purpose, it's a simple
+ * scrambler to obfuscate clear-text passwords.
+ *
+ * @see org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory#deObfuscate
+ *
+ * @param password
+ * The password to be encrypted, as a char[] array.
+ * @return An obfuscated password as a String.
+ */
+ public static String obfuscate(char[] password) {
+ if (password == null)
+ return null;
+ byte[] bytes = toByte(password);
+ for (int i = 0; i < bytes.length; i++) {
+ bytes[i] = (byte) ((bytes[i] ^ key[i % key.length]) & 0x00ff);
+ }
+ String encryptedValue = xorTag
+ + new String(SimpleBase64Encoder.encode(bytes));
+ return encryptedValue;
+ }
+
+ /**
+ * The inverse operation of obfuscate: returns a cleartext password that was
+ * previously obfuscated using the XOR scrambler.
+ *
+ * @see org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory#obfuscate
+ *
+ * @param ePassword
+ * An obfuscated password.
+ * @return An array of char, containing the clear text password.
+ */
+ public static char[] deObfuscate(String ePassword) {
+ if (ePassword == null)
+ return null;
+ byte[] bytes = null;
+ try {
+ bytes = SimpleBase64Encoder.decode(ePassword.substring(xorTag
+ .length()));
+ } catch (Exception e) {
+ return null;
+ }
+
+ for (int i = 0; i < bytes.length; i++) {
+ bytes[i] = (byte) ((bytes[i] ^ key[i % key.length]) & 0x00ff);
+ }
+ return toChar(bytes);
+ }
+
+ /**
+ * Converts an array of ciphers into a single String.
+ *
+ * @param ciphers
+ * The array of cipher names.
+ * @return A string containing the name of the ciphers, separated by comma.
+ */
+ public static String packCipherSuites(String[] ciphers) {
+ String cipherSet=null;
+ if (ciphers != null) {
+ StringBuffer buf = new StringBuffer();
+ for (int i = 0; i < ciphers.length; i++) {
+ buf.append(ciphers[i]);
+ if (i < ciphers.length - 1) {
+ buf.append(',');
+ }
+ }
+ cipherSet = buf.toString();
+ }
+ return cipherSet;
+ }
+
+ /**
+ * Inverse operation of packCipherSuites: converts a string of cipher names
+ * into an array of cipher names
+ *
+ * @param ciphers
+ * A list of ciphers, separated by comma.
+ * @return An array of string, each string containing a single cipher name.
+ */
+ public static String[] unpackCipherSuites(String ciphers) {
+ // can't use split as split is not available on all java platforms.
+ if(ciphers==null) return null;
+ Vector c=new Vector();
+ int i=ciphers.indexOf(',');
+ int j=0;
+ // handle all commas.
+ while(i>-1) {
+ // add stuff before and up to (but not including) the comma.
+ c.add(ciphers.substring(j, i));
+ j=i+1; // skip the comma.
+ i=ciphers.indexOf(',',j);
+ }
+ // add last element after the comma or only element if no comma is present.
+ c.add(ciphers.substring(j));
+ String[] s = new String[c.size()];
+ c.toArray(s);
+ return s;
+ }
+
+ /**
+ * Obfuscate any key & trust store passwords within the given properties.
+ *
+ * @see org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory#obfuscate
+ *
+ * @param p
+ * properties
+ */
+ private void convertPassword(Properties p) {
+ String pw = p.getProperty(KEYSTOREPWD);
+ if (pw != null && !pw.startsWith(xorTag)) {
+ String epw = obfuscate(pw.toCharArray());
+ p.put(KEYSTOREPWD, epw);
+ }
+ pw = p.getProperty(TRUSTSTOREPWD);
+ if (pw != null && !pw.startsWith(xorTag)) {
+ String epw = obfuscate(pw.toCharArray());
+ p.put(TRUSTSTOREPWD, epw);
+ }
+ }
+
+ /**
+ * Returns the properties object for configuration configID or creates a new
+ * one if required.
+ *
+ * @param configID
+ * The configuration identifier for selecting a configuration or
+ * null for the default configuration.
+ * @return the properties object for configuration configID
+ */
+// private Properties getOrCreate(String configID) {
+// Properties res = null;
+// if (configID == null) {
+// if (this.defaultProperties == null) {
+// this.defaultProperties = new Properties();
+// }
+// res = this.defaultProperties;
+// } else {
+// res = (Properties) this.configs.get(configID);
+// if (res == null) {
+// res = new Properties();
+// this.configs.put(configID, res);
+// }
+// }
+// return res;
+// }
+
+ /**
+ * Initializes the SSLSocketFactoryFactory with the provided properties for
+ * the provided configuration.
+ *
+ * @param props
+ * A properties object containing IBM SSL properties that are
+ * qualified by one or more configuration identifiers.
+ * @param configID
+ * The configuration identifier for selecting a configuration or
+ * null for the default configuration.
+ * @throws IllegalArgumentException
+ * if any of the properties is not a valid IBM SSL property key.
+ */
+ public void initialize(Properties props, String configID)
+ throws IllegalArgumentException {
+ checkPropertyKeys(props);
+ // copy the properties.
+ Properties p = new Properties();
+ p.putAll(props);
+ convertPassword(p);
+ if (configID != null) {
+ this.configs.put(configID, p);
+ } else {
+ this.defaultProperties = p;
+ }
+ }
+
+ /**
+ * Merges the given IBM SSL properties into the existing configuration,
+ * overwriting existing properties. This method is used to selectively
+ * change properties for a given configuration. The method throws an
+ * IllegalArgumentException if any of the properties is not a valid IBM SSL
+ * property key.
+ *
+ * @param props
+ * A properties object containing IBM SSL properties
+ * @param configID
+ * The configuration identifier for selecting a configuration or
+ * null for the default configuration.
+ * @throws IllegalArgumentException
+ * if any of the properties is not a valid IBM SSL property key.
+ */
+ public void merge(Properties props, String configID)
+ throws IllegalArgumentException {
+ checkPropertyKeys(props);
+ Properties p = this.defaultProperties;
+ if (configID != null) {
+ p = (Properties) this.configs.get(configID);
+ }
+ if (p == null) {
+ p = new Properties();
+ }
+ convertPassword(props);
+ p.putAll(props);
+ if (configID != null) {
+ this.configs.put(configID, p);
+ } else {
+ this.defaultProperties = p;
+ }
+
+ }
+
+ /**
+ * Remove the configuration of a given configuration identifier.
+ *
+ * @param configID
+ * The configuration identifier for selecting a configuration or
+ * null for the default configuration.
+ * @return true, if the configuation could be removed.
+ */
+ public boolean remove(String configID) {
+ boolean res = false;
+ if (configID != null) {
+ res = this.configs.remove(configID) != null;
+ } else {
+ if(null != this.defaultProperties) {
+ res = true;
+ this.defaultProperties = null;
+ }
+ }
+ return res;
+ }
+
+ /**
+ * Returns the configuration of the SSLSocketFactoryFactory for a given
+ * configuration. Note that changes in the property are reflected in the
+ * SSLSocketFactoryFactory.
+ *
+ * @param configID
+ * The configuration identifier for selecting a configuration or
+ * null for the default configuration.
+ * @return A property object containing the current configuration of the
+ * SSLSocketFactoryFactory. Note that it could be null.
+ */
+ public Properties getConfiguration(String configID) {
+ return (Properties) (configID == null ? this.defaultProperties
+ : this.configs.get(configID));
+ }
+
+ /**
+ * @return Returns the set of configuration IDs that exist in the SSLSocketFactoryFactory.
+ */
+// public String[] getConfigurationIDs() {
+// Set s = this.configs.keySet();
+// String[] configs = new String[s.size()];
+// configs = (String[]) s.toArray(configs);
+// return configs;
+// }
+
+ /**
+ * If the value is not null, then put it in the properties object using the
+ * key. If the value is null, then remove the entry in the properties object
+ * with the key.
+ *
+ * @param p
+ * @param key
+ * @param value
+ */
+// private final void putOrRemove(Properties p, String key, String value) {
+// if (value == null) {
+// p.remove(key);
+// } else {
+// p.put(key, value);
+// }
+// }
+
+ /**
+ * Sets the SSL protocol variant. If protocol is NULL then an existing value
+ * will be removed.
+ *
+ * @param configID
+ * The configuration identifier for selecting a configuration or
+ * null for the default configuration.
+ * @param protocol
+ * One of SSL, SSLv3, TLS, TLSv1, SSL_TLS
+ */
+// public void setSSLProtocol(String configID, String protocol) {
+// Properties p = getOrCreate(configID);
+// putOrRemove(p, SSLPROTOCOL, protocol);
+// }
+
+ /**
+ * Sets the JSSE context provider. If provider is null, then an existing
+ * value will be removed.
+ *
+ * @param configID
+ * The configuration identifier for selecting a configuration or
+ * null for the default configuration.
+ * @param provider
+ * The JSSE provider. For example "IBMJSSE2" or "SunJSSE".
+ */
+// public void setJSSEProvider(String configID, String provider) {
+// Properties p = getOrCreate(configID);
+// putOrRemove(p, JSSEPROVIDER, provider);
+// }
+
+ /**
+ * Sets the filename of the keyStore object. A null value is ignored.
+ *
+ * @param configID
+ * The configuration identifier for selecting a configuration or
+ * null for the default configuration.
+ * @param keyStore
+ * A filename that points to a valid keystore.
+ */
+// public void setKeyStore(String configID, String keyStore) {
+// if (keyStore == null)
+// return;
+// Properties p = getOrCreate(configID);
+// putOrRemove(p, KEYSTORE, keyStore);
+// }
+
+ /**
+ * Sets the password that is used for the keystore. The password must be
+ * provided in plain text, but it will be stored internally in a scrambled
+ * XOR format.
+ *
+ * @see org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory#obfuscate
+ *
+ * @param configID
+ * The configuration identifier for selecting a configuration or
+ * null for the default configuration.
+ * @param password
+ * The keystore password
+ */
+// public void setKeyStorePassword(String configID, char[] password) {
+// if (password == null)
+// return;
+// Properties p = getOrCreate(configID);
+// // convert password, using XOR-based scrambling.
+// String ePasswd = obfuscate(password);
+// for(int i=0;i=3){
- encoded.append(to64((((bytes[i] & 0xff) << 16)
- | (int) ((bytes[i+1] & 0xff) << 8) | (int) (bytes[i+2] & 0xff)),4));
- i+=3;
- j-=3;
- }
- // j==2 | j==1 | j==0
- if(j==2) {
- // there is a rest of 2 bytes. This encodes into 3 chars.
- encoded.append(to64(((bytes[i] &0xff)<<8) | ((bytes[i+1] & 0xff)),3));
- }
- if(j==1) {
- // there is a rest of 1 byte. This encodes into 1 char.
- encoded.append(to64(((bytes[i] & 0xff)),2));
- }
- return encoded.toString();
- }
-
- public static byte[] decode(String string) {
- byte[] encoded=string.getBytes();
- int len=encoded.length;
- byte[] decoded=new byte[len*3/4];
- int i=0;
- int j=len;
- int k=0;
- while(j>=4) {
- long d=from64(encoded, i, 4);
- j-=4;
- i+=4;
- for(int l=2;l>=0;l--) {
- decoded[k+l]=(byte) (d & 0xff);
- d=d >>8;
- }
- k+=3;
- }
- // j==3 | j==2
- if(j==3) {
- long d=from64(encoded, i, 3);
- for(int l=1;l>=0;l--) {
- decoded[k+l]=(byte) (d & 0xff);
- d=d >>8;
- }
- }
- if(j==2) {
- long d=from64(encoded, i, 2);
- decoded[k]=(byte) (d & 0xff);
- }
- return decoded;
- }
-
- /* the core conding routine. Translates an input integer into
- * a string of the given length.*/
- private final static String to64(long input, int size) {
- final StringBuffer result = new StringBuffer(size);
- while (size > 0) {
- size--;
- result.append(PWDCHARS_ARRAY[((int) (input & 0x3f))]);
- input = input >> 6;
- }
- return result.toString();
- }
-
- /*
- * The reverse operation of to64
- */
- private final static long from64(byte[] encoded, int idx, int size) {
- long res=0;
- int f=0;
- while(size>0) {
- size--;
- long r=0;
- // convert encoded[idx] back into a 6-bit value.
- byte d=encoded[idx++];
- if(d=='/') {
- r=1;
- }
- if(d>='0' && d<='9') {
- r=2+d-'0';
- }
- if(d>='A' && d<='Z') {
- r=12+d-'A';
- }
- if(d>='a' && d<='z') {
- r=38+d-'a';
- }
- res=res+((long)r << f);
- f+=6;
- }
- return res;
- }
-
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal.security;
+
+public class SimpleBase64Encoder {
+
+ // if this string is changed, then the decode method must also be adapted.
+ private static final String PWDCHARS_STRING = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ private static final char[] PWDCHARS_ARRAY = PWDCHARS_STRING.toCharArray();
+
+ /**
+ * Encodes an array of byte into a string of printable ASCII characters
+ * using a base-64 encoding.
+ * @param bytes The array of bytes to e encoded
+ * @return The encoded array.
+ */
+ public static String encode(byte[] bytes) {
+ // Allocate a string buffer.
+ int len = bytes.length;
+ final StringBuffer encoded = new StringBuffer((len+2)/3*4);
+ int i=0;
+ int j=len;
+ while(j>=3){
+ encoded.append(to64((((bytes[i] & 0xff) << 16)
+ | (int) ((bytes[i+1] & 0xff) << 8) | (int) (bytes[i+2] & 0xff)),4));
+ i+=3;
+ j-=3;
+ }
+ // j==2 | j==1 | j==0
+ if(j==2) {
+ // there is a rest of 2 bytes. This encodes into 3 chars.
+ encoded.append(to64(((bytes[i] &0xff)<<8) | ((bytes[i+1] & 0xff)),3));
+ }
+ if(j==1) {
+ // there is a rest of 1 byte. This encodes into 1 char.
+ encoded.append(to64(((bytes[i] & 0xff)),2));
+ }
+ return encoded.toString();
+ }
+
+ public static byte[] decode(String string) {
+ byte[] encoded=string.getBytes();
+ int len=encoded.length;
+ byte[] decoded=new byte[len*3/4];
+ int i=0;
+ int j=len;
+ int k=0;
+ while(j>=4) {
+ long d=from64(encoded, i, 4);
+ j-=4;
+ i+=4;
+ for(int l=2;l>=0;l--) {
+ decoded[k+l]=(byte) (d & 0xff);
+ d=d >>8;
+ }
+ k+=3;
+ }
+ // j==3 | j==2
+ if(j==3) {
+ long d=from64(encoded, i, 3);
+ for(int l=1;l>=0;l--) {
+ decoded[k+l]=(byte) (d & 0xff);
+ d=d >>8;
+ }
+ }
+ if(j==2) {
+ long d=from64(encoded, i, 2);
+ decoded[k]=(byte) (d & 0xff);
+ }
+ return decoded;
+ }
+
+ /* the core conding routine. Translates an input integer into
+ * a string of the given length.*/
+ private final static String to64(long input, int size) {
+ final StringBuffer result = new StringBuffer(size);
+ while (size > 0) {
+ size--;
+ result.append(PWDCHARS_ARRAY[((int) (input & 0x3f))]);
+ input = input >> 6;
+ }
+ return result.toString();
+ }
+
+ /*
+ * The reverse operation of to64
+ */
+ private final static long from64(byte[] encoded, int idx, int size) {
+ long res=0;
+ int f=0;
+ while(size>0) {
+ size--;
+ long r=0;
+ // convert encoded[idx] back into a 6-bit value.
+ byte d=encoded[idx++];
+ if(d=='/') {
+ r=1;
+ }
+ if(d>='0' && d<='9') {
+ r=2+d-'0';
+ }
+ if(d>='A' && d<='Z') {
+ r=12+d-'A';
+ }
+ if(d>='a' && d<='z') {
+ r=38+d-'a';
+ }
+ res=res+((long)r << f);
+ f+=6;
+ }
+ return res;
+ }
+
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/CountingInputStream.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/CountingInputStream.java
similarity index 71%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/CountingInputStream.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/CountingInputStream.java
index 74b3bf83..484b72e9 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/CountingInputStream.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/CountingInputStream.java
@@ -1,54 +1,58 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal.wire;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * An input stream that counts the bytes read from it.
- */
-public class CountingInputStream extends InputStream {
- private InputStream in;
- private int counter;
-
- /**
- * Constructs a new CountingInputStream wrapping the supplied
- * input stream.
- */
- public CountingInputStream(InputStream in) {
- this.in = in;
- this.counter = 0;
- }
-
- public int read() throws IOException {
- int i = in.read();
- if (i != -1) {
- counter++;
- }
- return i;
- }
-
- /**
- * Returns the number of bytes read since the last reset.
- */
- public int getCounter() {
- return counter;
- }
-
- /**
- * Resets the counter to zero.
- */
- public void resetCounter() {
- counter = 0;
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal.wire;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An input stream that counts the bytes read from it.
+ */
+public class CountingInputStream extends InputStream {
+ private InputStream in;
+ private int counter;
+
+ /**
+ * Constructs a new CountingInputStream wrapping the supplied
+ * input stream.
+ */
+ public CountingInputStream(InputStream in) {
+ this.in = in;
+ this.counter = 0;
+ }
+
+ public int read() throws IOException {
+ int i = in.read();
+ if (i != -1) {
+ counter++;
+ }
+ return i;
+ }
+
+ /**
+ * Returns the number of bytes read since the last reset.
+ */
+ public int getCounter() {
+ return counter;
+ }
+
+ /**
+ * Resets the counter to zero.
+ */
+ public void resetCounter() {
+ counter = 0;
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttAck.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttAck.java
new file mode 100644
index 00000000..ffc7f41c
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttAck.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal.wire;
+
+
+/**
+ * Abstract super-class of all acknowledgement messages.
+ */
+public abstract class MqttAck extends MqttWireMessage {
+ public MqttAck(byte type) {
+ super(type);
+ }
+
+ protected byte getMessageInfo() {
+ return 0;
+ }
+
+ /**
+ * @return String representation of the wire message
+ */
+ public String toString() {
+ return super.toString() + " msgId " + msgId;
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttConnack.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttConnack.java
similarity index 59%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttConnack.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttConnack.java
index 6b44959c..3d216193 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttConnack.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttConnack.java
@@ -1,51 +1,70 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal.wire;
-
-import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
-import java.io.IOException;
-
-import org.eclipse.paho.client.mqttv3.MqttException;
-
-
-/**
- * An on-the-wire representation of an MQTT CONNACK.
- */
-public class MqttConnack extends MqttAck {
- private int returnCode;
-
- public MqttConnack(byte info, byte[] variableHeader) throws IOException {
- super(MqttWireMessage.MESSAGE_TYPE_CONNACK);
- ByteArrayInputStream bais = new ByteArrayInputStream(variableHeader);
- DataInputStream dis = new DataInputStream(bais);
- dis.readByte();
- returnCode = dis.readUnsignedByte();
- dis.close();
- }
-
- public int getReturnCode() {
- return returnCode;
- }
-
- protected byte[] getVariableHeader() throws MqttException {
- // Not needed, as the client never encodes a CONNACK
- return new byte[0];
- }
-
- /**
- * Returns whether or not this message needs to include a message ID.
- */
- public boolean isMessageIdRequired() {
- return false;
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ * Ian Craggs - MQTT 3.1.1 support
+ */
+package org.eclipse.paho.client.mqttv3.internal.wire;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+
+/**
+ * An on-the-wire representation of an MQTT CONNACK.
+ */
+public class MqttConnack extends MqttAck {
+ public static final String KEY = "Con";
+
+ private int returnCode;
+ private boolean sessionPresent;
+
+ public MqttConnack(byte info, byte[] variableHeader) throws IOException {
+ super(MqttWireMessage.MESSAGE_TYPE_CONNACK);
+ ByteArrayInputStream bais = new ByteArrayInputStream(variableHeader);
+ DataInputStream dis = new DataInputStream(bais);
+ sessionPresent = (dis.readUnsignedByte() & 0x01) == 0x01;
+ returnCode = dis.readUnsignedByte();
+ dis.close();
+ }
+
+ public int getReturnCode() {
+ return returnCode;
+ }
+
+ protected byte[] getVariableHeader() throws MqttException {
+ // Not needed, as the client never encodes a CONNACK
+ return new byte[0];
+ }
+
+ /**
+ * Returns whether or not this message needs to include a message ID.
+ */
+ public boolean isMessageIdRequired() {
+ return false;
+ }
+
+ public String getKey() {
+ return KEY;
+ }
+
+ public String toString() {
+ return super.toString() + " session present:" + sessionPresent + " return code: " + returnCode;
+ }
+
+ public boolean getSessionPresent() {
+ return sessionPresent;
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttConnect.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttConnect.java
similarity index 56%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttConnect.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttConnect.java
index 2ad19039..beaeace0 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttConnect.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttConnect.java
@@ -1,128 +1,168 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal.wire;
-
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-
-import org.eclipse.paho.client.mqttv3.MqttException;
-import org.eclipse.paho.client.mqttv3.MqttMessage;
-import org.eclipse.paho.client.mqttv3.MqttTopic;
-
-
-/**
- * An on-the-wire representation of an MQTT CONNECT message.
- */
-public class MqttConnect extends MqttWireMessage {
- private String clientId;
- private boolean cleanSession;
- private MqttMessage willMessage;
- private String userName;
- private char[] password;
- private int keepAliveInterval;
- private MqttTopic willDestination;
-
-
- public MqttConnect(String clientId,
- boolean cleanSession,
- int keepAliveInterval,
- String userName,
- char[] password,
- MqttMessage willMessage,
- MqttTopic willDestination) {
- super(MqttWireMessage.MESSAGE_TYPE_CONNECT);
- this.clientId = clientId;
- this.cleanSession = cleanSession;
- this.keepAliveInterval = keepAliveInterval;
- this.userName = userName;
- this.password = password;
- this.willMessage = willMessage;
- this.willDestination = willDestination;
- }
-
- protected byte getMessageInfo() {
- return (byte) 0;
- }
-
- public boolean isCleanSession() {
- return cleanSession;
- }
-
- protected byte[] getVariableHeader() throws MqttException {
- try {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(baos);
- dos.writeUTF("MQIsdp");
- dos.write(3);
- byte connectFlags = 0;
-
- if (cleanSession) {
- connectFlags |= 0x02;
- }
-
- if (willMessage != null ) {
- connectFlags |= 0x04;
- connectFlags |= (willMessage.getQos()<<3);
- if (willMessage.isRetained()) {
- connectFlags |= 0x20;
- }
- }
-
- if (userName != null) {
- connectFlags |= 0x80;
- if (password != null) {
- connectFlags |= 0x40;
- }
- }
- dos.write(connectFlags);
- dos.writeShort(keepAliveInterval);
- dos.flush();
- return baos.toByteArray();
- } catch(IOException ioe) {
- throw new MqttException(ioe);
- }
- }
-
- public byte[] getPayload() throws MqttException {
- try {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(baos);
- dos.writeUTF(clientId);
-
- if (willMessage != null) {
- dos.writeUTF(willDestination.getName());
- dos.writeShort(willMessage.getPayload().length);
- dos.write(willMessage.getPayload());
- }
-
- if (userName != null) {
- dos.writeUTF(userName);
- if (password != null) {
- dos.writeUTF(new String(password));
- }
- }
- dos.flush();
- return baos.toByteArray();
- }
- catch (IOException ex) {
- throw new MqttException(ex);
- }
- }
-
- /**
- * Returns whether or not this message needs to include a message ID.
- */
- public boolean isMessageIdRequired() {
- return false;
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ * Ian Craggs - MQTT 3.1.1 support
+ */
+package org.eclipse.paho.client.mqttv3.internal.wire;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+/**
+ * An on-the-wire representation of an MQTT CONNECT message.
+ */
+public class MqttConnect extends MqttWireMessage {
+
+ public static final String KEY = "Con";
+
+ private String clientId;
+ private boolean cleanSession;
+ private MqttMessage willMessage;
+ private String userName;
+ private char[] password;
+ private int keepAliveInterval;
+ private String willDestination;
+ private int MqttVersion;
+
+ /**
+ * Constructor for an on the wire MQTT connect message
+ *
+ * @param info
+ * @param data
+ * @throws IOException
+ * @throws MqttException
+ */
+ public MqttConnect(byte info, byte[] data) throws IOException, MqttException {
+ super(MqttWireMessage.MESSAGE_TYPE_CONNECT);
+ ByteArrayInputStream bais = new ByteArrayInputStream(data);
+ DataInputStream dis = new DataInputStream(bais);
+
+ String protocol_name = decodeUTF8(dis);
+ int protocol_version = dis.readByte();
+ byte connect_flags = dis.readByte();
+ keepAliveInterval = dis.readUnsignedShort();
+ clientId = decodeUTF8(dis);
+ dis.close();
+ }
+
+ public MqttConnect(String clientId, int MqttVersion, boolean cleanSession, int keepAliveInterval, String userName, char[] password, MqttMessage willMessage, String willDestination) {
+ super(MqttWireMessage.MESSAGE_TYPE_CONNECT);
+ this.clientId = clientId;
+ this.cleanSession = cleanSession;
+ this.keepAliveInterval = keepAliveInterval;
+ this.userName = userName;
+ this.password = password;
+ this.willMessage = willMessage;
+ this.willDestination = willDestination;
+ this.MqttVersion = MqttVersion;
+ }
+
+ public String toString() {
+ String rc = super.toString();
+ rc += " clientId " + clientId + " keepAliveInterval " + keepAliveInterval;
+ return rc;
+ }
+
+ protected byte getMessageInfo() {
+ return (byte) 0;
+ }
+
+ public boolean isCleanSession() {
+ return cleanSession;
+ }
+
+ protected byte[] getVariableHeader() throws MqttException {
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(baos);
+
+ if (MqttVersion == 3) {
+ encodeUTF8(dos,"MQIsdp");
+ }
+ else if (MqttVersion == 4) {
+ encodeUTF8(dos,"MQTT");
+ }
+ dos.write(MqttVersion);
+
+ byte connectFlags = 0;
+
+ if (cleanSession) {
+ connectFlags |= 0x02;
+ }
+
+ if (willMessage != null ) {
+ connectFlags |= 0x04;
+ connectFlags |= (willMessage.getQos()<<3);
+ if (willMessage.isRetained()) {
+ connectFlags |= 0x20;
+ }
+ }
+
+ if (userName != null) {
+ connectFlags |= 0x80;
+ if (password != null) {
+ connectFlags |= 0x40;
+ }
+ }
+ dos.write(connectFlags);
+ dos.writeShort(keepAliveInterval);
+ dos.flush();
+ return baos.toByteArray();
+ } catch(IOException ioe) {
+ throw new MqttException(ioe);
+ }
+ }
+
+ public byte[] getPayload() throws MqttException {
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(baos);
+ encodeUTF8(dos,clientId);
+
+ if (willMessage != null) {
+ encodeUTF8(dos,willDestination);
+ dos.writeShort(willMessage.getPayload().length);
+ dos.write(willMessage.getPayload());
+ }
+
+ if (userName != null) {
+ encodeUTF8(dos,userName);
+ if (password != null) {
+ encodeUTF8(dos,new String(password));
+ }
+ }
+ dos.flush();
+ return baos.toByteArray();
+ } catch (IOException ex) {
+ throw new MqttException(ex);
+ }
+ }
+
+ /**
+ * Returns whether or not this message needs to include a message ID.
+ */
+ public boolean isMessageIdRequired() {
+ return false;
+ }
+
+ public String getKey() {
+ return KEY;
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttDisconnect.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttDisconnect.java
similarity index 56%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttDisconnect.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttDisconnect.java
index 1dd8d512..9aa618ad 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttDisconnect.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttDisconnect.java
@@ -1,39 +1,54 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal.wire;
-
-import org.eclipse.paho.client.mqttv3.MqttException;
-
-/**
- * An on-the-wire representation of an MQTT DISCONNECT message.
- */
-public class MqttDisconnect extends MqttWireMessage {
-
- public MqttDisconnect() {
- super(MqttWireMessage.MESSAGE_TYPE_DISCONNECT);
- }
-
- protected byte getMessageInfo() {
- return (byte) 0;
- }
-
- protected byte[] getVariableHeader() throws MqttException {
- return new byte[0];
- }
-
- /**
- * Returns whether or not this message needs to include a message ID.
- */
- public boolean isMessageIdRequired() {
- return false;
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal.wire;
+
+import java.io.IOException;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+
+/**
+ * An on-the-wire representation of an MQTT DISCONNECT message.
+ */
+public class MqttDisconnect extends MqttWireMessage {
+ public static final String KEY="Disc";
+
+ public MqttDisconnect() {
+ super(MqttWireMessage.MESSAGE_TYPE_DISCONNECT);
+ }
+
+ public MqttDisconnect(byte info, byte[] variableHeader) throws IOException {
+ super(MqttWireMessage.MESSAGE_TYPE_DISCONNECT);
+ }
+
+ protected byte getMessageInfo() {
+ return (byte) 0;
+ }
+
+ protected byte[] getVariableHeader() throws MqttException {
+ return new byte[0];
+ }
+
+ /**
+ * Returns whether or not this message needs to include a message ID.
+ */
+ public boolean isMessageIdRequired() {
+ return false;
+ }
+
+ public String getKey() {
+ return KEY;
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttInputStream.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttInputStream.java
similarity index 55%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttInputStream.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttInputStream.java
index c31f6c58..5261ea63 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttInputStream.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttInputStream.java
@@ -1,69 +1,104 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal.wire;
-
-import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.eclipse.paho.client.mqttv3.MqttException;
-import org.eclipse.paho.client.mqttv3.internal.ExceptionHelper;
-
-
-/**
- * An MqttInputStream lets applications read instances of
- * MqttWireMessage.
- */
-public class MqttInputStream extends InputStream {
- private DataInputStream in;
-
- public MqttInputStream(InputStream in) {
- this.in = new DataInputStream(in);
- }
-
- public int read() throws IOException {
- return in.read();
- }
-
- public int available() throws IOException {
- return in.available();
- }
-
- public void close() throws IOException {
- in.close();
- }
-
- /**
- * Reads an MqttWireMessage from the stream.
- */
- public MqttWireMessage readMqttWireMessage() throws IOException, MqttException {
- ByteArrayOutputStream bais = new ByteArrayOutputStream();
- byte first = in.readByte();
- byte type = (byte) ((first >>> 4) & 0x0F);
- if ((type < MqttWireMessage.MESSAGE_TYPE_CONNECT) ||
- (type > MqttWireMessage.MESSAGE_TYPE_DISCONNECT)) {
- // Invalid MQTT message type...
- throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_INVALID_MESSAGE);
- }
- long remLen = MqttWireMessage.readMBI(in).getValue();
- bais.write(first);
- // bit silly, we decode it then encode it
- bais.write(MqttWireMessage.encodeMBI(remLen));
- byte[] packet = new byte[(int)(bais.size()+remLen)];
- in.readFully(packet,bais.size(),packet.length - bais.size());
- byte[] header = bais.toByteArray();
- System.arraycopy(header,0,packet,0, header.length);
- MqttWireMessage message = MqttWireMessage.createWireMessage(packet);
- return message;
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal.wire;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.internal.ClientState;
+import org.eclipse.paho.client.mqttv3.internal.ExceptionHelper;
+import org.eclipse.paho.client.mqttv3.logging.Logger;
+import org.eclipse.paho.client.mqttv3.logging.LoggerFactory;
+
+
+/**
+ * An MqttInputStream lets applications read instances of
+ * MqttWireMessage.
+ */
+public class MqttInputStream extends InputStream {
+ private static final String CLASS_NAME = MqttInputStream.class.getName();
+ private static final Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME);
+
+ private ClientState clientState = null;
+ private DataInputStream in;
+
+ public MqttInputStream(ClientState clientState, InputStream in) {
+ this.clientState = clientState;
+ this.in = new DataInputStream(in);
+ }
+
+ public int read() throws IOException {
+ return in.read();
+ }
+
+ public int available() throws IOException {
+ return in.available();
+ }
+
+ public void close() throws IOException {
+ in.close();
+ }
+
+ /**
+ * Reads an MqttWireMessage from the stream.
+ */
+ public MqttWireMessage readMqttWireMessage() throws IOException, MqttException {
+ final String methodName ="readMqttWireMessage";
+ ByteArrayOutputStream bais = new ByteArrayOutputStream();
+ byte first = in.readByte();
+ clientState.notifyReceivedBytes(1);
+
+ byte type = (byte) ((first >>> 4) & 0x0F);
+ if ((type < MqttWireMessage.MESSAGE_TYPE_CONNECT) ||
+ (type > MqttWireMessage.MESSAGE_TYPE_DISCONNECT)) {
+ // Invalid MQTT message type...
+ throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_INVALID_MESSAGE);
+ }
+ long remLen = MqttWireMessage.readMBI(in).getValue();
+ bais.write(first);
+ // bit silly, we decode it then encode it
+ bais.write(MqttWireMessage.encodeMBI(remLen));
+ byte[] packet = new byte[(int)(bais.size()+remLen)];
+ readFully(packet,bais.size(),packet.length - bais.size());
+
+ byte[] header = bais.toByteArray();
+ System.arraycopy(header,0,packet,0, header.length);
+ MqttWireMessage message = MqttWireMessage.createWireMessage(packet);
+ // @TRACE 501= received {0}
+ log.fine(CLASS_NAME, methodName, "501",new Object[] {message});
+ return message;
+ }
+
+
+ private void readFully(byte b[], int off, int len) throws IOException {
+ if (len < 0)
+ throw new IndexOutOfBoundsException();
+ int n = 0;
+ while (n < len) {
+ int count = in.read(b, off + n, len - n);
+ clientState.notifyReceivedBytes(count);
+
+ if (count < 0)
+ throw new EOFException();
+ n += count;
+ }
+ }
+}
+
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttOutputStream.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttOutputStream.java
new file mode 100644
index 00000000..6717406f
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttOutputStream.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal.wire;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.internal.ClientState;
+import org.eclipse.paho.client.mqttv3.logging.Logger;
+import org.eclipse.paho.client.mqttv3.logging.LoggerFactory;
+
+
+/**
+ * An MqttOutputStream lets applications write instances of
+ * MqttWireMessage.
+ */
+public class MqttOutputStream extends OutputStream {
+ private static final String CLASS_NAME = MqttOutputStream.class.getName();
+ private static final Logger log = LoggerFactory.getLogger(LoggerFactory.MQTT_CLIENT_MSG_CAT, CLASS_NAME);
+
+ private ClientState clientState = null;
+ private BufferedOutputStream out;
+
+ public MqttOutputStream(ClientState clientState, OutputStream out) {
+ this.clientState = clientState;
+ this.out = new BufferedOutputStream(out);
+ }
+
+ public void close() throws IOException {
+ out.close();
+ }
+
+ public void flush() throws IOException {
+ out.flush();
+ }
+
+ public void write(byte[] b) throws IOException {
+ out.write(b);
+ clientState.notifySentBytes(b.length);
+ }
+
+ public void write(byte[] b, int off, int len) throws IOException {
+ out.write(b, off, len);
+ clientState.notifySentBytes(len);
+ }
+
+ public void write(int b) throws IOException {
+ out.write(b);
+ }
+
+ /**
+ * Writes an MqttWireMessage to the stream.
+ */
+ public void write(MqttWireMessage message) throws IOException, MqttException {
+ final String methodName = "write";
+ byte[] bytes = message.getHeader();
+ byte[] pl = message.getPayload();
+// out.write(message.getHeader());
+// out.write(message.getPayload());
+ out.write(bytes,0,bytes.length);
+ clientState.notifySentBytes(bytes.length);
+
+ int offset = 0;
+ int chunckSize = 1024;
+ while (offset < pl.length) {
+ int length = Math.min(chunckSize, pl.length - offset);
+ out.write(pl, offset, length);
+ offset += chunckSize;
+ clientState.notifySentBytes(length);
+ }
+
+ // @TRACE 500= sent {0}
+ log.fine(CLASS_NAME, methodName, "500", new Object[]{message});
+ }
+}
+
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPersistableWireMessage.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPersistableWireMessage.java
similarity index 72%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPersistableWireMessage.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPersistableWireMessage.java
index fa2d042d..c826d87d 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPersistableWireMessage.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPersistableWireMessage.java
@@ -1,63 +1,67 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal.wire;
-
-import org.eclipse.paho.client.mqttv3.MqttException;
-import org.eclipse.paho.client.mqttv3.MqttPersistable;
-import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
-
-public abstract class MqttPersistableWireMessage extends MqttWireMessage
- implements MqttPersistable {
-
- public MqttPersistableWireMessage(byte type) {
- super(type);
- }
-
- public byte[] getHeaderBytes() throws MqttPersistenceException {
- try {
- return getHeader();
- }
- catch (MqttException ex) {
- throw new MqttPersistenceException(ex.getCause());
- }
- }
-
- public int getHeaderLength() throws MqttPersistenceException {
- return getHeaderBytes().length;
- }
-
- public int getHeaderOffset() throws MqttPersistenceException{
- return 0;
- }
-
- public String getKey() throws MqttPersistenceException {
- return new Integer(getMessageId()).toString();
- }
-
- public byte[] getPayloadBytes() throws MqttPersistenceException {
- try {
- return getPayload();
- }
- catch (MqttException ex) {
- throw new MqttPersistenceException(ex.getCause());
- }
- }
-
- public int getPayloadLength() throws MqttPersistenceException {
- return 0;
- }
-
- public int getPayloadOffset() throws MqttPersistenceException {
- return 0;
- }
-
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal.wire;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttPersistable;
+import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
+
+public abstract class MqttPersistableWireMessage extends MqttWireMessage
+ implements MqttPersistable {
+
+ public MqttPersistableWireMessage(byte type) {
+ super(type);
+ }
+
+ public byte[] getHeaderBytes() throws MqttPersistenceException {
+ try {
+ return getHeader();
+ }
+ catch (MqttException ex) {
+ throw new MqttPersistenceException(ex.getCause());
+ }
+ }
+
+ public int getHeaderLength() throws MqttPersistenceException {
+ return getHeaderBytes().length;
+ }
+
+ public int getHeaderOffset() throws MqttPersistenceException{
+ return 0;
+ }
+
+// public String getKey() throws MqttPersistenceException {
+// return new Integer(getMessageId()).toString();
+// }
+
+ public byte[] getPayloadBytes() throws MqttPersistenceException {
+ try {
+ return getPayload();
+ }
+ catch (MqttException ex) {
+ throw new MqttPersistenceException(ex.getCause());
+ }
+ }
+
+ public int getPayloadLength() throws MqttPersistenceException {
+ return 0;
+ }
+
+ public int getPayloadOffset() throws MqttPersistenceException {
+ return 0;
+ }
+
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPingReq.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPingReq.java
similarity index 56%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPingReq.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPingReq.java
index bee1c62e..e2472bed 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPingReq.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPingReq.java
@@ -1,39 +1,56 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal.wire;
-
-import org.eclipse.paho.client.mqttv3.MqttException;
-
-/**
- * An on-the-wire representation of an MQTT PINGREQ message.
- */
-public class MqttPingReq extends MqttWireMessage {
- public MqttPingReq() {
- super(MqttWireMessage.MESSAGE_TYPE_PINGREQ);
- }
-
- /**
- * Returns false as message IDs are not required for MQTT
- * PINGREQ messages.
- */
- public boolean isMessageIdRequired() {
- return false;
- }
-
- protected byte[] getVariableHeader() throws MqttException {
- return new byte[0];
- }
-
- protected byte getMessageInfo() {
- return 0;
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal.wire;
+
+import java.io.IOException;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+
+/**
+ * An on-the-wire representation of an MQTT PINGREQ message.
+ */
+public class MqttPingReq extends MqttWireMessage {
+ public static final String KEY = "Ping";
+
+ public MqttPingReq() {
+ super(MqttWireMessage.MESSAGE_TYPE_PINGREQ);
+ }
+
+ public MqttPingReq(byte info, byte[] variableHeader) throws IOException {
+ super(MqttWireMessage.MESSAGE_TYPE_PINGREQ);
+ }
+
+ /**
+ * Returns false as message IDs are not required for MQTT
+ * PINGREQ messages.
+ */
+ public boolean isMessageIdRequired() {
+ return false;
+ }
+
+ protected byte[] getVariableHeader() throws MqttException {
+ return new byte[0];
+ }
+
+ protected byte getMessageInfo() {
+ return 0;
+ }
+
+ public String getKey() {
+ return KEY;
+ }
+}
+
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPingResp.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPingResp.java
similarity index 59%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPingResp.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPingResp.java
index 8146143e..4f07fc90 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPingResp.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPingResp.java
@@ -1,38 +1,46 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal.wire;
-
-import java.io.IOException;
-
-import org.eclipse.paho.client.mqttv3.MqttException;
-
-
-/**
- * An on-the-wire representation of an MQTT PINGRESP.
- */
-public class MqttPingResp extends MqttAck {
- public MqttPingResp(byte info, byte[] variableHeader) throws IOException {
- super(MqttWireMessage.MESSAGE_TYPE_PINGRESP);
- }
-
- protected byte[] getVariableHeader() throws MqttException {
- // Not needed, as the client never encodes a PINGRESP
- return new byte[0];
- }
-
- /**
- * Returns whether or not this message needs to include a message ID.
- */
- public boolean isMessageIdRequired() {
- return false;
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal.wire;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+
+
+/**
+ * An on-the-wire representation of an MQTT PINGRESP.
+ */
+public class MqttPingResp extends MqttAck {
+ public static final String KEY = "Ping";
+
+ public MqttPingResp(byte info, byte[] variableHeader) {
+ super(MqttWireMessage.MESSAGE_TYPE_PINGRESP);
+ }
+
+ protected byte[] getVariableHeader() throws MqttException {
+ // Not needed, as the client never encodes a PINGRESP
+ return new byte[0];
+ }
+
+ /**
+ * Returns whether or not this message needs to include a message ID.
+ */
+ public boolean isMessageIdRequired() {
+ return false;
+ }
+
+ public String getKey() {
+ return KEY;
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubAck.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubAck.java
similarity index 72%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubAck.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubAck.java
index d06cf8c4..357b09fa 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubAck.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubAck.java
@@ -1,42 +1,46 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal.wire;
-
-import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
-import java.io.IOException;
-
-import org.eclipse.paho.client.mqttv3.MqttException;
-
-
-
-/**
- * An on-the-wire representation of an MQTT PUBACK message.
- */
-public class MqttPubAck extends MqttAck {
- public MqttPubAck(byte info, byte[] data) throws IOException {
- super(MqttWireMessage.MESSAGE_TYPE_PUBACK);
- ByteArrayInputStream bais = new ByteArrayInputStream(data);
- DataInputStream dis = new DataInputStream(bais);
- msgId = dis.readUnsignedShort();
- dis.close();
- }
-
- public MqttPubAck(MqttPublish publish) {
- super(MqttWireMessage.MESSAGE_TYPE_PUBACK);
- msgId = publish.getMessageId();
- }
-
- protected byte[] getVariableHeader() throws MqttException {
- return encodeMessageId();
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal.wire;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+
+
+
+/**
+ * An on-the-wire representation of an MQTT PUBACK message.
+ */
+public class MqttPubAck extends MqttAck {
+ public MqttPubAck(byte info, byte[] data) throws IOException {
+ super(MqttWireMessage.MESSAGE_TYPE_PUBACK);
+ ByteArrayInputStream bais = new ByteArrayInputStream(data);
+ DataInputStream dis = new DataInputStream(bais);
+ msgId = dis.readUnsignedShort();
+ dis.close();
+ }
+
+ public MqttPubAck(MqttPublish publish) {
+ super(MqttWireMessage.MESSAGE_TYPE_PUBACK);
+ msgId = publish.getMessageId();
+ }
+
+ protected byte[] getVariableHeader() throws MqttException {
+ return encodeMessageId();
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubComp.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubComp.java
similarity index 74%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubComp.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubComp.java
index 5f8369b3..59c90b0b 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubComp.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubComp.java
@@ -1,47 +1,51 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal.wire;
-
-import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
-import java.io.IOException;
-
-import org.eclipse.paho.client.mqttv3.MqttException;
-
-
-
-/**
- * An on-the-wire representation of an MQTT PUBCOMP message.
- */
-public class MqttPubComp extends MqttAck {
- public MqttPubComp(byte info, byte[] data) throws IOException {
- super(MqttWireMessage.MESSAGE_TYPE_PUBCOMP);
- ByteArrayInputStream bais = new ByteArrayInputStream(data);
- DataInputStream dis = new DataInputStream(bais);
- msgId = dis.readUnsignedShort();
- dis.close();
- }
-
- public MqttPubComp(MqttPublish publish) {
- super(MqttWireMessage.MESSAGE_TYPE_PUBCOMP);
- this.msgId = publish.getMessageId();
- }
-
- public MqttPubComp(int msgId) {
- super(MqttWireMessage.MESSAGE_TYPE_PUBCOMP);
- this.msgId = msgId;
- }
-
- protected byte[] getVariableHeader() throws MqttException {
- return encodeMessageId();
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal.wire;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+
+
+
+/**
+ * An on-the-wire representation of an MQTT PUBCOMP message.
+ */
+public class MqttPubComp extends MqttAck {
+ public MqttPubComp(byte info, byte[] data) throws IOException {
+ super(MqttWireMessage.MESSAGE_TYPE_PUBCOMP);
+ ByteArrayInputStream bais = new ByteArrayInputStream(data);
+ DataInputStream dis = new DataInputStream(bais);
+ msgId = dis.readUnsignedShort();
+ dis.close();
+ }
+
+ public MqttPubComp(MqttPublish publish) {
+ super(MqttWireMessage.MESSAGE_TYPE_PUBCOMP);
+ this.msgId = publish.getMessageId();
+ }
+
+ public MqttPubComp(int msgId) {
+ super(MqttWireMessage.MESSAGE_TYPE_PUBCOMP);
+ this.msgId = msgId;
+ }
+
+ protected byte[] getVariableHeader() throws MqttException {
+ return encodeMessageId();
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubRec.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubRec.java
similarity index 72%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubRec.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubRec.java
index 399057f8..f2dac684 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubRec.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubRec.java
@@ -1,42 +1,46 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal.wire;
-
-import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
-import java.io.IOException;
-
-import org.eclipse.paho.client.mqttv3.MqttException;
-
-
-
-/**
- * An on-the-wire representation of an MQTT PUBREC message.
- */
-public class MqttPubRec extends MqttAck {
- public MqttPubRec(byte info, byte[] data) throws IOException {
- super(MqttWireMessage.MESSAGE_TYPE_PUBREC);
- ByteArrayInputStream bais = new ByteArrayInputStream(data);
- DataInputStream dis = new DataInputStream(bais);
- msgId = dis.readUnsignedShort();
- dis.close();
- }
-
- public MqttPubRec(MqttPublish publish) {
- super(MqttWireMessage.MESSAGE_TYPE_PUBREC);
- msgId = publish.getMessageId();
- }
-
- protected byte[] getVariableHeader() throws MqttException {
- return encodeMessageId();
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal.wire;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+
+
+
+/**
+ * An on-the-wire representation of an MQTT PUBREC message.
+ */
+public class MqttPubRec extends MqttAck {
+ public MqttPubRec(byte info, byte[] data) throws IOException {
+ super(MqttWireMessage.MESSAGE_TYPE_PUBREC);
+ ByteArrayInputStream bais = new ByteArrayInputStream(data);
+ DataInputStream dis = new DataInputStream(bais);
+ msgId = dis.readUnsignedShort();
+ dis.close();
+ }
+
+ public MqttPubRec(MqttPublish publish) {
+ super(MqttWireMessage.MESSAGE_TYPE_PUBREC);
+ msgId = publish.getMessageId();
+ }
+
+ protected byte[] getVariableHeader() throws MqttException {
+ return encodeMessageId();
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubRel.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubRel.java
similarity index 62%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubRel.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubRel.java
index 569c7008..5d33ddb2 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubRel.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPubRel.java
@@ -1,49 +1,64 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal.wire;
-
-import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
-import java.io.IOException;
-
-import org.eclipse.paho.client.mqttv3.MqttException;
-
-
-
-/**
- * An on-the-wire representation of an MQTT PUBREL message.
- */
-public class MqttPubRel extends MqttPersistableWireMessage {
-
- public MqttPubRel(MqttPubRec pubRec) {
- super(MqttWireMessage.MESSAGE_TYPE_PUBREL);
- this.setMessageId(pubRec.getMessageId());
- }
-
- public MqttPubRel(byte info, byte[] data) throws IOException {
- super(MqttWireMessage.MESSAGE_TYPE_PUBREL);
- ByteArrayInputStream bais = new ByteArrayInputStream(data);
- DataInputStream dis = new DataInputStream(bais);
- msgId = dis.readUnsignedShort();
- dis.close();
- }
-
-
- protected byte[] getVariableHeader() throws MqttException {
- return encodeMessageId();
- }
-
- protected byte getMessageInfo() {
- return (byte)( 2 | (this.duplicate?8:0));
- }
-
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal.wire;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+
+/**
+ * An on-the-wire representation of an MQTT PUBREL message.
+ */
+public class MqttPubRel extends MqttPersistableWireMessage {
+
+ /**
+ * Createa a pubrel message based on a pubrec
+ * @param pubRec
+ */
+ public MqttPubRel(MqttPubRec pubRec) {
+ super(MqttWireMessage.MESSAGE_TYPE_PUBREL);
+ this.setMessageId(pubRec.getMessageId());
+ }
+
+ /**
+ * Creates a pubrel based on a pubrel set of bytes read fro the network
+ * @param info
+ * @param data
+ * @throws IOException
+ */
+ public MqttPubRel(byte info, byte[] data) throws IOException {
+ super(MqttWireMessage.MESSAGE_TYPE_PUBREL);
+ ByteArrayInputStream bais = new ByteArrayInputStream(data);
+ DataInputStream dis = new DataInputStream(bais);
+ msgId = dis.readUnsignedShort();
+ dis.close();
+ }
+
+ protected byte[] getVariableHeader() throws MqttException {
+ return encodeMessageId();
+ }
+
+ protected byte getMessageInfo() {
+ return (byte)( 2 | (this.duplicate?8:0));
+ }
+
+ public String toString() {
+ return super.toString() + " msgId " + msgId;
+ }
+
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPublish.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPublish.java
similarity index 62%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPublish.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPublish.java
index 80c58f64..c98a7755 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttPublish.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttPublish.java
@@ -1,163 +1,179 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal.wire;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-
-import org.eclipse.paho.client.mqttv3.MqttException;
-import org.eclipse.paho.client.mqttv3.MqttMessage;
-
-
-/**
- * An on-the-wire representation of an MQTT SEND message.
- */
-public class MqttPublish extends MqttPersistableWireMessage {
- public static final byte DESTINATION_TYPE_TOPIC = 0;
-
- private MqttMessage message;
- private String topicName;
-
- private byte[] encodedPayload = null;
-
- public MqttPublish(String name, MqttMessage message) {
- super(MqttWireMessage.MESSAGE_TYPE_PUBLISH);
- this.topicName = name;
- this.message = message;
- }
-
- /**
- * Constructs a new MqttPublish object.
- * @param info the message info byte
- * @param data the variable header and payload bytes
- */
- public MqttPublish(byte info, byte[] data) throws MqttException, IOException {
- super(MqttWireMessage.MESSAGE_TYPE_PUBLISH);
- this.message = new MqttReceivedMessage();
- message.setQos((info >> 1) & 0x03);
- if ((info & 0x01) == 0x01) {
- message.setRetained(true);
- }
- if ((info & 0x08) == 0x08) {
- ((MqttReceivedMessage) message).setDuplicate(true);
- }
-
- ByteArrayInputStream bais = new ByteArrayInputStream(data);
- CountingInputStream counter = new CountingInputStream(bais);
- DataInputStream dis = new DataInputStream(counter);
- topicName = dis.readUTF();
- if (message.getQos() > 0) {
- msgId = dis.readUnsignedShort();
- }
- dis.close();
- byte[] payload = new byte[data.length-counter.getCounter()];
- dis.readFully(payload);
- message.setPayload(payload);
- }
-
- protected byte getMessageInfo() {
- byte info = (byte) (message.getQos() << 1);
- if (message.isRetained()) {
- info |= 0x01;
- }
- if (message.isDuplicate()) {
- info |= 0x08;
- }
-
- return info;
- }
-
- public String getTopicName() {
- return topicName;
- }
-
- public MqttMessage getMessage() {
- return message;
- }
-
- protected static byte[] encodePayload(MqttMessage message) throws MqttException {
-// byte payloadType = message.getPayloadType();
-// if (payloadType == MqttMessage.PAYLOAD_EMPTY) {
-// return new byte[0];
-// }
-// else if (payloadType == MqttMessage.PAYLOAD_BYTES) {
- return message.getPayload();
-// }
-// else if (payloadType == MqttMessage.PAYLOAD_TEXT) {
-// try {
-// return message.getStringPayload().getBytes(STRING_ENCODING);
-// } catch(UnsupportedEncodingException uee) {
-// throw new MqttException(uee);
-// }
-// }
-// else if (payloadType == MqttMessage.PAYLOAD_MAP) {
-// try {
-// return encodeUserProperties(message.getMapPayload());
-// }
-// catch (IOException ex) {
-// throw new MqttException(ex);
-// }
-// }
-// else {
-// // TODO: This is actually an error. Throw an exception?
-// return new byte[0];
-// }
- }
-
- public byte[] getPayload() throws MqttException {
- if (encodedPayload == null) {
- // TODO: inefficient, as this puts two copies in memory
- encodedPayload = encodePayload(message);
- }
- return encodedPayload;
- }
-
- public int getPayloadLength() {
- int length = 0;
- try {
- length = getPayload().length;
- } catch(MqttException me) {
- }
- return length;
- }
-
- public void setMessageId(int msgId) {
- super.setMessageId(msgId);
- if (message instanceof MqttReceivedMessage) {
- ((MqttReceivedMessage)message).setMessageId(msgId);
- }
- }
-
- protected byte[] getVariableHeader() throws MqttException {
- try {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(baos);
- dos.writeUTF(topicName);
- if (message.getQos() > 0) {
- dos.writeShort(msgId);
- }
- dos.flush();
- return baos.toByteArray();
- }
- catch (IOException ex) {
- throw new MqttException(ex);
- }
- }
-
- public boolean isMessageIdRequired() {
- // all publishes require a message ID as it's used as the key to the token store
- return true;
- }
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal.wire;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+/**
+ * An on-the-wire representation of an MQTT SEND message.
+ */
+public class MqttPublish extends MqttPersistableWireMessage {
+
+ private MqttMessage message;
+ private String topicName;
+
+ private byte[] encodedPayload = null;
+
+ public MqttPublish(String name, MqttMessage message) {
+ super(MqttWireMessage.MESSAGE_TYPE_PUBLISH);
+ topicName = name;
+ this.message = message;
+ }
+
+ /**
+ * Constructs a new MqttPublish object.
+ * @param info the message info byte
+ * @param data the variable header and payload bytes
+ */
+ public MqttPublish(byte info, byte[] data) throws MqttException, IOException {
+ super(MqttWireMessage.MESSAGE_TYPE_PUBLISH);
+ message = new MqttReceivedMessage();
+ message.setQos((info >> 1) & 0x03);
+ if ((info & 0x01) == 0x01) {
+ message.setRetained(true);
+ }
+ if ((info & 0x08) == 0x08) {
+ ((MqttReceivedMessage) message).setDuplicate(true);
+ }
+
+ ByteArrayInputStream bais = new ByteArrayInputStream(data);
+ CountingInputStream counter = new CountingInputStream(bais);
+ DataInputStream dis = new DataInputStream(counter);
+ topicName = decodeUTF8(dis);
+ if (message.getQos() > 0) {
+ msgId = dis.readUnsignedShort();
+ }
+ byte[] payload = new byte[data.length-counter.getCounter()];
+ dis.readFully(payload);
+ dis.close();
+ message.setPayload(payload);
+ }
+
+ public String toString() {
+
+ // Convert the first few bytes of the payload into a hex string
+ StringBuffer hex = new StringBuffer();
+ byte[] payload = message.getPayload();
+ int limit = Math.min(payload.length, 20);
+ for (int i = 0; i < limit; i++) {
+ byte b = payload[i];
+ String ch = Integer.toHexString(b);
+ if (ch.length() == 1) {
+ ch = "0" + ch;
+ }
+ hex.append(ch);
+ }
+
+ // It will not always be possible to convert the binary payload into
+ // characters, but never-the-less we attempt to do this as it is often
+ // useful
+ String string = null;
+ try {
+ string = new String(payload, 0, limit, "UTF-8");
+ } catch (Exception e) {
+ string = "?";
+ }
+
+ StringBuffer sb = new StringBuffer();
+ sb.append(super.toString());
+ sb.append(" qos:").append(message.getQos());
+ if (message.getQos() > 0) {
+ sb.append(" msgId:").append(msgId);
+ }
+ sb.append(" retained:").append(message.isRetained());
+ sb.append(" dup:").append(duplicate);
+ sb.append(" topic:\"").append(topicName).append("\"");
+ sb.append(" payload:[hex:").append(hex);
+ sb.append(" utf8:\"").append(string).append("\"");
+ sb.append(" length:").append(payload.length).append("]");
+
+ return sb.toString();
+ }
+
+ protected byte getMessageInfo() {
+ byte info = (byte) (message.getQos() << 1);
+ if (message.isRetained()) {
+ info |= 0x01;
+ }
+ if (message.isDuplicate() || duplicate ) {
+ info |= 0x08;
+ }
+
+ return info;
+ }
+
+ public String getTopicName() {
+ return topicName;
+ }
+
+ public MqttMessage getMessage() {
+ return message;
+ }
+
+ protected static byte[] encodePayload(MqttMessage message) {
+ return message.getPayload();
+ }
+
+ public byte[] getPayload() throws MqttException {
+ if (encodedPayload == null) {
+ encodedPayload = encodePayload(message);
+ }
+ return encodedPayload;
+ }
+
+ public int getPayloadLength() {
+ int length = 0;
+ try {
+ length = getPayload().length;
+ } catch(MqttException me) {
+ }
+ return length;
+ }
+
+ public void setMessageId(int msgId) {
+ super.setMessageId(msgId);
+ if (message instanceof MqttReceivedMessage) {
+ ((MqttReceivedMessage)message).setMessageId(msgId);
+ }
+ }
+
+ protected byte[] getVariableHeader() throws MqttException {
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(baos);
+ encodeUTF8(dos, topicName);
+ if (message.getQos() > 0) {
+ dos.writeShort(msgId);
+ }
+ dos.flush();
+ return baos.toByteArray();
+ } catch (IOException ex) {
+ throw new MqttException(ex);
+ }
+ }
+
+ public boolean isMessageIdRequired() {
+ // all publishes require a message ID as it's used as the key to the token store
+ return true;
+ }
}
\ No newline at end of file
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttReceivedMessage.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttReceivedMessage.java
similarity index 64%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttReceivedMessage.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttReceivedMessage.java
index 778ebbfe..cdf3e28f 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttReceivedMessage.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttReceivedMessage.java
@@ -1,33 +1,37 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal.wire;
-
-import org.eclipse.paho.client.mqttv3.MqttMessage;
-
-public class MqttReceivedMessage extends MqttMessage {
-
- private int messageId;
-
- public void setMessageId(int msgId) {
- this.messageId = msgId;
- }
-
- public int getMessageId() {
- return messageId;
- }
-
- // This method exists here to get around the protected visibility of the
- // super class method.
- public void setDuplicate(boolean value) {
- super.setDuplicate(value);
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal.wire;
+
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+public class MqttReceivedMessage extends MqttMessage {
+
+ private int messageId;
+
+ public void setMessageId(int msgId) {
+ this.messageId = msgId;
+ }
+
+ public int getMessageId() {
+ return messageId;
+ }
+
+ // This method exists here to get around the protected visibility of the
+ // super class method.
+ public void setDuplicate(boolean value) {
+ super.setDuplicate(value);
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttSuback.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttSuback.java
similarity index 59%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttSuback.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttSuback.java
index 401b67fd..cd74a43a 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MqttSuback.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttSuback.java
@@ -1,47 +1,66 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal.wire;
-
-import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
-import java.io.IOException;
-
-import org.eclipse.paho.client.mqttv3.MqttException;
-
-
-/**
- * An on-the-wire representation of an MQTT SUBACK.
- */
-public class MqttSuback extends MqttAck {
- private int[] grantedQos;
-
- public MqttSuback(byte info, byte[] data) throws IOException {
- super(MqttWireMessage.MESSAGE_TYPE_SUBACK);
- ByteArrayInputStream bais = new ByteArrayInputStream(data);
- DataInputStream dis = new DataInputStream(bais);
- msgId = dis.readUnsignedShort();
- int index = 0;
- grantedQos = new int[data.length-2];
- int qos = dis.read();
- while (qos != -1) {
- grantedQos[index] = qos;
- index++;
- qos = dis.read();
- }
- dis.close();
- }
-
- protected byte[] getVariableHeader() throws MqttException {
- // Not needed, as the client never encodes a SUBACK
- return new byte[0];
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ * Ian Craggs - MQTT 3.1.1 support
+ */
+package org.eclipse.paho.client.mqttv3.internal.wire;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+
+
+/**
+ * An on-the-wire representation of an MQTT SUBACK.
+ */
+public class MqttSuback extends MqttAck {
+ private int[] grantedQos;
+
+ public MqttSuback(byte info, byte[] data) throws IOException {
+ super(MqttWireMessage.MESSAGE_TYPE_SUBACK);
+ ByteArrayInputStream bais = new ByteArrayInputStream(data);
+ DataInputStream dis = new DataInputStream(bais);
+ msgId = dis.readUnsignedShort();
+ int index = 0;
+ grantedQos = new int[data.length-2];
+ int qos = dis.read();
+ while (qos != -1) {
+ grantedQos[index] = qos;
+ index++;
+ qos = dis.read();
+ }
+ dis.close();
+ }
+
+ protected byte[] getVariableHeader() throws MqttException {
+ // Not needed, as the client never encodes a SUBACK
+ return new byte[0];
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(super.toString()).append(" granted Qos");
+ for (int i = 0; i < grantedQos.length; ++i) {
+ sb.append(" ").append(grantedQos[i]);
+ }
+ return sb.toString();
+ }
+
+ public int[] getGrantedQos() {
+ return grantedQos;
+ }
+
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttSubscribe.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttSubscribe.java
new file mode 100644
index 00000000..e18e0dba
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MqttSubscribe.java
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal.wire;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+
+
+/**
+ * An on-the-wire representation of an MQTT SUBSCRIBE message.
+ */
+public class MqttSubscribe extends MqttWireMessage {
+ private String[] names;
+ private int[] qos;
+ private int count;
+
+ /**
+ * Constructor for an on the wire MQTT subscribe message
+ *
+ * @param info
+ * @param data
+ */
+ public MqttSubscribe(byte info, byte[] data) throws IOException {
+ super(MqttWireMessage.MESSAGE_TYPE_SUBSCRIBE);
+ ByteArrayInputStream bais = new ByteArrayInputStream(data);
+ DataInputStream dis = new DataInputStream(bais);
+ msgId = dis.readUnsignedShort();
+
+ count = 0;
+ names = new String[10];
+ qos = new int[10];
+ boolean end = false;
+ while (!end) {
+ try {
+ names[count] = decodeUTF8(dis);
+ qos[count++] = dis.readByte();
+ } catch (Exception e) {
+ end = true;
+ }
+ }
+ dis.close();
+ }
+
+ /**
+ * Constructor for an on the wire MQTT subscribe message
+ * @param names - one or more topics to subscribe to
+ * @param qos - the max QoS that each each topic will be subscribed at
+ */
+ public MqttSubscribe(String[] names, int[] qos) {
+ super(MqttWireMessage.MESSAGE_TYPE_SUBSCRIBE);
+ this.names = names;
+ this.qos = qos;
+
+ if (names.length != qos.length) {
+ throw new IllegalArgumentException();
+ }
+
+ for (int i=0;i 0) {
+ sb.append(", ");
+ }
+ sb.append("\"").append(names[i]).append("\"");
+ }
+ sb.append("] qos:[");
+ for (int i = 0; i < count; i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ sb.append(qos[i]);
+ }
+ sb.append("]");
+
+ return sb.toString();
+ }
+
+ protected byte getMessageInfo() {
+ return (byte) (2 | (duplicate ? 8 : 0));
+ }
+
+ protected byte[] getVariableHeader() throws MqttException {
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(baos);
+ dos.writeShort(msgId);
+ dos.flush();
+ return baos.toByteArray();
+ } catch (IOException ex) {
+ throw new MqttException(ex);
+ }
+ }
+
+ public byte[] getPayload() throws MqttException {
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(baos);
+ for (int i=0; i 0) {
+ sb.append(", ");
+ }
+ sb.append("\"" + names[i] + "\"");
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ protected byte getMessageInfo() {
+ return (byte) (2 | (duplicate ? 8 : 0));
+ }
+
+ protected byte[] getVariableHeader() throws MqttException {
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(baos);
+ dos.writeShort(msgId);
+ dos.flush();
+ return baos.toByteArray();
+ } catch (IOException ex) {
+ throw new MqttException(ex);
+ }
+ }
+
+ public byte[] getPayload() throws MqttException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(baos);
+ for (int i=0; i> 4);
- byte info = (byte) (first &= 0x0f);
- long remLen = readMBI(in).getValue();
- long totalToRead = counter.getCounter() + remLen;
-
- MqttWireMessage result;
- long remainder = totalToRead - counter.getCounter();
- byte[] data = new byte[0];
- // The remaining bytes must be the payload...
- if (remainder > 0) {
- data = new byte[(int) remainder];
- in.readFully(data, 0, data.length);
- }
-
- if (type == MqttWireMessage.MESSAGE_TYPE_PUBLISH) {
- result = new MqttPublish(info, data);
- }
- else if (type == MqttWireMessage.MESSAGE_TYPE_PUBACK) {
- result = new MqttPubAck(info, data);
- }
- else if (type == MqttWireMessage.MESSAGE_TYPE_PUBCOMP) {
- result = new MqttPubComp(info, data);
- }
- else if (type == MqttWireMessage.MESSAGE_TYPE_CONNACK) {
- result = new MqttConnack(info, data);
- }
- else if (type == MqttWireMessage.MESSAGE_TYPE_PINGRESP) {
- result = new MqttPingResp(info, data);
- }
- else if (type == MqttWireMessage.MESSAGE_TYPE_SUBACK) {
- result = new MqttSuback(info, data);
- }
- else if (type == MqttWireMessage.MESSAGE_TYPE_UNSUBACK) {
- result = new MqttUnsubAck(info, data);
- }
- else if (type == MqttWireMessage.MESSAGE_TYPE_PUBREL) {
- result = new MqttPubRel(info, data);
- }
- else if (type == MqttWireMessage.MESSAGE_TYPE_PUBREC) {
- result = new MqttPubRec(info, data);
- }
- else {
- throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_UNEXPECTED_ERROR);
- }
- return result;
- } catch(IOException io) {
- throw new MqttException(io);
- }
- }
-
- protected static byte[] encodeMBI( long number) {
- int numBytes = 0;
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- // Encode the remaining length fields in the four bytes
- do {
- byte digit = (byte)(number % 128);
- number = number / 128;
- if (number > 0) {
- digit |= 0x80;
- }
- bos.write(digit);
- numBytes++;
- } while ( (number > 0) && (numBytes<4) );
-
- return bos.toByteArray();
- }
-
- /**
- * Decodes an MQTT Multi-Byte Integer from the given stream.
- */
- protected static MultiByteInteger readMBI(DataInputStream in) throws IOException {
- byte digit;
- long msgLength = 0;
- int multiplier = 1;
- int count = 0;
-
- do {
- digit = in.readByte();
- count++;
- msgLength += ((digit & 0x7F) * multiplier);
- multiplier *= 128;
- } while ((digit & 0x80) != 0);
-
- return new MultiByteInteger(msgLength, count);
- }
-
- protected byte[] encodeMessageId() throws MqttException {
- try {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(baos);
- dos.writeShort(msgId);
- dos.flush();
- return baos.toByteArray();
- }
- catch (IOException ex) {
- throw new MqttException(ex);
- }
- }
-
- public boolean isRetryable() {
- return false;
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.internal.wire;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.MqttPersistable;
+import org.eclipse.paho.client.mqttv3.internal.ExceptionHelper;
+
+
+/**
+ * An on-the-wire representation of an MQTT message.
+ */
+public abstract class MqttWireMessage {
+ public static final byte MESSAGE_TYPE_CONNECT = 1;
+ public static final byte MESSAGE_TYPE_CONNACK = 2;
+ public static final byte MESSAGE_TYPE_PUBLISH = 3;
+ public static final byte MESSAGE_TYPE_PUBACK = 4;
+ public static final byte MESSAGE_TYPE_PUBREC = 5;
+ public static final byte MESSAGE_TYPE_PUBREL = 6;
+ public static final byte MESSAGE_TYPE_PUBCOMP = 7;
+ public static final byte MESSAGE_TYPE_SUBSCRIBE = 8;
+ public static final byte MESSAGE_TYPE_SUBACK = 9;
+ public static final byte MESSAGE_TYPE_UNSUBSCRIBE = 10;
+ public static final byte MESSAGE_TYPE_UNSUBACK = 11;
+ public static final byte MESSAGE_TYPE_PINGREQ = 12;
+ public static final byte MESSAGE_TYPE_PINGRESP = 13;
+ public static final byte MESSAGE_TYPE_DISCONNECT = 14;
+
+ protected static final String STRING_ENCODING = "UTF-8";
+
+ private static final String PACKET_NAMES[] = { "reserved", "CONNECT", "CONNACK", "PUBLISH",
+ "PUBACK", "PUBREC", "PUBREL", "PUBCOMP", "SUBSCRIBE", "SUBACK",
+ "UNSUBSCRIBE", "UNSUBACK", "PINGREQ", "PINGRESP", "DISCONNECT" };
+
+ //The type of the message (e.g. CONNECT, PUBLISH, PUBACK)
+ private byte type;
+ //The MQTT message ID
+ protected int msgId;
+
+ protected boolean duplicate = false;
+
+
+ public MqttWireMessage(byte type) {
+ this.type = type;
+ // Use zero as the default message ID. Can't use -1, as that is serialized
+ // as 65535, which would be a valid ID.
+ this.msgId = 0;
+ }
+
+ /**
+ * Sub-classes should override this to encode the message info.
+ * Only the least-significant four bits will be used.
+ */
+ protected abstract byte getMessageInfo();
+
+ /**
+ * Sub-classes should override this method to supply the payload bytes.
+ */
+ public byte[] getPayload() throws MqttException {
+ return new byte[0];
+ }
+
+ /**
+ * Returns the type of the message.
+ */
+ public byte getType() {
+ return type;
+ }
+
+ /**
+ * Returns the MQTT message ID.
+ */
+ public int getMessageId() {
+ return msgId;
+ }
+
+ /**
+ * Sets the MQTT message ID.
+ */
+ public void setMessageId(int msgId) {
+ this.msgId = msgId;
+ }
+
+ /**
+ * Returns a key associated with the message. For most message types
+ * this will be unique. For connect, disconnect and ping only one
+ * message of this type is allowed so a fixed key will be returned
+ * @return key a key associated with the message
+ */
+ public String getKey() {
+ return new Integer(getMessageId()).toString();
+ }
+
+ public byte[] getHeader() throws MqttException {
+ try {
+ int first = ((getType() & 0x0f) << 4) ^ (getMessageInfo() & 0x0f);
+ byte[] varHeader = getVariableHeader();
+ int remLen = varHeader.length + getPayload().length;
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(baos);
+ dos.writeByte(first);
+ dos.write(encodeMBI(remLen));
+ dos.write(varHeader);
+ dos.flush();
+ return baos.toByteArray();
+ } catch(IOException ioe) {
+ throw new MqttException(ioe);
+ }
+ }
+
+ protected abstract byte[] getVariableHeader() throws MqttException;
+
+
+ /**
+ * Returns whether or not this message needs to include a message ID.
+ */
+ public boolean isMessageIdRequired() {
+ return true;
+ }
+
+ public static MqttWireMessage createWireMessage(MqttPersistable data) throws MqttException {
+ byte[] payload = data.getPayloadBytes();
+ // The persistable interface allows a message to be restored entirely in the header array
+ // Need to treat these two arrays as a single array of bytes and use the decoding
+ // logic to identify the true header/payload split
+ if (payload == null) {
+ payload = new byte[0];
+ }
+ MultiByteArrayInputStream mbais = new MultiByteArrayInputStream(
+ data.getHeaderBytes(),
+ data.getHeaderOffset(),
+ data.getHeaderLength(),
+ payload,
+ data.getPayloadOffset(),
+ data.getPayloadLength());
+ return createWireMessage(mbais);
+ }
+
+ public static MqttWireMessage createWireMessage(byte[] bytes) throws MqttException {
+ ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+ return createWireMessage(bais);
+ }
+
+ private static MqttWireMessage createWireMessage(InputStream inputStream) throws MqttException {
+ try {
+ CountingInputStream counter = new CountingInputStream(inputStream);
+ DataInputStream in = new DataInputStream(counter);
+ int first = in.readUnsignedByte();
+ byte type = (byte) (first >> 4);
+ byte info = (byte) (first &= 0x0f);
+ long remLen = readMBI(in).getValue();
+ long totalToRead = counter.getCounter() + remLen;
+
+ MqttWireMessage result;
+ long remainder = totalToRead - counter.getCounter();
+ byte[] data = new byte[0];
+ // The remaining bytes must be the payload...
+ if (remainder > 0) {
+ data = new byte[(int) remainder];
+ in.readFully(data, 0, data.length);
+ }
+
+ if (type == MqttWireMessage.MESSAGE_TYPE_CONNECT) {
+ result = new MqttConnect(info, data);
+ }
+ else if (type == MqttWireMessage.MESSAGE_TYPE_PUBLISH) {
+ result = new MqttPublish(info, data);
+ }
+ else if (type == MqttWireMessage.MESSAGE_TYPE_PUBACK) {
+ result = new MqttPubAck(info, data);
+ }
+ else if (type == MqttWireMessage.MESSAGE_TYPE_PUBCOMP) {
+ result = new MqttPubComp(info, data);
+ }
+ else if (type == MqttWireMessage.MESSAGE_TYPE_CONNACK) {
+ result = new MqttConnack(info, data);
+ }
+ else if (type == MqttWireMessage.MESSAGE_TYPE_PINGREQ) {
+ result = new MqttPingReq(info, data);
+ }
+ else if (type == MqttWireMessage.MESSAGE_TYPE_PINGRESP) {
+ result = new MqttPingResp(info, data);
+ }
+ else if (type == MqttWireMessage.MESSAGE_TYPE_SUBSCRIBE) {
+ result = new MqttSubscribe(info, data);
+ }
+ else if (type == MqttWireMessage.MESSAGE_TYPE_SUBACK) {
+ result = new MqttSuback(info, data);
+ }
+ else if (type == MqttWireMessage.MESSAGE_TYPE_UNSUBSCRIBE) {
+ result = new MqttUnsubscribe(info, data);
+ }
+ else if (type == MqttWireMessage.MESSAGE_TYPE_UNSUBACK) {
+ result = new MqttUnsubAck(info, data);
+ }
+ else if (type == MqttWireMessage.MESSAGE_TYPE_PUBREL) {
+ result = new MqttPubRel(info, data);
+ }
+ else if (type == MqttWireMessage.MESSAGE_TYPE_PUBREC) {
+ result = new MqttPubRec(info, data);
+ }
+ else if (type == MqttWireMessage.MESSAGE_TYPE_DISCONNECT) {
+ result = new MqttDisconnect(info, data);
+ }
+ else {
+ throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_UNEXPECTED_ERROR);
+ }
+ return result;
+ } catch(IOException io) {
+ throw new MqttException(io);
+ }
+ }
+
+ protected static byte[] encodeMBI( long number) {
+ int numBytes = 0;
+ long no = number;
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ // Encode the remaining length fields in the four bytes
+ do {
+ byte digit = (byte)(no % 128);
+ no = no / 128;
+ if (no > 0) {
+ digit |= 0x80;
+ }
+ bos.write(digit);
+ numBytes++;
+ } while ( (no > 0) && (numBytes<4) );
+
+ return bos.toByteArray();
+ }
+
+ /**
+ * Decodes an MQTT Multi-Byte Integer from the given stream.
+ */
+ protected static MultiByteInteger readMBI(DataInputStream in) throws IOException {
+ byte digit;
+ long msgLength = 0;
+ int multiplier = 1;
+ int count = 0;
+
+ do {
+ digit = in.readByte();
+ count++;
+ msgLength += ((digit & 0x7F) * multiplier);
+ multiplier *= 128;
+ } while ((digit & 0x80) != 0);
+
+ return new MultiByteInteger(msgLength, count);
+ }
+
+ protected byte[] encodeMessageId() throws MqttException {
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(baos);
+ dos.writeShort(msgId);
+ dos.flush();
+ return baos.toByteArray();
+ }
+ catch (IOException ex) {
+ throw new MqttException(ex);
+ }
+ }
+
+ public boolean isRetryable() {
+ return false;
+ }
+
+ public void setDuplicate(boolean duplicate) {
+ this.duplicate = duplicate;
+ }
+
+ /**
+ * Encodes a String given into UTF-8, before writing this to the DataOutputStream the length of the
+ * encoded string is encoded into two bytes and then written to the DataOutputStream. @link{DataOutputStream#writeUFT(String)}
+ * should be no longer used. @link{DataOutputStream#writeUFT(String)} does not correctly encode UTF-16 surrogate characters.
+ *
+ * @param dos The stream to write the encoded UTF-8 String to.
+ * @param stringToEncode The String to be encoded
+ * @throws MqttException Thrown when an error occurs with either the encoding or writing the data to the stream
+ */
+ protected void encodeUTF8(DataOutputStream dos, String stringToEncode) throws MqttException
+ {
+ try {
+
+ byte[] encodedString = stringToEncode.getBytes("UTF-8");
+ byte byte1 = (byte) ((encodedString.length >>> 8) & 0xFF);
+ byte byte2 = (byte) ((encodedString.length >>> 0) & 0xFF);
+
+
+ dos.write(byte1);
+ dos.write(byte2);
+ dos.write(encodedString);
+ }
+ catch(UnsupportedEncodingException ex)
+ {
+ throw new MqttException(ex);
+ } catch (IOException ex) {
+ throw new MqttException(ex);
+ }
+ }
+
+ /**
+ * Decodes a UTF-8 string from the DataInputStream provided. @link(DataInoutStream#readUTF()) should be no longer used, because @link(DataInoutStream#readUTF())
+ * does not decode UTF-16 surrogate characters correctly.
+ *
+ * @param input The input stream from which to read the encoded string
+ * @return a decoded String from the DataInputStream
+ * @throws MqttException thrown when an error occurs with either reading from the stream or
+ * decoding the encoded string.
+ */
+ protected String decodeUTF8(DataInputStream input) throws MqttException
+ {
+ int encodedLength;
+ try {
+ encodedLength = input.readUnsignedShort();
+
+ byte[] encodedString = new byte[encodedLength];
+ input.readFully(encodedString);
+
+ return new String(encodedString, "UTF-8");
+ } catch (IOException ex) {
+ throw new MqttException(ex);
+ }
+ }
+
+ public String toString() {
+ return PACKET_NAMES[type];
+ }
+
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MultiByteArrayInputStream.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MultiByteArrayInputStream.java
similarity index 73%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MultiByteArrayInputStream.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MultiByteArrayInputStream.java
index 8460e679..b9b95115 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/internal/wire/MultiByteArrayInputStream.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/internal/wire/MultiByteArrayInputStream.java
@@ -1,52 +1,56 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3.internal.wire;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-public class MultiByteArrayInputStream extends InputStream {
-
- private byte[] bytesA;
- private int offsetA;
- private int lengthA;
- private byte[] bytesB;
- private int offsetB;
- private int lengthB;
-
- private int pos = 0;
-
- public MultiByteArrayInputStream(byte[] bytesA, int offsetA, int lengthA, byte[] bytesB, int offsetB, int lengthB) {
- this.bytesA = bytesA;
- this.bytesB = bytesB;
- this.offsetA = offsetA;
- this.offsetB = offsetB;
- this.lengthA = lengthA;
- this.lengthB = lengthB;
- }
- public int read() throws IOException {
- int result = -1;
- if (posA sample java.util.logging properties file - jsr47min.properties is provided that demonstrates
+ * how to run with a memory based trace facility that runs with minimal performance
+ * overhead. The memory buffer can be dumped when a log/trace record is written matching
+ * the MemoryHandlers trigger level or when the push method is invoked on the MemoryHandler.
+ * {@link org.eclipse.paho.client.mqttv3.util.Debug Debug} provides method to make it easy
+ * to dump the memory buffer as well as other useful debug info.
+ */
+public class JSR47Logger implements Logger {
+ private java.util.logging.Logger julLogger = null;
+ private ResourceBundle logMessageCatalog = null;
+ private ResourceBundle traceMessageCatalog = null;
+ private String catalogID = null;
+ private String resourceName = null;
+ private String loggerName = null;
+
+ /**
+ *
+ * @param logMsgCatalog The resource bundle associated with this logger
+ * @param loggerID The suffix for the loggerName (will be appeneded to org.eclipse.paho.client.mqttv3
+ * @param resourceContext A context for the logger e.g. clientID or appName...
+ */
+ public void initialise(ResourceBundle logMsgCatalog, String loggerID, String resourceContext ) {
+ this.traceMessageCatalog = logMessageCatalog;
+ this.resourceName = resourceContext;
+// loggerName = "org.eclipse.paho.client.mqttv3." + ((null == loggerID || 0 == loggerID.length()) ? "internal" : loggerID);
+ loggerName = loggerID;
+ this.julLogger = java.util.logging.Logger.getLogger(loggerName);
+ this.logMessageCatalog = logMsgCatalog;
+ this.traceMessageCatalog = logMsgCatalog;
+ this.catalogID = logMessageCatalog.getString("0");
+
+ }
+
+ public void setResourceName(String logContext) {
+ this.resourceName = logContext;
+ }
+
+ public boolean isLoggable(int level) {
+ return julLogger.isLoggable(mapJULLevel(level)); // || InternalTracer.isLoggable(level);
+ }
+
+ public void severe(String sourceClass, String sourceMethod, String msg) {
+ log(SEVERE, sourceClass, sourceMethod, msg, null, null);
+ }
+
+ public void severe(String sourceClass, String sourceMethod, String msg, Object[] inserts) {
+ log(SEVERE, sourceClass, sourceMethod, msg, inserts, null);
+ }
+
+ public void severe(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable thrown) {
+ log(SEVERE, sourceClass, sourceMethod, msg, inserts, thrown);
+ }
+
+ public void warning(String sourceClass, String sourceMethod, String msg) {
+ log(WARNING, sourceClass, sourceMethod, msg, null, null);
+ }
+
+ public void warning(String sourceClass, String sourceMethod, String msg, Object[] inserts) {
+ log(WARNING, sourceClass, sourceMethod, msg, inserts, null);
+ }
+
+ public void warning(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable thrown) {
+ log(WARNING, sourceClass, sourceMethod, msg, inserts, thrown);
+ }
+
+ public void info(String sourceClass, String sourceMethod, String msg) {
+ log(INFO, sourceClass, sourceMethod, msg, null, null);
+ }
+
+ public void info(String sourceClass, String sourceMethod, String msg, Object[] inserts) {
+ log(INFO, sourceClass, sourceMethod, msg, inserts, null);
+ }
+
+ public void info(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable thrown) {
+ log(INFO, sourceClass, sourceMethod, msg, inserts, thrown);
+ }
+
+ public void config(String sourceClass, String sourceMethod, String msg) {
+ log(CONFIG, sourceClass, sourceMethod, msg, null, null);
+ }
+
+ public void config(String sourceClass, String sourceMethod, String msg, Object[] inserts) {
+ log(CONFIG, sourceClass, sourceMethod, msg, inserts, null);
+ }
+
+ public void config(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable thrown) {
+ log(CONFIG, sourceClass, sourceMethod, msg, inserts, thrown);
+ }
+
+ public void log(int level, String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable thrown) {
+// InternalTracer.log(this.catalogID, level, sourceClass, sourceMethod, msg, inserts, thrown);
+ java.util.logging.Level julLevel = mapJULLevel(level);
+ if (julLogger.isLoggable(julLevel)) {
+ logToJsr47(julLevel, sourceClass, sourceMethod, this.catalogID, this.logMessageCatalog, msg, inserts, thrown);
+ }
+ }
+
+// public void setTrace(Trace trace) {
+// InternalTracer.setTrace(trace);
+// }
+
+ public void fine(String sourceClass, String sourceMethod, String msg) {
+ trace(FINE, sourceClass, sourceMethod, msg, null, null);
+ }
+
+ public void fine(String sourceClass, String sourceMethod, String msg, Object[] inserts) {
+ trace(FINE, sourceClass, sourceMethod, msg, inserts, null);
+ }
+
+ public void fine(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable ex) {
+ trace(FINE, sourceClass, sourceMethod, msg, inserts, ex);
+ }
+
+ public void finer(String sourceClass, String sourceMethod, String msg) {
+ trace(FINER, sourceClass, sourceMethod, msg, null, null);
+ }
+
+ public void finer(String sourceClass, String sourceMethod, String msg, Object[] inserts) {
+ trace(FINER, sourceClass, sourceMethod, msg, inserts, null);
+ }
+
+ public void finer(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable ex) {
+ trace(FINER, sourceClass, sourceMethod, msg, inserts, ex);
+ }
+
+ public void finest(String sourceClass, String sourceMethod, String msg) {
+ trace(FINEST, sourceClass, sourceMethod, msg, null, null);
+ }
+
+ public void finest(String sourceClass, String sourceMethod, String msg, Object[] inserts) {
+ trace(FINEST, sourceClass, sourceMethod, msg, inserts, null);
+ }
+
+ public void finest(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable ex) {
+ trace(FINEST, sourceClass, sourceMethod, msg, inserts, ex);
+ }
+
+
+ public void trace(int level, String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable ex) {
+ java.util.logging.Level julLevel = mapJULLevel(level);
+ boolean isJULLoggable = julLogger.isLoggable(julLevel);
+// if (FINE == level || isJULLoggable || InternalTracer.isLoggable(level)) {
+// InternalTracer.traceForced(level, sourceClass, sourceMethod, msg, inserts);
+// }
+ if (isJULLoggable) {
+ logToJsr47(julLevel, sourceClass, sourceMethod, this.catalogID, this.traceMessageCatalog, msg, inserts, ex);
+ }
+ }
+
+
+ private String getResourceMessage(ResourceBundle messageCatalog, String msg) {
+ String message;
+ try {
+ message = messageCatalog.getString(msg);
+ } catch (MissingResourceException e) {
+ // This is acceptable, simply return the given msg string.
+ message = msg;
+ }
+ return message;
+ }
+
+ private void logToJsr47(java.util.logging.Level julLevel, String sourceClass, String sourceMethod, String catalogName,
+ ResourceBundle messageCatalog, String msg, Object[] inserts, Throwable thrown) {
+// LogRecord logRecord = new LogRecord(julLevel, msg);
+ String formattedWithArgs = msg;
+ if (msg.indexOf("=====")== -1) {
+ formattedWithArgs = MessageFormat.format(getResourceMessage(messageCatalog, msg), inserts);
+ }
+ LogRecord logRecord = new LogRecord(julLevel, resourceName + ": " +formattedWithArgs);
+
+ logRecord.setSourceClassName(sourceClass);
+ logRecord.setSourceMethodName(sourceMethod);
+ logRecord.setLoggerName(loggerName);
+// logRecord.setResourceBundleName(catalogName);
+// logRecord.setResourceBundle(messageCatalog);
+// if (null != inserts) {
+// logRecord.setParameters(inserts);
+// }
+ if (null != thrown) {
+ logRecord.setThrown(thrown);
+ }
+
+ julLogger.log(logRecord);
+ }
+
+ private java.util.logging.Level mapJULLevel(int level) {
+ java.util.logging.Level julLevel = null;
+
+ switch (level) {
+ case SEVERE:
+ julLevel = java.util.logging.Level.SEVERE;
+ break;
+ case WARNING:
+ julLevel = java.util.logging.Level.WARNING;
+ break;
+ case INFO:
+ julLevel = java.util.logging.Level.INFO;
+ break;
+ case CONFIG:
+ julLevel = java.util.logging.Level.CONFIG;
+ break;
+ case FINE:
+ julLevel = java.util.logging.Level.FINE;
+ break;
+ case FINER:
+ julLevel = java.util.logging.Level.FINER;
+ break;
+ case FINEST:
+ julLevel = java.util.logging.Level.FINEST;
+ break;
+
+ default:
+ }
+
+ return julLevel;
+ }
+
+ public String formatMessage(String msg, Object[] inserts) {
+ String formatString;
+ try {
+ formatString = logMessageCatalog.getString(msg);
+ } catch (MissingResourceException e) {
+ formatString = msg;
+ }
+ return formatString;
+ }
+
+ public void dumpTrace() {
+ dumpMemoryTrace47(julLogger);
+ }
+
+ protected static void dumpMemoryTrace47(java.util.logging.Logger logger) {
+ MemoryHandler mHand = null;
+
+ if (logger!= null) {
+ Handler[] handlers = logger.getHandlers();
+
+ for (int i=0; i
+ * The int levels define a set of standard logging levels that can be used to
+ * control logging output. The logging levels are ordered and are specified by
+ * ordered integers. Enabling logging at a given level also enables logging at
+ * all higher levels.
+ *
+ * Clients should use the the convenience methods such as severe() and fine() or
+ * one of the predefined level constants such as Logger.SEVERE and Logger.FINE
+ * with the appropriate log(int level...) or trace(int level...) methods.
+ *
+ * The levels in descending order are:
+ *
+ *
SEVERE (log - highest value)
+ *
WARNING (log)
+ *
INFO (log)
+ *
CONFIG (log)
+ *
FINE (trace)
+ *
FINER (trace)
+ *
FINEST (trace - lowest value)
+ *
+ *
+ */
+public interface Logger {
+ /**
+ * SEVERE is a message level indicating a serious failure.
+ *
+ * In general SEVERE messages should describe events that are of
+ * considerable importance and which will prevent normal program execution.
+ * They should be reasonably intelligible to end users and to system
+ * administrators.
+ */
+ public static final int SEVERE = 1;
+ /**
+ * WARNING is a message level indicating a potential problem.
+ *
+ * In general WARNING messages should describe events that will be of
+ * interest to end users or system managers, or which indicate potential
+ * problems.
+ */
+ public static final int WARNING = 2;
+ /**
+ * INFO is a message level for informational messages.
+ *
+ * Typically INFO messages will be written to the console or its equivalent.
+ * So the INFO level should only be used for reasonably significant messages
+ * that will make sense to end users and system admins.
+ */
+ public static final int INFO = 3;
+ /**
+ * CONFIG is a message level for static configuration messages.
+ *
+ * CONFIG messages are intended to provide a variety of static configuration
+ * information, to assist in debugging problems that may be associated with
+ * particular configurations. For example, CONFIG message might include the
+ * CPU type, the graphics depth, the GUI look-and-feel, etc.
+ */
+ public static final int CONFIG = 4;
+ /**
+ * FINE is a message level providing tracing information.
+ *
+ * All of FINE, FINER, and FINEST are intended for relatively detailed
+ * tracing. The exact meaning of the three levels will vary between
+ * subsystems, but in general, FINEST should be used for the most voluminous
+ * detailed output, FINER for somewhat less detailed output, and FINE for
+ * the lowest volume (and most important) messages.
+ *
+ * In general the FINE level should be used for information that will be
+ * broadly interesting to developers who do not have a specialized interest
+ * in the specific subsystem.
+ *
+ * FINE messages might include things like minor (recoverable) failures.
+ * Issues indicating potential performance problems are also worth logging
+ * as FINE.
+ */
+ public static final int FINE = 5;
+ /**
+ * FINER indicates a fairly detailed tracing message. By default logging
+ * calls for entering, returning, or throwing an exception are traced at
+ * this level.
+ */
+ public static final int FINER = 6;
+ /**
+ * FINEST indicates a highly detailed tracing message.
+ */
+ public static final int FINEST = 7;
+
+ public void initialise(ResourceBundle messageCatalog, String loggerID, String resourceName);
+
+ /**
+ * Set a name that can be used to provide context with each log record.
+ * This overrides the value passed in on initialise
+ */
+ public void setResourceName(String logContext);
+
+ /**
+ * Check if a message of the given level would actually be logged by this
+ * logger. This check is based on the Loggers effective level, which may be
+ * inherited from its parent.
+ *
+ * @param level
+ * a message logging level.
+ * @return true if the given message level is currently being logged.
+ */
+ public boolean isLoggable(int level);
+
+ /**
+ * Log a message, specifying source class and method, if the logger is
+ * currently enabled for the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used.
+ */
+ public void severe(String sourceClass, String sourceMethod, String msg);
+
+ /**
+ * Log a message, specifying source class and method, with an array of
+ * object arguments, if the logger is currently enabled for the given
+ * message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used. The formatter uses java.text.MessageFormat
+ * style formatting to format parameters, so for example a format
+ * string "{0} {1}" would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ */
+ public void severe(String sourceClass, String sourceMethod, String msg, Object[] inserts);
+
+ /**
+ * Log a message, specifying source class and method, with an array of
+ * object arguments and a throwable, if the logger is currently enabled for
+ * the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used. The formatter uses java.text.MessageFormat
+ * style formatting to format parameters, so for example a format
+ * string "{0} {1}" would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ * @param thrown
+ * Throwable associated with log message.
+ */
+ public void severe(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable thrown);
+
+ /**
+ * Log a message, specifying source class and method, if the logger is
+ * currently enabled for the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used.
+ */
+ public void warning(String sourceClass, String sourceMethod, String msg);
+
+ /**
+ * Log a message, specifying source class and method, with an array of
+ * object arguments, if the logger is currently enabled for the given
+ * message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used. The formatter uses java.text.MessageFormat
+ * style formatting to format parameters, so for example a format
+ * string "{0} {1}" would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ */
+ public void warning(String sourceClass, String sourceMethod, String msg, Object[] inserts);
+
+ /**
+ * Log a message, specifying source class and method, with an array of
+ * object arguments and a throwable, if the logger is currently enabled for
+ * the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used. The formatter uses java.text.MessageFormat
+ * style formatting to format parameters, so for example a format
+ * string "{0} {1}" would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ * @param thrown
+ * Throwable associated with log message.
+ */
+ public void warning(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable thrown);
+
+ /**
+ * Log a message, specifying source class and method, if the logger is
+ * currently enabled for the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used.
+ */
+ public void info(String sourceClass, String sourceMethod, String msg);
+
+ /**
+ * Log a message, specifying source class and method, with an array of
+ * object arguments, if the logger is currently enabled for the given
+ * message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used. The formatter uses java.text.MessageFormat
+ * style formatting to format parameters, so for example a format
+ * string "{0} {1}" would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ */
+ public void info(String sourceClass, String sourceMethod, String msg, Object[] inserts);
+
+ /**
+ * Log a message, specifying source class and method, with an array of
+ * object arguments and a throwable, if the logger is currently enabled for
+ * the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used. The formatter uses java.text.MessageFormat
+ * style formatting to format parameters, so for example a format
+ * string "{0} {1}" would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ * @param thrown
+ * Throwable associated with log message.
+ */
+ public void info(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable thrown);
+
+ /**
+ * Log a message, specifying source class and method, if the logger is
+ * currently enabled for the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used.
+ */
+ public void config(String sourceClass, String sourceMethod, String msg);
+
+ /**
+ * Log a message, specifying source class and method, with an array of
+ * object arguments, if the logger is currently enabled for the given
+ * message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used. The formatter uses java.text.MessageFormat
+ * style formatting to format parameters, so for example a format
+ * string "{0} {1}" would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ */
+ public void config(String sourceClass, String sourceMethod, String msg, Object[] inserts);
+
+ /**
+ * Log a message, specifying source class and method, with an array of
+ * object arguments and a throwable, if the logger is currently enabled for
+ * the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used. The formatter uses java.text.MessageFormat
+ * style formatting to format parameters, so for example a format
+ * string "{0} {1}" would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ * @param thrown
+ * Throwable associated with log message.
+ */
+ public void config(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable thrown);
+
+ /**
+ * Trace a message, specifying source class and method, if the logger is
+ * currently enabled for the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message catalog for the message or the actual
+ * message itself. During formatting, if the logger has a mapping
+ * for the msg string, then the msg string is replaced by the
+ * value. Otherwise the original msg string is used.
+ */
+ public void fine(String sourceClass, String sourceMethod, String msg);
+
+ /**
+ * Trace a message, specifying source class and method, with an array of
+ * object arguments, if the logger is currently enabled for the given
+ * message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message catalog for the message or the actual
+ * message itself. During formatting, if the logger has a mapping
+ * for the msg string, then the msg string is replaced by the
+ * value. Otherwise the original msg string is used. The
+ * formatter uses java.text.MessageFormat style formatting to
+ * format parameters, so for example a format string "{0} {1}"
+ * would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ */
+ public void fine(String sourceClass, String sourceMethod, String msg, Object[] inserts);
+
+ public void fine(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable ex);
+
+ /**
+ * Trace a message, specifying source class and method, if the logger is
+ * currently enabled for the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message catalog for the message or the actual
+ * message itself. During formatting, if the logger has a mapping
+ * for the msg string, then the msg string is replaced by the
+ * value. Otherwise the original msg string is used.
+ */
+ public void finer(String sourceClass, String sourceMethod, String msg);
+
+ /**
+ * Trace a message, specifying source class and method, with an array of
+ * object arguments, if the logger is currently enabled for the given
+ * message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message catalog for the message or the actual
+ * message itself. During formatting, if the logger has a mapping
+ * for the msg string, then the msg string is replaced by the
+ * value. Otherwise the original msg string is used. The
+ * formatter uses java.text.MessageFormat style formatting to
+ * format parameters, so for example a format string "{0} {1}"
+ * would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ */
+ public void finer(String sourceClass, String sourceMethod, String msg, Object[] inserts);
+
+ public void finer(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable ex);
+
+ /**
+ * Trace a message, specifying source class and method, if the logger is
+ * currently enabled for the given message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message catalog for the message or the actual
+ * message itself. During formatting, if the logger has a mapping
+ * for the msg string, then the msg string is replaced by the
+ * value. Otherwise the original msg string is used.
+ */
+ public void finest(String sourceClass, String sourceMethod, String msg);
+
+ /**
+ * Trace a message, specifying source class and method, with an array of
+ * object arguments, if the logger is currently enabled for the given
+ * message level.
+ *
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message catalog for the message or the actual
+ * message itself. During formatting, if the logger has a mapping
+ * for the msg string, then the msg string is replaced by the
+ * value. Otherwise the original msg string is used. The
+ * formatter uses java.text.MessageFormat style formatting to
+ * format parameters, so for example a format string "{0} {1}"
+ * would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ */
+ public void finest(String sourceClass, String sourceMethod, String msg, Object[] inserts);
+
+ public void finest(String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable ex);
+
+ /**
+ * Log a message, specifying source class and method, with an array of
+ * object arguments and a throwable, if the logger is currently enabled for
+ * the given message level.
+ *
+ * @param level
+ * One of the message level identifiers, e.g. SEVERE.
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used. The formatter uses java.text.MessageFormat
+ * style formatting to format parameters, so for example a format
+ * string "{0} {1}" would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message, may be null.
+ * @param thrown
+ * Throwable associated with log message.
+ */
+ public void log(int level, String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable thrown);
+
+ /**
+ * Log a trace message, specifying source class and method, with an array of
+ * object arguments and a throwable, if the logger is currently enabled for
+ * the given message level.
+ *
+ * @param level
+ * One of the message level identifiers, e.g. SEVERE.
+ * @param sourceClass
+ * Name of class that issued the logging request.
+ * @param sourceMethod
+ * Name of method that issued the logging request.
+ * @param msg
+ * The key in the message catalog for the message or the actual
+ * message itself. During formatting, if the logger has a mapping
+ * for the msg string, then the msg string is replaced by the
+ * value. Otherwise the original msg string is used. The
+ * formatter uses java.text.MessageFormat style formatting to
+ * format parameters, so for example a format string "{0} {1}"
+ * would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message, may be null.
+ */
+ public void trace(int level, String sourceClass, String sourceMethod, String msg, Object[] inserts, Throwable ex);
+
+ /**
+ * Format a log message without causing it to be written to the log.
+ *
+ * @param msg
+ * The key in the message localization catalog for the message or
+ * the actual message itself. During formatting, if the logger
+ * has a mapping for the msg string, then the msg string is
+ * replaced by the localized value. Otherwise the original msg
+ * string is used. The formatter uses java.text.MessageFormat
+ * style formatting to format parameters, so for example a format
+ * string "{0} {1}" would format two inserts into the message.
+ * @param inserts
+ * Array of parameters to the message.
+ * @return The formatted message for the current locale.
+ */
+ public String formatMessage(String msg, Object[] inserts);
+
+ public void dumpTrace();
+}
\ No newline at end of file
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/logging/LoggerFactory.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/logging/LoggerFactory.java
new file mode 100644
index 00000000..3f915bc7
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/logging/LoggerFactory.java
@@ -0,0 +1,156 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.logging;
+
+import java.lang.reflect.Method;
+
+/**
+ * LoggerFactory will create a logger instance ready for use by the caller.
+ *
+ * The default is to create a logger that utilises the Java's built in
+ * logging facility java.util.logging (JSR47). It is possible to override
+ * this for systems where JSR47 is not available or an alternative logging
+ * facility is needed by using setLogger and passing the the class name of
+ * a logger that implements {@link Logger}
+ */
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+/**
+ * A factory that returns a logger for use by the MQTT client.
+ *
+ * The default log and trace facility uses Java's build in log facility:-
+ * java.util.logging. For systems where this is not available or where
+ * an alternative logging framework is required the logging facility can be
+ * replaced using {@link org.eclipse.paho.client.mqttv3.logging.LoggerFactory#setLogger(String)}
+ * which takes an implementation of the {@link org.eclipse.paho.client.mqttv3.logging.Logger}
+ * interface.
+ */
+public class LoggerFactory {
+ /**
+ * Default message catalog.
+ */
+ public final static String MQTT_CLIENT_MSG_CAT = "org.eclipse.paho.client.mqttv3.internal.nls.logcat";
+ private static final String CLASS_NAME = LoggerFactory.class.getName();
+
+ private static String overrideloggerClassName = null;
+ /**
+ * Default logger that uses java.util.logging.
+ */
+ private static String jsr47LoggerClassName = "org.eclipse.paho.client.mqttv3.logging.JSR47Logger";
+
+ /**
+ * Find or create a logger for a named package/class.
+ * If a logger has already been created with the given name
+ * it is returned. Otherwise a new logger is created. By default a logger
+ * that uses java.util.logging will be returned.
+ *
+ * @param messageCatalogName the resource bundle containing the logging messages.
+ * @param loggerID unique name to identify this logger.
+ * @return a suitable Logger.
+ * @throws Exception
+ */
+ public static Logger getLogger(String messageCatalogName, String loggerID) {
+ String loggerClassName = overrideloggerClassName;
+ Logger logger = null;
+
+ if (loggerClassName == null) {
+ loggerClassName = jsr47LoggerClassName;
+ }
+// logger = getJSR47Logger(ResourceBundle.getBundle(messageCatalogName), loggerID, null) ;
+ logger = getLogger(loggerClassName, ResourceBundle.getBundle(messageCatalogName), loggerID, null) ;
+// }
+
+ if (null == logger) {
+ throw new MissingResourceException("Error locating the logging class", CLASS_NAME, loggerID);
+ }
+
+ return logger;
+ }
+
+
+ /**
+ * Return an instance of a logger
+ *
+ * @param the class name of the load to load
+ * @param messageCatalog the resource bundle containing messages
+ * @param loggerID an identifier for the logger
+ * @param resourceName a name or context to associate with this logger instance.
+ * @return a ready for use logger
+ */
+ private static Logger getLogger(String loggerClassName, ResourceBundle messageCatalog, String loggerID, String resourceName) { //, FFDC ffdc) {
+ Logger logger = null;
+ Class logClass = null;
+
+ try {
+ logClass = Class.forName(loggerClassName);
+ } catch (NoClassDefFoundError ncdfe) {
+ return null;
+ } catch (ClassNotFoundException cnfe) {
+ return null;
+ }
+ if (null != logClass) {
+ // Now instantiate the log
+ try {
+ logger = (Logger)logClass.newInstance();
+ } catch (IllegalAccessException e) {
+ return null;
+ } catch (InstantiationException e) {
+ return null;
+ } catch (ExceptionInInitializerError e) {
+ return null;
+ } catch (SecurityException e) {
+ return null;
+ }
+ logger.initialise(messageCatalog, loggerID, resourceName);
+ }
+
+ return logger;
+ }
+
+ /**
+ * When run in JSR47, this allows access to the properties in the logging.properties
+ * file.
+ * If not run in JSR47, or the property isn't set, returns null.
+ * @param name the property to return
+ * @return the property value, or null if it isn't set or JSR47 isn't being used
+ */
+ public static String getLoggingProperty(String name) {
+ String result = null;
+ try {
+ // Hide behind reflection as java.util.logging is guaranteed to be
+ // available.
+ Class logManagerClass = Class.forName("java.util.logging.LogManager");
+ Method m1 = logManagerClass.getMethod("getLogManager", new Class[]{});
+ Object logManagerInstance = m1.invoke(null, null);
+ Method m2 = logManagerClass.getMethod("getProperty", new Class[]{String.class});
+ result = (String)m2.invoke(logManagerInstance,new Object[]{name});
+ } catch(Exception e) {
+ // Any error, assume JSR47 isn't available and return null
+ result = null;
+ }
+ return result;
+ }
+
+ /**
+ * Set the class name of the logger that the LoggerFactory will load
+ * If not set getLogger will attempt to create a logger
+ * appropriate for the platform.
+ * @param loggerClassName - Logger implementation class name to use.
+ */
+ public static void setLogger(String loggerClassName) {
+ LoggerFactory.overrideloggerClassName = loggerClassName;
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/logging/SimpleLogFormatter.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/logging/SimpleLogFormatter.java
new file mode 100644
index 00000000..63dedede
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/logging/SimpleLogFormatter.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (c) 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ */
+
+package org.eclipse.paho.client.mqttv3.logging;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.MessageFormat;
+import java.util.Date;
+import java.util.logging.Formatter;
+import java.util.logging.LogRecord;
+
+/**
+ * SimpleLogFormatter prints a single line
+ * log record in human readable form.
+ */
+public class SimpleLogFormatter extends Formatter {
+
+ private static final String LS = System.getProperty("line.separator");
+ /**
+ * Constructs a SimpleFormatter object.
+ */
+ public SimpleLogFormatter() {
+ super();
+ }
+
+ /**
+ * Format the logrecord as a single line with well defined columns.
+ */
+ public String format(LogRecord r) {
+ StringBuffer sb = new StringBuffer();
+ sb.append(r.getLevel().getName()).append("\t");
+ sb.append(MessageFormat.format("{0, date, yy-MM-dd} {0, time, kk:mm:ss.SSSS} ",
+ new Object[] { new Date(r.getMillis()) })+"\t");
+ String cnm = r.getSourceClassName();
+ String cn="";
+ if (cnm != null) {
+ int cnl = cnm.length();
+ if (cnl>20) {
+ cn = r.getSourceClassName().substring(cnl-19);
+ } else {
+ char sp[] = {' '};
+ StringBuffer sb1= new StringBuffer().append(cnm);
+ cn = sb1.append(sp,0, 1).toString();
+ }
+ }
+ sb.append(cn).append("\t").append(" ");
+ sb.append(left(r.getSourceMethodName(),23,' ')).append("\t");
+ sb.append(r.getThreadID()).append("\t");
+ sb.append(formatMessage(r)).append(LS);
+ if (null != r.getThrown()) {
+ sb.append("Throwable occurred: ");
+ Throwable t = r.getThrown();
+ PrintWriter pw = null;
+ try {
+ StringWriter sw = new StringWriter();
+ pw = new PrintWriter(sw);
+ t.printStackTrace(pw);
+ sb.append(sw.toString());
+ } finally {
+ if (pw != null) {
+ try {
+ pw.close();
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Left justify a string.
+ *
+ * @param s the string to justify
+ * @param width the field width to justify within
+ * @param fillChar the character to fill with
+ *
+ * @return the justified string.
+ */
+ public static String left(String s, int width, char fillChar) {
+ if (s.length() >= width) {
+ return s;
+ }
+ StringBuffer sb = new StringBuffer(width);
+ sb.append(s);
+ for (int i = width - s.length(); --i >= 0;) {
+ sb.append(fillChar);
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/logging/jsr47min.properties b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/logging/jsr47min.properties
new file mode 100644
index 00000000..06265514
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/logging/jsr47min.properties
@@ -0,0 +1,83 @@
+# Properties file which configures the operation of the JDK logging facility.
+#
+# The configuration in this file is the suggesgted configuration
+# for collecting trace for helping debug problems related to the
+# Paho MQTT client. It configures trace to be continuosly collected
+# in memory with minimal impact on performance.
+#
+# When the push trigger (by default a Severe level message) or a
+# specific request is made to "push" the in memory trace then it
+# is "pushed" to the configured target handler. By default
+# this is the standard java.util.logging.FileHandler. The Paho Debug
+# class can be used to push the memory trace to its target
+#
+# To enable trace either:
+# - use this properties file as is and set the logging facility up
+# to use it by configuring the util logging system property e.g.
+#
+# >java -Djava.util.logging.config.file=\jsr47min.properties
+#
+# - This contents of this file can also be merged with another
+# java.util.logging config file to ensure provide wider logging
+# and trace including Paho trace
+
+# Global logging properties.
+# ------------------------------------------
+# The set of handlers to be loaded upon startup.
+# Comma-separated list of class names.
+# - Root handlers are not enabled by default - just handlers on the Paho packages.
+#handlers=java.util.logging.MemoryHandler,java.util.logging.FileHandler, java.util.logging.ConsoleHandler
+
+# Default global logging level.
+# Loggers and Handlers may override this level
+#.level=INFO
+
+# Loggers
+# ------------------------------------------
+# A memoryhandler is attached to the paho packages
+# and the level specified to collected all trace related
+# to paho packages. This will override any root/global
+# level handlers if set.
+org.eclipse.paho.client.mqttv3.handlers=java.util.logging.MemoryHandler
+org.eclipse.paho.client.mqttv3.level=ALL
+# It is possible to set more granular trace on a per class basis e.g.
+#org.eclipse.paho.client.mqttv3.internal.ClientComms.level=ALL
+
+# Handlers
+# -----------------------------------------
+# Note: the target handler that is associated with the MemoryHandler is not a root handler
+# and hence not returned when getting the handlers from root. It appears accessing
+# target handler programatically is not possible as target is a private variable in
+# class MemoryHandler
+java.util.logging.MemoryHandler.level=FINEST
+java.util.logging.MemoryHandler.size=10000
+java.util.logging.MemoryHandler.push=SEVERE
+java.util.logging.MemoryHandler.target=java.util.logging.FileHandler
+#java.util.logging.MemoryHandler.target=java.util.logging.ConsoleHandler
+
+
+# --- FileHandler ---
+# Override of global logging level
+java.util.logging.FileHandler.level=ALL
+
+# Naming style for the output file:
+# (The output file is placed in the directory
+# defined by the "user.home" System property.)
+# See java.util.logging for more options
+java.util.logging.FileHandler.pattern=%h/paho%u.log
+
+# Limiting size of output file in bytes:
+java.util.logging.FileHandler.limit=200000
+
+# Number of output files to cycle through, by appending an
+# integer to the base file name:
+java.util.logging.FileHandler.count=3
+
+# Style of output (Simple or XML):
+java.util.logging.FileHandler.formatter=org.eclipse.paho.client.mqttv3.logging.SimpleLogFormatter
+
+# --- ConsoleHandler ---
+# Override of global logging level
+#java.util.logging.ConsoleHandler.level=INFO
+#java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
+#java.util.logging.ConsoleHandler.formatter=org.eclipse.paho.client.mqttv3.logging.SimpleLogFormatter
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/logging/package.html b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/logging/package.html
new file mode 100644
index 00000000..a679cf10
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/logging/package.html
@@ -0,0 +1,18 @@
+
+Provides facilities to write and format log and trace to help debug problems.
+
+
The default log and trace facility uses Java's build in log facility:-
+java.util.logging. For systems where this is not available or where
+an alternative logging framework is required the logging facility can be
+replaced using {@link org.eclipse.paho.client.mqttv3.logging.LoggerFactory#setLogger(String)}
+which takes an implementation of the {@link org.eclipse.paho.client.mqttv3.logging.Logger}
+interface.
+
+
A sample java.util.logging properties file - jsr47min.properties is provided that demonstrates
+how to run with a memory based trace facility that runs with minimal performance
+overhead. The memory buffer can be dumped when a log/trace record is written matching
+the MemoryHandlers trigger level or when the push method is invoked on the MemoryHandler.
+{@link org.eclipse.paho.client.mqttv3.util.Debug Debug} provides method to make it easy
+to dump the memory buffer as well as other useful debug info.
+
+
\ No newline at end of file
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/package.html b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/package.html
new file mode 100644
index 00000000..00ca45c7
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/package.html
@@ -0,0 +1,136 @@
+
+Contains a programming interface enabling applications to communicate with an MQTT server.
+
+
+The MQ Telemetry Transport (MQTT) is a lightweight broker-based publish/subscribe
+messaging protocol designed to be open, simple, lightweight and easy to implement.
+These characteristics make it ideal for use in constrained environments, for example,
+but not limited to:
+
+
Where the network is expensive, has low bandwidth or is unreliable such as mobile and vsat networks
+
When run on an embedded or mobile device with limited processor, memory or battery
+
+
Features of the protocol include:
+
+
The publish/subscribe message pattern to provide one-to-many message
+ distribution and decoupling of applications
+
A messaging transport that is agnostic to the content of the payload
+
The use of TCP/IP to provide network connectivity
+
The use of SSL/TLS to provide network security and trust
+
Three qualities of service for message delivery which are maintained across
+ network, client and server breaks.
+
+
"At most once", where messages are delivered according to the best efforts
+ of the underlying TCP/IP network. Message loss or duplication can occur.
+ This level could be used, for example, with ambient sensor data where it
+ does not matter if an individual reading is lost as the next one will be published soon after.
+
"At least once", where messages are assured to arrive but duplicates may occur.
+
"Exactly once", where message are assured to arrive exactly once. This
+ level could be used, for example, with billing systems where duplicate or
+ lost messages could lead to incorrect charges being applied.
+
+ The quality of service for message delivery is met even if the network connection
+ breaks, or the client or the server stop while a message is being delivered
+
A small transport overhead (the fixed-length header is just 2 bytes), and
+ protocol exchanges minimised to reduce network traffic
+
A mechanism to notify interested parties to an abnormal disconnection of
+ a client using the Last Will and Testament feature
+
+
+
The basic means of operating the client is:
+
+
Create an instance of {@link org.eclipse.paho.client.mqttv3.MqttClient} or
+ {@link org.eclipse.paho.client.mqttv3.MqttAsyncClient}, providing
+ the address of an MQTT server and a unique client identifier.
+
connect to the server
+
Exchange messages with the server:
+
+
publish messages to the server
+ specifying a topic as the destination on the server
+
subscribe to one more topics. The server will send any messages
+ it receives on those topics to the client. The client will be informed when a message
+ arrives via a callback
+
+
disconnect from the server.
+
+
+
The programming model and concepts like the protocol are small and easy to use. Key concepts
+to use when creating MQTT application include:
+
+
Every client instance that connects to an MQTT server must have a unique client identifier.
+ If a second instance of a client with the same ID connects to a server the first instance will be
+ disconnected.
+
For message delivery to be reliable and withstand normal and abnormal network breaks together with client
+ and server outages the client must use a persistent store to hold messages while they are being delivered. This is
+ the default case where a file based persistent store
+ {@link org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence MqttDefaultFilePersistence} is used.
+
When connecting the {@link org.eclipse.paho.client.mqttv3.MqttConnectOptions#setCleanSession(boolean) cleansession}
+ option has a big impact on the operation of the client. If set to false:
+
+
Message delivery will match the quality of service specified when the message was published even across
+ failures of the network, client or server
+
The server will store messages for active subscriptions on behalf of the client when the client is not connected.
+ The server will deliver these messages to the client the next time it connects.
+
+ If set to true:
+
+
Any state stored on the client and server related to the client will be cleansed
+ before the connection is fully started. Subscriptions from earlier sessions will be unsubscribed
+ and any messages still in-flight from previous sessions will be deleted.
+
When the client disconnects either as the result of the application requesting a disconnect
+ or a network failure, state related to the client will be cleansed just as at connect time.
+
Messages will only be delivered to the quality of service requested at publish time if
+ the connection is maintained while the message is being delivered
+
+
When subscribing for messages the subscription can be for an absolute topic or a wildcarded topic.
+
When unsubscribing the topic to be unsubscribed must match one specified on an earlier subscribe.
+
There are two MQTT client libraries to choose from:
+
+
{@link org.eclipse.paho.client.mqttv3.IMqttAsyncClient MqttAsyncClient} which provides a non-blocking interface where
+ methods return before the requested operation has completed. The completion of the operation
+ can be monitored by in several ways:
+
+
Use the {@link org.eclipse.paho.client.mqttv3.IMqttToken#waitForCompletion waitForCompletion}
+ call on the token returned from the operation. This will block
+ until the operation completes.
+
Pass a {@link org.eclipse.paho.client.mqttv3.IMqttActionListener IMqttActionListener}
+ to the operation. The listener will then be called back when the operation completes.
+
Set a {@link org.eclipse.paho.client.mqttv3.MqttCallback MqttCallback} on the client. It
+ will be notified when a message arrives, a message have been delivered to the server and when the
+ connection to the server is lost.
+
+
{@link org.eclipse.paho.client.mqttv3.IMqttClient MqttClient} where methods block until
+ the operation has completed.
+
+
For both the blocking and non-blocking clients some operations are asynchronous. This includes:
+
+
Notification that a new message has arrived:
+ {@link org.eclipse.paho.client.mqttv3.MqttCallback#messageArrived messageArrived}.
+
Notification that the connection to the server has broken:
+ {@link org.eclipse.paho.client.mqttv3.MqttCallback#connectionLost connectionLost}.
+
Notification that a message has been delivered to the server:
+ {@link org.eclipse.paho.client.mqttv3.MqttCallback#deliveryComplete deliveryComplete}.
+
+ A client registers interest in these notifications by registering a
+ {@link org.eclipse.paho.client.mqttv3.MqttCallback MqttCallback} on the client
+
There are a number of programs that demonstrate the different modes of
+ writing MQTT applications
+
+
{@link org.eclipse.paho.sample.mqttv3app.Sample} uses the blocking client interface
+
{@link org.eclipse.paho.sample.mqttv3app.SampleAsyncCallBack} uses the asynchronous client with
+ callbacks which are notified when an operation completes
+
{@link org.eclipse.paho.sample.mqttv3app.SampleAsyncWait} uses the asynchronous client and
+ shows how to use the token returned from each operation to block until the operation completes.
+
+
{@link org.eclipse.paho.client.mqttv3.MqttConnectOptions MqttConnectOptions} can be used to override the
+ default connection options. This includes:
+
+
Setting the cleansession flag
+
Specifying a list of MQTT servers that the client can attempt to connect to
+
Set a keepalive interval
+
Setting the last will and testament
+
Setting security credentials
+
+
+
+
\ No newline at end of file
diff --git a/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/persist/MemoryPersistence.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/persist/MemoryPersistence.java
new file mode 100644
index 00000000..c684ddf9
--- /dev/null
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/persist/MemoryPersistence.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package org.eclipse.paho.client.mqttv3.persist;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import org.eclipse.paho.client.mqttv3.MqttClientPersistence;
+import org.eclipse.paho.client.mqttv3.MqttPersistable;
+import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
+
+/**
+ * Persistence that uses memory
+ *
+ * In cases where reliability is not required across client or device
+ * restarts memory this memory peristence can be used. In cases where
+ * reliability is required like when clean session is set to false
+ * then a non-volatile form of persistence should be used.
+ *
+ */
+public class MemoryPersistence implements MqttClientPersistence {
+
+ private Hashtable data;
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.MqttClientPersistence#close()
+ */
+ public void close() throws MqttPersistenceException {
+ data.clear();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.MqttClientPersistence#keys()
+ */
+ public Enumeration keys() throws MqttPersistenceException {
+ return data.keys();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.MqttClientPersistence#get(java.lang.String)
+ */
+ public MqttPersistable get(String key) throws MqttPersistenceException {
+ return (MqttPersistable)data.get(key);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.MqttClientPersistence#open(java.lang.String, java.lang.String)
+ */
+ public void open(String clientId, String serverURI) throws MqttPersistenceException {
+ this.data = new Hashtable();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.MqttClientPersistence#put(java.lang.String, org.eclipse.paho.client.mqttv3.MqttPersistable)
+ */
+ public void put(String key, MqttPersistable persistable) throws MqttPersistenceException {
+ data.put(key, persistable);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.MqttClientPersistence#remove(java.lang.String)
+ */
+ public void remove(String key) throws MqttPersistenceException {
+ data.remove(key);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.MqttClientPersistence#clear()
+ */
+ public void clear() throws MqttPersistenceException {
+ data.clear();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.paho.client.mqttv3.MqttClientPersistence#containsKey(java.lang.String)
+ */
+ public boolean containsKey(String key) throws MqttPersistenceException {
+ return data.containsKey(key);
+ }
+}
diff --git a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttDefaultFilePersistence.java b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/persist/MqttDefaultFilePersistence.java
similarity index 81%
rename from org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttDefaultFilePersistence.java
rename to org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/persist/MqttDefaultFilePersistence.java
index 87710419..f45e5aaa 100644
--- a/org.eclipse.paho.client.mqttv3/src/org/eclipse/paho/client/mqttv3/MqttDefaultFilePersistence.java
+++ b/org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/persist/MqttDefaultFilePersistence.java
@@ -1,289 +1,284 @@
-/*
- * Copyright (c) 2009, 2012 IBM Corp.
- *
- * 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
- *
- * Contributors:
- * Dave Locke - initial API and implementation and/or initial documentation
- */
-package org.eclipse.paho.client.mqttv3;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.util.Enumeration;
-import java.util.Vector;
-
-import org.eclipse.paho.client.mqttv3.internal.FileLock;
-import org.eclipse.paho.client.mqttv3.internal.MqttPersistentData;
-
-
-/**
- * An implementation of the {@link MqttClientPersistence} interface that provides
- * file based persistence.
- *
- * A directory is specified when the Persistence object is created. When the persistence
- * is then opened (see {@link #open(String, String)}), a sub-directory is made beneath the base
- * for this particular client ID and connection key. This allows one persistence base directory
- * to be shared by multiple clients.
- *
- * The sub-directory's name is created from a concatenation of the client ID and connection key
- * with any instance of '/', '\\', ':' or ' ' removed.
- */
-public class MqttDefaultFilePersistence implements MqttClientPersistence {
-
- private File dataDir;
- private File clientDir = null;
- private FileLock fileLock = null;
- private static final String MESSAGE_FILE_EXTENSION = ".msg";
- private static final String MESSAGE_BACKUP_FILE_EXTENSION = ".bup";
- private static final String LOCK_FILENAME = ".lck";
-
- private static final FilenameFilter FILE_FILTER = new FilenameFilter() {
- public boolean accept(File dir, String name) { return name.endsWith(MESSAGE_FILE_EXTENSION); }
- };
-
- public MqttDefaultFilePersistence() throws MqttPersistenceException {
- this(System.getProperty("user.dir"));
- }
-
- /**
- * Create an file-based persistent data store within the specified directory.
- * @param directory the directory to use.
- */
- public MqttDefaultFilePersistence(String directory) throws MqttPersistenceException {
- dataDir = new File(directory);
- }
-
- public void open(String clientId, String theConnection) throws MqttPersistenceException {
-
- if (dataDir.exists() && !dataDir.isDirectory()) {
- throw new MqttPersistenceException();
- } else if (!dataDir.exists() ) {
- if (!dataDir.mkdirs()) {
- throw new MqttPersistenceException();
- }
- } else {
- if (!dataDir.canWrite()) {
- throw new MqttPersistenceException();
- }
- }
-
-
- StringBuffer keyBuffer = new StringBuffer();
- for (int i=0;i
+Contains implementations of the MqttClientPersistence interface.
+
+
+An MQTT client needs a persistence mechanism to store messages while they
+are in the process of being delivered. This package contains several
+implementations of the interface. If a persistence class is not
+specified on the constructor to an MQTT client,
+{@link org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence MqttDefaultFilePersistence}
+is used by default.
+
+