Merge tag 'edgent-1.2.0'

[maven-release-plugin] copy for tag edgent-1.2.0
diff --git a/scripts/runhelloedgent.sh b/.editorconfig
old mode 100755
new mode 100644
similarity index 80%
rename from scripts/runhelloedgent.sh
rename to .editorconfig
index f08010f..df7383c
--- a/scripts/runhelloedgent.sh
+++ b/.editorconfig
@@ -1,4 +1,3 @@
-#!/bin/bash
 #
 # Licensed to the Apache Software Foundation (ASF) under one or more
 # contributor license agreements.  See the NOTICE file distributed with
@@ -15,9 +14,18 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-edgent=..
 
-# Runs HelloEdgent
-#
-export CLASSPATH="${edgent}/samples/lib/edgent.samples.topology.jar"
-java org.apache.edgent.samples.topology.HelloEdgent
+[*]
+charset=utf-8
+end_of_line=lf
+insert_final_newline=false
+indent_style=space
+indent_size=4
+
+[{*.yml,*.yaml}]
+indent_style=space
+indent_size=2
+
+[*.xml]
+indent_style=space
+indent_size=2
diff --git a/.gitignore b/.gitignore
index 92778e2..ea7e192 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,13 +1,14 @@
 *.class
 
+# Local developers toolchain
+/toolchains-local.xml
+
+# Exclude the maven-wrapper.jar
+.mvn/wrapper/maven-wrapper.jar
+
 # More generated artifacts
-/target
-/test/svt/lib
-/release-edgent
+target/
 /reports
-/.gradle/
-build/
-/externalJars
 
 # Mobile Tools for Java (J2ME)
 .mtj.tmp/
@@ -22,3 +23,11 @@
 # IntelliJ Idea
 .idea/
 *.iml
+
+# Eclipse
+.classpath
+.project
+.settings/
+
+# Emacs
+*~
\ No newline at end of file
diff --git a/.gradle-wrapper/gradle-wrapper.jar b/.gradle-wrapper/gradle-wrapper.jar
deleted file mode 100644
index ca78035..0000000
--- a/.gradle-wrapper/gradle-wrapper.jar
+++ /dev/null
Binary files differ
diff --git a/.gradle-wrapper/gradle-wrapper.properties b/.gradle-wrapper/gradle-wrapper.properties
deleted file mode 100644
index 501e1c4..0000000
--- a/.gradle-wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-bin.zip
-distributionSha256Sum=c7de3442432253525902f7e8d7eac8b5fd6ce1623f96d76916af6d0e383010fc
diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 0000000..44f8e00
--- /dev/null
+++ b/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,110 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+    /**
+     * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+     */
+    private static final String DEFAULT_DOWNLOAD_URL =
+            "https://repo1.maven.org/maven2/io/takari/maven-wrapper/0.2.1/maven-wrapper-0.2.1.jar";
+
+    /**
+     * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+     * use instead of the default one.
+     */
+    private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+            ".mvn/wrapper/maven-wrapper.properties";
+
+    /**
+     * Path where the maven-wrapper.jar will be saved to.
+     */
+    private static final String MAVEN_WRAPPER_JAR_PATH =
+            ".mvn/wrapper/maven-wrapper.jar";
+
+    /**
+     * Name of the property which should be used to override the default download url for the wrapper.
+     */
+    private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+    public static void main(String args[]) {
+        System.out.println("- Downloader started");
+        File baseDirectory = new File(args[0]);
+        System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+        // If the maven-wrapper.properties exists, read it and check if it contains a custom
+        // wrapperUrl parameter.
+        File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+        String url = DEFAULT_DOWNLOAD_URL;
+        if(mavenWrapperPropertyFile.exists()) {
+            FileInputStream mavenWrapperPropertyFileInputStream = null;
+            try {
+                mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+                Properties mavenWrapperProperties = new Properties();
+                mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+                url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+            } catch (IOException e) {
+                System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+            } finally {
+                try {
+                    if(mavenWrapperPropertyFileInputStream != null) {
+                        mavenWrapperPropertyFileInputStream.close();
+                    }
+                } catch (IOException e) {
+                    // Ignore ...
+                }
+            }
+        }
+        System.out.println("- Downloading from: : " + url);
+
+        File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+        if(!outputFile.getParentFile().exists()) {
+            if(!outputFile.getParentFile().mkdirs()) {
+                System.out.println(
+                        "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+            }
+        }
+        System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+        try {
+            downloadFileFromURL(url, outputFile);
+            System.out.println("Done");
+            System.exit(0);
+        } catch (Throwable e) {
+            System.out.println("- Error downloading");
+            e.printStackTrace();
+            System.exit(1);
+        }
+    }
+
+    private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+        URL website = new URL(urlString);
+        ReadableByteChannel rbc;
+        rbc = Channels.newChannel(website.openStream());
+        FileOutputStream fos = new FileOutputStream(destination);
+        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+        fos.close();
+        rbc.close();
+    }
+
+}
diff --git a/gradle.properties b/.mvn/wrapper/maven-wrapper.properties
similarity index 65%
rename from gradle.properties
rename to .mvn/wrapper/maven-wrapper.properties
index cca1472..7e8f382 100644
--- a/gradle.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -15,13 +15,6 @@
 # specific language governing permissions and limitations
 # under the License.
 
-build_group: org.apache.edgent
-build_name: edgent
-build_version: 1.2.0
-build_vendor: Apache Software Foundation
+distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip
 
-# Minimum required gradle version and version for the wrapper to use.
-# Comment out gradleDistributionSha256Sum to disable validation of
-# a wrapper downloaded gradle distribution.
-gradleVersion = 3.1
-gradleDistributionSha256Sum = c7de3442432253525902f7e8d7eac8b5fd6ce1623f96d76916af6d0e383010fc
+#wrapperUrl=https://repo1.maven.org/maven2/io/takari/maven-wrapper/0.2.1/maven-wrapper-0.2.1.jar
diff --git a/.project b/.project
deleted file mode 100644
index 8a1877a..0000000
--- a/.project
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-    <name>_edgent</name>
-    <comment>Top level project</comment>
-</projectDescription>
diff --git a/.travis.yml b/.travis.yml
index 31171ba..b7d6c8e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,24 +17,15 @@
 
 language: java
 
+env:
+   - MAVEN_OPTS="-Xms512m -Xmx512m"
+
 matrix:
    fast_finish: true
 
 install: true
 
-# avoid uploading the cache after every build (per travis doc)
-before_cache:
-  - rm -f  $HOME/.gradle/caches/modules-2/modules-2.lock
-  - rm -fr $HOME/.gradle/caches/*/plugin-resolution/
-cache:
-  directories:
-    - $HOME/.gradle/caches/
-    - $HOME/.gradle/wrapper/
-
 script:
     - jdk_switcher use oraclejdk8
-    - ./gradlew release -Dedgent.build.ci=true
-    - ./gradlew test --continue -Dedgent.build.ci=true
-    - ./gradlew test7Compile -Dedgent.build.ci=true
-    - jdk_switcher use oraclejdk7
-    - ./gradlew test7Run --continue -Dedgent.build.ci=true 
+    - mvn clean install -Pdistribution -Dedgent.build.ci=true
+#    - mvn -t toolchains-travis.xml -Djava8.home=/usr/lib/jvm/java-8-oracle clean install site:site -Pdistribution,platform-java7,platform-android -Dedgent.build.ci=true
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index 3d84f50..18e5261 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -1,5 +1,32 @@
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+
 The following volunteers have contributed code, documentation, testing and/or support to the Apache Edgent(incubating).
 
+Apache Edgent(incubating) 1.2.0
+---------------------
+Christofer Dutz
+Dale LaBossiere
+John Ament
+Justin Mclean
+Thomas Cristanis
+
 Apache Edgent(incubating) 1.1.0
 ---------------------
 Cazen Lee
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
index feea297..d57cdc1 100644
--- a/DEVELOPMENT.md
+++ b/DEVELOPMENT.md
@@ -1,10 +1,28 @@
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
 ## Development of Apache Edgent
 
 *Apache Edgent is an effort undergoing incubation at The Apache Software Foundation (ASF), sponsored by the Incubator PMC. Incubation is required of all newly accepted projects until a further review indicates that the infrastructure, communications, and decision making process have stabilized in a manner consistent with other successful ASF projects. While incubation status is not necessarily a reflection of the completeness or stability of the code, it does indicate that the project has yet to be fully endorsed by the ASF.*
 
 See [README.md](README.md) for high-level information about Apache Edgent.
 
-This document describes development of Apache Edgent itself, not how to develop Edgent applications.
+This document describes building and the development of Apache Edgent itself, not how to develop Edgent applications.
 
  * See http://edgent.incubator.apache.org/docs/edgent-getting-started for getting started using Edgent
 
@@ -16,106 +34,181 @@
 
 See the [Edgent Wiki](https://cwiki.apache.org/confluence/display/EDGENT) for additional information including Internal and Design notes. 
 
-## Switched from Ant to Gradle
+## Switched from Ant and Gradle to Maven
 
 See the updated _Building_ and _Using Eclipse_ sections below.
-The Ant tooling is no longer functional.
+The Ant and Gradle tooling is no longer functional.
 
 It's recommended that developers of Edgent create a new workspace instead of
-reusing current ant-based Edgent workspaces.
+reusing current gradle-based Edgent workspaces.
 
-## Renamed from Apache Quarks
-Apache Edgent is the new name and the conversion is complete.
+## Branches
 
-Code changes:
+The `develop` branch is used for development.  Jenkins is setup to build this branch and publish internal SNAPSHOT build results to the ASF Nexus SNAPSHOTS Repository (https://repository.apache.org/content/repositories/snapshots).
 
-  * Package names have the prefix "org.apache.edgent"
-  * JAR names have the prefix "edgent"
-
-Users of Edgent will need to update their references to the above.
-It's recommended that developers of Edgent create a new workspace instead of
-reusing their Quarks workspace.
+The `master` branch contains released code. Releases are published to the ASF Nexus Releases Repository (https://repository.apache.org/content/repositories/releases). The Releases repository is automatically mirrored to the Maven Central Repository.
 
 ## Setup
 
 Once you have forked the repository and created your local clone you need to download
 these additional development software tools.
 
-* Java 8 - The development setup assumes Java 8 and Linux.
-* gradle - (https://gradle.org/) only if building from a source release bundle
+* Java 8 - The development setup assumes Java 8
+* Java 7 - *(optional) only required when also building the Java 7 and Android artifacts with `toolchain` support* 
+* Maven - *(optional) (https://maven.apache.org/)*
 
-All Edgent runtime development is done using Java 8.  JARs for Java 7 and Android
-platforms are created as described below.
+Maven is used as build tool. Currently there are two options:
 
-## Building a Binary Release Bundle
+1. Using the maven-wrapper (the `mvnw` or `mvnw.bat` command - preferred)
+2. Using an installed version of Maven (the `mvn` command)
 
-Building from a source release bundle (lacking a `./gradlew`) requires
-performing a one-time bootstrap step using an installed version of gradle:
-``` sh
-$ gradle          # one time gradle build bootstrap setup.
+The maven-wrapper will automatically download and install the correct Maven version and use that. Besides this, there is no difference between using the `mvn` and `mvnw` command.
+
+You may also use a maven-integrated IDE for Edgent development.  e.g., see the _Using Eclipse_ section below.
+
+All Edgent runtime development is done using Java 8. JARs for Java 7 and Android platforms are created by back-porting the compiled Java 8 code using a tool called `retrolambda`. More details on this below.
+
+Per default the build will use Java 8 to perform the build of the Java 7 and Android modules. In order to reliably __test__ the Java 7 modules on a real Java 7 Runtime, we defined an additional profile `toolchain` which lets Maven run the tests in the Java 7 Modules with a real Java 7 Runtime.
+
+In preparation for testing the Java 7 and Android modules with enabled `toolchain` support, edit or create `~/.m2/toolchains.xml`:
+
+``` toolchains.xml
+<?xml version="1.0" encoding="UTF8"?>
+<toolchains>
+  <toolchain>
+    <type>jdk</type>
+    <provides>
+      <version>1.8</version>
+      <vendor>oracle</vendor>
+    </provides>
+    <configuration>
+      <jdkHome>{path to the Java 8 SDK}</jdkHome>
+    </configuration>
+  </toolchain>
+  <toolchain>
+    <type>jdk</type>
+    <provides>
+      <version>1.7</version>
+      <vendor>oracle</vendor>
+    </provides>
+    <configuration>
+      <jdkHome>{path to the Java 7 SDK}</jdkHome>
+    </configuration>
+  </toolchain>
+<toolchains>
 ```
 
-Building an Edgent binary release bundle:
+Set the jdkHome values appropriately for your system.
+e.g., on an OSX system:
 ``` sh
-$ ./gradlew release
+  j8 jdkHome:  /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home
+  j7 jdkHome:  /Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home
 ```
 
-The build reports the location of the binary distribution bundle that can then
-be unpacked and used in building applications.
 
-See [Getting Started](https://edgent.apache.org/docs/edgent-getting-started)
-for information on using the binary release bundle.
+## Building Edgent For Edgent Development
 
-## Building for Edgent Runtime Development
+Any pull request is expected to maintain the build success of `mvn package`.
 
-The primary build process is using [Gradle](https://gradle.org/),
-any pull request is expected to maintain the build success of `clean, assemble, test`.
-
-The Gradle wrapper `edgent/{gradlew,gradlew.bat}` should be used.
-The wrapper ensures the appropriate version of Gradle is used and it
-will automatically download it if needed, e.g.:
+To build and test for Java 8
 ``` sh
-$ ./gradlew --version
-$ ./gradlew clean build
+$ ./mvnw clean package   # -DskipTests to omit tests
 ```
 
-The Gradle tooling:
+To build __and properly test__ the Edgent Java 7 and Android platform jars
+requires an installed Java 7 JRE and the `toolchains.xml` setup above, then
+``` sh
+$ ./mvnw clean package -Djava8.home=$JAVA_HOME -Ptoolchain,platform-java7,platform-android
+```
 
-- Creates release images under `<edgent>/build/release-edgent`
-- Creates build artifacts under `<edgent>/build/distributions` and `<edgent>/<project>/build`
+### Documentation of all defined Maven profiles
+
+A set of Maven `profiles` have been created to control which parts should be built. 
+The default profile only builds and tests the Java 8 versions of all modules and doesn't assemble a binary distribution as usually Maven builds don't require such a step. 
+It also doesn't build the Java 7 or Android modules either.
+
+Edgent currently comes with these profiles:
+
+- `apache-release`: Builds source release bundle under `target` 
+- `distribution`: Builds one binary distribution bundle under `distribution/target` for Java 8. If the java 7 and android profiles are enabled too, for each of these an additional binary distribution is created.
+- `platform-java7`: Builds Java 7 versions of all Edgent modules and runs the tests.
+- `platform-android`: Builds Android versions of all Edgent modules that are compatible with Android (See [JAVA_SUPPORT.md](JAVA_SUPPORT.md).
+- `toolchain`: Runs the tests in the Java 7 and Android modules using a Java 7 runtime instead of Java 8 version, which happens if this profile is not enabled. 
+
+As the Android modules are based on the Java 7 versions, when building the `platform-android` profile, the `platform-java7` profile is required to be enabled too, or the build will fail. 
+
+For a not quite two hour introduction into Maven please feel free to watch this video we created for another Apache project: https://vimeo.com/167857327
+
+## Building Edgent For Using Edgent
+
+__Note:__ Apache Edgent releases include convenience binaries. Use of them
+is covered in [samples/APPLICATION_DEVELOPMENT.md](https://github.com/apache/incubator-edgent-samples/blob/develop/APPLICATION_DEVELOPMENT.md).
+
+If instead you want to build Edgent for your use there are two different use-cases:
+
+1.  Build Edgent for use with a Maven project. 
+2.  Build Edgent for use with non-Maven integrated tooling.  
+
+### Building Edgent for use with Maven
+
+Build, test, and install the Edgent Java8 jars in the local maven repository
+``` sh
+$ ./mvnw clean install  # -DskipTests to skip tests
+```
+
+To also build, but not test, the Edgent Java 7 and Android platform jars:
+``` sh
+$ ./mvnw clean install -DskipTests -Pplatform-java7,platform-android
+```
+
+To build __and properly test__ the Edgent Java 7 and Android platform jars
+requires an installed Java 7 JRE and the `toolchains.xml` setup above, then
+``` sh
+$ ./mvnw clean install -Djava8.home=$JAVA_HOME -Ptoolchain,platform-java7,platform-android
+```
 
 
-The top-level Gradle file is `edgent/build.gradle` and it contains several
-unique tasks:
+### Building Edgent for NOT using it with Maven
 
-* `wrapper` (default) : one-time bootstrap processing for use when building from a source release bundle 
-* `assemble` : Build all code and Javadoc into `build\distributions`. The build will fail on any code error or Javadoc warning or error.
-* `all` : Synonym for `assemble`
-* `build` : Essentially like "assemble test reports"
-* `clean` : Clean the project
-* `test` : Run the JUnit tests, if any test fails the test run stops.  Use `--continue` to not stop on the first failure.
-  * Use a project test task and optionally the `--tests` option to run a subset of the tests.  Multiple `--tests` options may be specified following each test task.
-    * `$ ./gradlew <project>:test`
-    * `$ ./gradlew <project>:test --tests '*.SomeTest'`
-    * `$ ./gradlew <project>:test --tests '*.SomeTest.someMethod'`
-  * Use the `cleanTest` task to force rerunning a previously successful test task (without forcing a rerun of all of the task's dependencies):
-    * `$ ./gradlew [<project>:]cleanTest [<project>:]test`
-* `reports` : Generate JUnit and Code Coverage reports in `build\distributions\reports`. Use after executing the `test` target.
-  * `reports\tests\overview-summary.html` - JUnit test report
-  * `reports\coverage\index.html` - Code coverage report
-* `release` : Build release bundles in `build/release-edgent`, that includes subsets of the Edgent JARs that run on Java 7 (`build/distributions/java7`) and Android (`build/distributions/android`). By default, SNAPSHOT bundles are created.  Specify `-Dedgent.snapshotId=""` to create bundles for a formal release.
-* `rat` : run the Apache Release Analysis Tool (license checking).
-* `signAll` : Sign the release bundles in `build/release-edgent` (first run `release`).  You will be promoted for your PGP code signing key's ID, the location of the keyring file, and the secret key password.  Default response values may be set with environment variables:
-  * `GPG_ID` - the code signing key's ID (e.g., D0F56CAD)
-  * `GPG_SECRING` - path to the secret key's keyring file
+Build Edgent as described above to populate the local maven repository.
+Then see [samples/APPLICATION_DEVELOPMENT.md](https://github.com/apache/incubator-edgent-samples/blob/develop/APPLICATION_DEVELOPMENT.md)
+for information about the `get-edgent-jars.sh` script.
 
-The build process has been tested on Linux and macOS.
+An alternative to using the `get-edgent-jars.sh` script is to
+create a binary distribution bundle consisting of the Edgent runtime
+jars and their external dependencies.
 
-To build on Windows probably needs some changes, please get involved and contribute them!
+Build a binary distribution bundle for Java 8
+``` sh
+$ ./mvnw clean package -DskipTests -Pdistribution
+```
+
+The distribution bundle is created under `distribution/target`.
+The `libs` directory inside the bundle contains the Edgent jars and 
+the `ext` directory contains third party dependencies the Edgent jars require.
+
+<b>NOTE: each third party dependency in the bundle comes with its 
+own copyright and license terms which you implicitly accept when using
+a distribution bundle.  See the README file in the bundle for more
+information.</b>
+
+You will need to manually setup the CLASSPATH for the build tooling that you're
+using to develop your Edgent application.
+
+
+To also build the Edgent Java 7 and Android platform jars:
+``` sh
+$ ./mvnw clean package -DskipTests -Pdistribution -Pplatform-java7,platform-android
+```
+The distribution bundles will be in `platforms/java7/distribution/target`
+and `platforms/android/distribution/target` respectively.
+ 
 
 ## Continuous Integration
 
-When a pull request is opened on the GitHub mirror site, the Travis CI service runs a full build.
+### Travis CI
+
+When a pull request is opened on the GitHub mirror site, the Travis CI service runs a full build of the java8 modules.
 
 The latest build status for the project's branches can be seen at: https://travis-ci.org/apache/incubator-edgent/branches
 
@@ -123,9 +216,23 @@
 It includes:
 
 * Building the project
-* Testing on Java 8 and Java 7
+* Testing on Java 8
   - Not all tests may be run, some tests are skipped due to timing issues or if excessive setup is required.
 
+In an attempt to more generally desentize tmo failures
+when the system property edgent.build.ci=true is set
+some runtime and test infrastructure components will 
+bump the normal tmo value (e.g., 10x).
+This affects travis and Jenkins runs (both set edgent.build.ci).
+See:
+
+    * TStreamTest.waitForCompletion()
+    * AbstractTester.complete()
+    * Execuatble.invokeAction()
+    * generally search for uses of edgent.build.ci
+        * maybe remove other test specific uses of it in light of the general change 
+
+The following may now best be avoided:
 If your test randomly fails because, for example, it depends on publicly available test services,
 or is timing dependent, and if timing variances on the Travis CI servers may make it more likely
 for your tests to fail, you may disable the test from being executed on Travis CI using the
@@ -141,43 +248,199 @@
 
 Closing and reopening a pull request will kick off a new build against the pull request.
 
-## Java 7 and Android
+### Jenkins, SonarQube
+
+In addition to Travis CI running the quick tests with only the Java8 modules, we have also setup additional build-jobs at the Apaches Jenkins instance at https://builds.apache.org/view/E-G/view/Edgent/
+
+This build also automatically runs on every commit, but in contrast to the Travis build, it also builds and tests the Java7 and Android modules using the toolchain profile.
+
+This is also the build which produces and deploys the Maven artifacts that are published to the Apache Maven repository at https://repository.apache.org/
+
+As an additional quality assurance tool, this build also runs a SonarQube analysis who's results are available at Apaches SonarQube instance at https://builds.apache.org/analysis/overview?id=45154
+
+Heads up: the (Jenkins?) test failure reporting tooling seems to get confused
+in the face of the same named tests being run for multiple platforms.
+Generally you will see each test file listed twice: once for Java8 and once
+for Java7.  In the html results it seems impossible to tell which platform
+a failed test (or passed test for that matter) applies to.  Even though the
+html links for the two tests differ (e.g., the 2nd one has a "_2" at the end
+of the URL), a failed test's page shows the passed test's page.  My approach
+to investigating failures is to open the "View as plain text" page and
+then use the browser's search feature to look for the test name of interest
+to locate the output for the failing test.  ugh.
+
+## Java 7 and Android Build Tooling
+
 Java 7 and Android target platforms are supported through use of
-retrolambda to convert Edgent Java8 JARs to Java7 JARs.
+retrolambda to convert Edgent Java 8 JARs to Java 7 JARs. In order
+to make it easy to address easily, for each Java 8 module a matching
+Java 7 version is located inside the `<edgent>/platforms/java7`
+directory. For Android only those counterparts exist which are generally
+supported on Android.
 
-Building a release (`./gradlew release`) produces three sets of JARs under
+In general all Java 7 modules differ from the ordinary Java 8 versions 
+as these modules don't contain any source code or resources. They are
+all built by unpacking the Java 8 jars content into the current modules 
+target directory. So the output is effectively located exactly in the 
+same location it would have when normally compiling the Java 8 version. 
+There the retrolambda plugin is executed to convert the existing class 
+files into ones compatible with Java 7.
 
-* build/distributions/java8 - Java 8 SE
-* build/distributions/java7 - Java 7 SE
-* build/distributions/android - Android
+The Android versions are even simpler, as all they do is unpack the Java 7
+versions and re-pack the content with the android groupId. All except the
+two modules which are currently only available on Android 
+(located in the `<edgent>/platforms/android/android` directory). These 
+modules are built up similar to the Java 8 versions, but they also contain
+the retrolambda plugin execution. While it would have been possible to 
+treat these modules as Java 7, for the sake of an equal coding experience
+it was decided to make it possible to write the same type of code for all
+modules.
 
-See [JAVA_SUPPORT.md](JAVA_SUPPORT.md) for which Edgent capabilities / JARs are supported
-for each environment.
+An Android module's dependency on the Java 7 version makes the requirement
+obvious, that in order to build the Android versions, the Java 7 versions
+have to be built too.
 
-### Adding Edgent Runtime JARs to Java 7 & Android
+See [JAVA_SUPPORT.md](JAVA_SUPPORT.md) for which Edgent capabilities / JARs 
+are supported for each environment.
 
-The Gradle tooling uses some Ant tooling to create the Java 7 and Android platform JARs.
-
-Java 7 Edgent runtime JARs are created using `platform/java7/build.xml`. Adding a JAR just requires:
-
-* Adding it to target `retro7.edgent` - Copy entry for an existing JAR.
-* Adding any tests for it to targets `test7.setup` and `test7.run` - Copy entry for an existing JAR.
-
-Any Java 7 JAR is automatically included in Android unless it is explictly excluded in `platform/android/build.xml`.
+Also see _Coding Conventions_ below.
 
 ## Test reports
 
-Running the `reports` target produces two reports:
+The typical maven build contains two phases of unit-tests.
+The Unit-Test phase which is executed by the surefire maven plugin
+and the Integration-Test phase, which is executed by the failsafe
+maven plugin.
 
-* `builds/distributions/reports/tests/index.html` - JUnit test report
-* `builds/distributions/reports/coverage/index.html` - Code coverage report.
+When running a normal maven `package` build, only the unit-test phase is executed.
+When running `verify` or above (`install`, `deploy`, etc.) the integration
+tests are also executed.
+
+Each Maven plugin produces output to different directories:
+
+* `<module>/target/surefire-reports` - JUnit unit-test reports
+* `<module>/target/failsafe-reports` - JUnit integration-test reports
+
+In addition to running the unit tests, coverage data is automatically 
+collected by the `jacoco-maven-plugin`, which is configured to store
+its data in `<module>/target/coverage-reports` in files called 
+`jacoco-ut.exec` and `jacoco-it.exec`.
+
+Even if at least the surfire and failsafe output is generated in a human
+readable txt and xml form, the jacoco output is intended on being used 
+by tools. SonarQube is for example able to interpret this information 
+In order to generate nicely formatted html reports, please have a look
+at the following `Site generation` chapter.
+
+## Site generation
+
+Maven has 3 built in lifecycles:
+
+* clean - For cleaning up (effectively simply deleting the output folder)
+* default - For building, testing, deploying the code
+* site - For generating, documentation, reports, ...
+
+If the human readable version of all of these should be generated, all needed
+to do this, is to append a simple `site:site` at the end of the maven command.
+
+```sh
+./mvnw -Pdistribution,platform-java7,platform-android clean verify site:site
+```
+Each modules `<module>/target/site` directory will then contain the generated 
+Module documentation.
+
+## More Build Tooling Miscellenea
+
+There is a lot of surface area to the maven build tooling.  The following
+information may help to better understand it.
+
+* `pom.xml/maven-surefile-plugin` - unit test execution
+* `pom.xml/maven-failsafe-plugin` - integration test execution
+* `pom.xml/jacoco-maven-plugin` - jacoco code coverage reports
+* `pom.xml/animal-sniffer-maven-plugin` - retrolambda results checker
+* `pom.xml/org.codehaus.sonar-plugins` - SonarQube code quality reports
+* `pom.xml/maven-javadoc-plugin` - javadoc generation and the config
+  for all of the "grouping" control.
+* `pom.xml/apache-rat-plugin` - builds automatically run Apache RAT
+  (Release Audit Tool) for checking for appropriate content.  
+  The build fails if the checking fails.
+  See configuration info for controlling excluded artifacts.
+* `pom.xml/maven-assembly-plugin` - used in a couple places for configuring
+  and generating "assemblies" => source release bundle, distribution bundles  
+* `pom.xml/maven-site-plugin` - things related to website generation that includes
+   a number of interesting things, including html reports from various things
+   above plus aggregated javadoc.  How / if this untimately relates to the
+   public website is TDB.
+* `platforms/java7/pom.xml/retrolambda-maven-plugin` 
+   and `platforms/android/android/pom.xml/retrolambda-maven-plugin` - where
+   retrolambda is enabled
+* As mentioned earlier the current scheme for generating Java7 and Android
+  Edgent jars, is achieved by replicating the java8 project structure in
+  platforms/{java7,android}.  <b>Manual synchronization of the corresponding
+  info in the alternate platform poms and other configuration files
+  is required.</b>
+* LICENSE and NOTICE: it's a requirement that released bundles
+  (source release bundle, released jars) contain accurate LICENSE, NOTICE
+  and DISCLAIMER files.  Some of the Edgent projects contain code that
+  that was contributed by IBM, some of the generated jars/war bundle
+  external components.  The build tooling is configured to automatically
+  include standard ALv2.0 LICENSE and NOTICE files in the jars.
+  The non-default cases are handled in a variety of ways: 
+    * `pom.xml/maven-remote-resources-plugin` config plays a role in all of this
+    * `src/main/appended-resources` - contains copies of license text for
+      artifacts that are bundled in Edgent bundles.  For the most part
+      this means the Edgent Console jar/war.  These are incorporated
+      into a jar by including a declaration like the following in the
+      project's pom:
+``` xml
+        <resource>
+          <directory>${project.basedir}/../../src/main/appended-resources/licenses</directory>
+          <targetPath>${project.build.directory}/${project.artifactId}-${project.version}/META-INF/licenses</targetPath>
+        </resource>
+```
+    * `src/main/ibm-remote-resources` - contains the NOTICE fragment for
+      projects containing IBM contributed code.  Applicable project's
+      define the following in their pom to ensure a correct
+      NOTICE is included in the project's jar:
+``` xml
+        <properties>
+          <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+        </properties>
+```
+    * `edgent-console-servlets:war` contains bundled code (downloaded and
+      incorporated by the build tooling).  It includes the bundled code's
+      license text as described above.  Its own LICENSE and NOTICE is
+      copied from the respective files in its `src/main/remote-resources/META-INF`.
+      <b>There are copies of those in under the java7 platform as well.</b>
+    * `edgent-console-server:jar` bundles the console-servlets war and as such
+      requires the same LICENSE/NOTICE/licenses treatment as the servlets war.
+      <b>There are copies of its LICENSE/NOTICE in its  `src/main/remote-resources/META-INF`.
+      There are copies of those in under the java7 platform as well.</b>
+* source-release bundle
+    * `src/assembly/source-release.xml` - configuration information
+      controlling source-release bundle and distribution bundle names,
+      included/excluded files, etc.
+* distribution bundles:
+  Each platform has a "distribution" project.
+  We don't release these bundles hence they aren't obligated to 
+  strictly conform to the ASF LICENSE/NOTICE file requirements.
+  That said, much of the same information is provided via an
+  automatically generated DEPENDENCIES file in the bundle.
+  <b>There are copies of the following information in the java7 and android platforms.</b>
+  Related, `samples/get-edgent-jars-project` uses the same scheme and has
+  its own copies of the files.
+  * the name of the bundle is inherited from the source-release bundle's
+    configuration file noted above.
+  * src/assembly/distribution.xml - additional configuration info
+  * src/main/resources/README - source of the file in the bundle
+    
 
 ## Testing the Kafka Connector
 
 The kafka connector tests aren't run by default as the connector must
 connect to a running Kafka/Zookeeper config.
 
-There are apparently ways to embedd Kafka and Zookeeper for testing purposes but
+There are apparently ways to embed Kafka and Zookeeper for testing purposes but
 we're not there yet. Contributions welcome!
 
 Setting up the servers is easy.
@@ -186,89 +449,16 @@
 Once kafka/zookeeper are running you can run the tests and samples:
 ```sh
 #### run the kafka tests
-./gradlew connectors:kafka:test --tests '*.*Manual'
+./mvnw -pl connectors/kafka test '-Dtest=**/*Manual'
 
 #### run the sample
-cd java8/scripts/connectors/kafka
+(cd samples; ./mvnw package -DskipTests)  # build if not already done
+cd samples/scripts/connectors/kafka
 cat README
 ./runkafkasample.sh sub
 ./runkafkasample.sh pub
 ```
 
-## Testing the JDBC Connector
-
-The JDBC connector tests are written to run against Apache Derby 
-as the backing dbms and the derby jar needs to be on the classpath.
-The tests are skipped if derby can't be loaded.
-The test harness adds $DERBY_HOME/db/lib/derby.jar to the classpath.
-See [JdbcStreamsTest](connectors/jdbc/src/test/java/org/apache/edgent/test/connectors/jdbc/JdbcStreamsTest.java) 
-for more info but the following should suffice:
-
-```sh
-export DERBY_HOME=$JAVA_HOME/db
-
-#### if JAVA_HOME isn't set - e.g., on OSX...
-export DERBY_HOME=`/usr/libexec/java_home`/db
-```
-
-Once DERBY_HOME is set the tests and samples can be run as follows
-```sh
-#### run the jdbc tests
-./gradlew connectors:jdbc:test
-
-#### run the sample
-cd java8/scripts/connectors/jdbc
-cat README
-./runjdbcsample.sh writer
-./runjdbcsample.sh reader
-```
-
-## Testing Edgent with Java7
-
-All of the standard build system _tasks_ above must be run with
-`JAVA_HOME` set to use a Java8 VM.
-
-As noted above, the `release` task includes generation of Java7
-compatible versions of the Edgent JARs. After the release task has been run,
-Edgent may be tested in a Java7 context using some special _test7_ tasks.
-
-See [JAVA_SUPPORT](JAVA_SUPPORT.md) for information about what
-Edgent features are supported in the different environments.
-
-``` sh
- # pave the way for useful report generation at the end
-$ ./gradlew cleanTest
-$ ./gradlew reports
-
- # run with JAVA_HOME/PATH set for Java8
-$ ./gradlew test7Compile  # compile the Edgent tests to operate in a Java7 environment
-
-$ sh   # muck with EVs for Java7 in a subshell
-  $ export JAVA_HOME=`/usr/libexec/java_home -v 1.7`   # on OSX
-  $ export PATH=$JAVA_HOME/bin:$PATH
-  $ ./gradlew test7Run      # run the tests with a Java7 VM
-  $ exit
-
- # run with JAVA_HOME set for Java8
-$ ./gradlew test7Reports  # generate the JUnit and coverage reports
-```
-
-## Publish to Maven Repository
-
-Initial support for publishing to a local Maven repository has been added.
-Use the following to do the publish.
-
-``` sh
-$ ./gradlew publishToMavenLocal
-```
-
-The component JARs / WARs are published as well as their sources.
-The published groupId is `org.apache.edgent`. The artifactIds match the
-names of the JARs in the target-dir / release tgz.
-
-For example: `org.apache.edgent:edgent.api.topology:0.4.0`
-
-
 ## Code Layout
 
 The code is broken into a number of projects and modules within those projects defined by directories under `edgent`.
@@ -294,10 +484,11 @@
 * `analytics` - Analytics for use by Edgent applications.
 * `utils` - Optional utilities for Edgent applications.
 * `console` - Development console that allows visualization of the streams within an Edgent application during development.
-* `samples` - Sample applications, from Hello World to some sensor simulation applications.
 * `android` - Code specific to Android.
 * `test` - SVT
 
+Samples are located at https://github.com/apache/incubator-edgent-samples
+
 ## Coding Conventions
 
 Placeholder: see [EDGENT-23](https://issues.apache.org/jira/browse/EDGENT-23)
@@ -308,16 +499,11 @@
 * Don't use wildcard imports
 * Don't deliver code with warnings (e.g., unused imports)
 * All source files, scripts, etc must have the standard Apache License header
-  * run the `rat` build task to check license headers
-* Per ASF policy, released source bundles must not contain binaries (e.g., .class, .jar)
+  * the build tooling automatically runs `rat` to check license headers
+    and fails if non-conforming files are encountered.
+* __Per ASF policy, released source bundles must not contain binaries (e.g., .class, .jar)__
 * Per ASF policy, release source and binary bundle LICENSE and NOTICE files must be accurate and up to date, and only bundled 3rd party dependencies whose license meets the ASF licensing requirements can be included. 
 
-## Logging
-
-[SLF4J](http://www.slf4j.org) is used for logging and tracing.
-
-Search the code for org.slf4j.LoggerFactory to see a sample of its use.
-
 ### Use of Java 8 features
 Edgent's primary development environment is Java 8, to take advantage of lambda expressions
 since Edgent's primary API is a functional one.
@@ -325,20 +511,24 @@
 **However**, in order to support Android (and Java 7), other features of Java 8 are not used in the core
 code. Lambdas are translated into Java 7 compatible classes using retrolambda.
 
-Thus:
+Thus for core code and tests that needs to run on Android/Java7:
 
-* For core code that needs to run on Android:
    * The only Java 8 feature that can be used is lambda expressions
-   * JMX functionality cannot be used.
-* For test code that tests core code that runs on Android:
-   * Java 8 lambda expressions can be used
-   * Java 8 default & static interface methods
+   * Java 8 default & static interface methods cannot be used
    * Java 8 new classes and methods cannot be used
+   * Android only: JMX functionality cannot be used
 
-In general, most code is expected to work on Android (but might not yet) with the exception:
+In general, most code is expected to work on Android (but might not yet)
+with the exception of these excluded features:
 
-* Functionality aimed at the developer environment, such as console and development provider
-* Any JMX related code
+   * Functionality aimed at the developer environment, such as console and development provider
+   * Any JMX related code
+
+### Logging
+
+[SLF4J](http://www.slf4j.org) is used for logging and tracing.
+
+Search the code for org.slf4j.LoggerFactory to see a sample of its use.
 
 ## The ASF / GitHub Integration
 
@@ -385,42 +575,57 @@
 
 ## Using Eclipse
 
-The Edgent Git repository contains Eclipse project definitions for the
-top-level directories that contain code, such as api, runtime, connectors.
+The Edgent Git repository, or source release bundle, contains 
+Maven project definitions for the various components of Edgent
+such as api, runtime, connectors.
 
-**The Git repository does not include the 3rd party JARs that Edgent depends on,
-and Eclipse builds of Edgent projects will fail until a Gradle task is run
-to make them available in your workspace.  See the steps below.**
+Once you import the Maven projects into your workspace,
+builds and JUnit testing of Edgent in Eclipse use the 
+same artifacts as the Maven command line tooling. Like
+the command line tooling, the jars for dependent projects
+are automatically downloaded to the local maven repository
+and used.
 
-Using the Eclipse Git Team Provider plugin allows you to import these projects
-into your Eclipse workspace directly from your fork.
+If you want to use Eclipse to clone your fork, use the Eclipse Git Team Provider plugin
 
 1. From the *File* menu, select *Import...*
 2. From the *Git* folder, select *Projects from Git* and click *Next*
-3. In the *Select Repository Source* window, select one of the following:
-  - *Existing local repository* if you have already cloned the project to your machine. Click *Next*.
-    + Click *Add* and browse to the local Git repository directory
-    + Select the checkbox next to the repository and click *Finish*
-    + In the *Select a Git repository* window, choose *incubator-edgent* and click *Next*
-  - *Clone URI* to clone the remote repository. Click *Next*.
+3. Select *Clone URI* to clone the remote repository. Click *Next*.
     + In the *Location* section, enter the URI of your fork in the *URI* field (e.g., `git@github.com:<username>/incubator-edgent.git`). The other fields will be populated automatically. Click *Next*. If required, enter your passphrase.
     + In the *Source Git Repository* window, select the branch (usually `master`) and click *Next*
-    + Specify the directory where your local clone will be stored and click *Next*. Note: You can build and run tests using the Gradle targets in this directory.
-4. In the *Select a wizard to use for importing projects* window, choose *Import existing Eclipse projects* and click *Next*
-5. In the *Import Projects* window, ensure that the *Search for nested projects* checkbox is selected. Click *Finish* to bring in all the Edgent projects. **Expect build failures until you...**
-6. Run the following Gradle task in your clone directory to make all of the dependant 3rd party JARs available to Eclipse. When the command finishes, refresh your Eclipse workspace (*File* > *Refresh*) so that it rebuilds the projects.
-``` sh
-$ ./gradlew setupExternalJars
-```
+    + Specify the directory where your local clone will be stored and click *Next*. The repository will be cloned. Note: You can build and run tests using Maven in this directory.
+4. In the *Select a wizard to use for importing projects* window, click *Cancel*.  Then follow the steps below to import the Maven projects.
 
-The `_edgent` project exists to make the top-level artifacts such as
-`build.gradle` manageable via Eclipse.  Unfortunately, folders for the
-other projects (e.g., `api`) also show up in the `_edgent` folder, and
-are best ignored.
 
-Builds and JUnit testing of Edgent in Eclipse are independent from the artifacts
-generated by the Gradle build tooling.  Neither environment is affected by
-the other. This is not ideal, but it's where things are at at this time.
-Both sets of tooling can be, and typically are, used in the same workspace.
+Once you have cloned the Git repository to your machine or are working from an unpacked source release bundle, import the Maven projects into your workspace
 
-Note: Specifics may change depending on your version of Eclipse or the Eclipse Git Team Provider.
+1. From the *File* menu, select *Import...*
+2. From the *Maven* folder, select *Existing Maven Projects* and click *Next*
+  + browse to the root of the clone or source release directory and select it.  A hierarchy of projects / pom.xml files will be listed and all selected. 
+  + Verify the *Add project(s) to working set* checkbox is checked
+  + Click *Finish*.  Eclipse starts the import process and builds the workspace.  Be patient, it may take a minute or so.
+
+Top-level artifacts such as `README.md` are available under the `edgent-parent` project.
+
+Note: Specifics may change depending on your version of Eclipse or the Eclipse Maven or Git Team Provider.
+
+### Markdown Text Editor
+
+The ALv2 license headers in various markdown files (e.g., README.md)
+seem to confuse the Eclipse `wikitext` editor resulting in blank contents
+in its preview panel.  This situation may be improved by installing 
+the `Markdown text editor` from the Eclipse marketplace and adjusting
+Eclipse's file associations accordingly.
+
+## Renamed from Apache Quarks
+Apache Edgent is the new name and the conversion is complete.
+
+Code changes:
+
+  * Package names have the prefix "org.apache.edgent"
+  * JAR names have the prefix "edgent"
+
+Users of Edgent will need to update their references to the above.
+It's recommended that developers of Edgent create a new workspace instead of
+reusing their Quarks workspace.
+
diff --git a/DISCLAIMER b/DISCLAIMER
index 8bcc295..58cf3ad 100644
--- a/DISCLAIMER
+++ b/DISCLAIMER
@@ -1,8 +1,10 @@
+Apache Edgent is an effort undergoing incubation at the Apache Software
+Foundation (ASF), sponsored by the Apache Incubator PMC.
 
-Apache Edgent is an effort undergoing incubation at The Apache Software Foundation (ASF), 
-sponsored by the Incubator PMC. Incubation is required of all newly accepted 
-projects until a further review indicates that the infrastructure, communications, 
-and decision making process have stabilized in a manner consistent with other 
-successful ASF projects. While incubation status is not necessarily a reflection
-of the completeness or stability of the code, it does indicate that the project
-has yet to be fully endorsed by the ASF.
+Incubation is required of all newly accepted projects until a further review
+indicates that the infrastructure, communications, and decision making process
+have stabilized in a manner consistent with other successful ASF projects.
+
+While incubation status is not necessarily a reflection of the completeness
+or stability of the code, it does indicate that the project has yet to be
+fully endorsed by the ASF.
diff --git a/JAVA_SUPPORT.md b/JAVA_SUPPORT.md
index ec6a221..5a3bb44 100644
--- a/JAVA_SUPPORT.md
+++ b/JAVA_SUPPORT.md
@@ -1,110 +1,173 @@
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
 # Edgent Java support
 
 The Edgent runtime is supported on all Java 8 SE, Java 7 SE, and Android
 platforms with the exceptions noted below.
 
-An Edgent binary release bundle has a directory for each of the target platforms.
-Each target platform directory contains a set of jars for that platform
-* java8 - Java 8 SE
-* java7 - Java 7 SE
-* android - Android
+An Edgent release includes convenience binaries for the Edgent SDK.
+The binaries (jars and wars) are available in the Apache Nexus repository
+and Maven Central.  If you are building the Edgent SDK, the artifacts
+will be installed into the local maven repository.
 
-When building an Edgent release (`./gradlew release`) the above platform
-directories are in `build/distributions`.  See [DEVELOPMENT.md](DEVELOPMENT.md) for more
-information.
+See `samples/README.md` for general information regarding Edgent Application
+development and deployment and tooling to support that.
 
-This page documents which jars are expected to work in each environment.
+SDK binary artifacts are published for each of the supported platform types
+and the artifact's groupId portion of its coordinate indicates which
+platform the artifact is for.  The Edgent jar/war file name is the same
+for each of the platforms.
 
-A blank entry means a jar is currently not supported in that environment
-and no investigation has taken place to see if it can be supported.
+The coordinates have the following form:
+* groupId: `org.apache.edgent[.platform]`
+* artifactId: `edgent-<component>-<subcomponent>`
+
+The [.platform] is as follows:
+* blank/omitted - Java 8 SE
+* `.java7` - Java 7 SE
+* `.android` - Android
+
+For example:
+```
+<!-- for Java8 -->
+<dependency>
+  <groupId>org.apache.edgent</groupId>
+  <artifactId>edgent-providers-direct</artifactId>
+  <version>1.2.0</version>
+</dependency>
+
+<!-- for Java7 -->
+<dependency>
+  <groupId>org.apache.edgent.java7</groupId>
+  <artifactId>edgent-providers-direct</artifactId>
+  <version>1.2.0</version>
+</dependency>
+```
+
+Generally, an Edgent application needs to declare these depedencies:
+* the Edgent Provider used
+* the Edgent Analytics used
+* the Edgent Utils used
+* the Edgent Connectors used
+* an SLF4J implementation for category "runtime"
+
+At application execution time, those same dependencies as well
+as their dependencies (e.g., other "internal" Edgent Core jars, 
+external dependency jars such as for mqtt) must be included on
+the application's classpath.
+
+The `samples/template/pom.xml` is structured to support all of this.
+
+This page documents which Edgent SDK jars are expected to work in each environment.
+
+A blank entry means no investigation has taken place to see if the jar
+and its features are supported in that environment.
 
 ## Core
 
-| Jar                             | Java 8 SE | Java 7 SE | Android | Notes |
-|---------------------------------|-----------|-----------|---------|-------|
-|edgent.api.execution.jar         | yes       | yes       | yes     |       |
-|edgent.api.function.jar          | yes       | yes       | yes     |       |
-|edgent.api.graph.jar             | yes       | yes       | yes     |       |
-|edgent.api.oplet.jar             | yes       | yes       | yes     |       |
-|edgent.api.topology.jar          | yes       | yes       | yes     |       |
-|edgent.api.window.jar            | yes       | yes       | yes     |       |
-|edgent.providers.development.jar | yes       |           | no      | Uses JMX, For development only, not deployment |
-|edgent.providers.direct.jar      | yes       | yes       | yes     |       |
-|edgent.providers.iot.jar         | yes       | yes       | yes     |       |
-|edgent.runtime.appservice.jar    | yes       | yes       | yes     |       |
-|edgent.runtime.etiao.jar         | yes       | yes       | yes     |       |
-|edgent.runtime.jmxcontrol.jar    | yes       | yes       | no      | Uses JMX |
-|edgent.runtime.jobregistry.jar   | yes       |           |         |       |
-|edgent.runtime.jsoncontrol.jar   | yes       | yes       | yes     |       |
-|edgent.spi.graph.jar             | yes       | yes       | yes     |       |
-|edgent.spi.topology.jar          | yes       | yes       | yes     |       |
+| Jar                                   | Java 8 SE | Java 7 SE | Android | Notes |
+|---------------------------------------|-----------|-----------|---------|-------|
+|edgent-api-execution-<ver>.jar         | yes       | yes       | yes     |       |
+|edgent-api-function-<ver>.jar          | yes       | yes       | yes     |       |
+|edgent-api-graph-<ver>.jar             | yes       | yes       | yes     |       |
+|edgent-api-oplet-<ver>.jar             | yes       | yes       | yes     |       |
+|edgent-api-topology-<ver>.jar          | yes       | yes       | yes     |       |
+|edgent-api-window-<ver>.jar            | yes       | yes       | yes     |       |
+|edgent-providers-development-<ver>.jar | yes       | yes       | no      | Uses JMX, For development only, not deployment |
+|edgent-providers-direct-<ver>.jar      | yes       | yes       | yes     |       |
+|edgent-providers-iot-<ver>.jar         | yes       | yes       | yes     |       |
+|edgent-runtime-appservice-<ver>.jar    | yes       | yes       | yes     |       |
+|edgent-runtime-etiao-<ver>.jar         | yes       | yes       | yes     |       |
+|edgent-runtime-jmxcontrol-<ver>.jar    | yes       | yes       | no      | Uses JMX |
+|edgent-runtime-jobregistry-<ver>.jar   | yes       | yes       | yes     |       |
+|edgent-runtime-jsoncontrol-<ver>.jar   | yes       | yes       | yes     |       |
+|edgent-spi-graph-<ver>.jar             | yes       | yes       | yes     |       |
+|edgent-spi-topology-<ver>.jar          | yes       | yes       | yes     |       |
 
 ## Connectors
 
-| Jar                                           | Java 8 SE | Java 7 SE | Android | Notes |
-|-----------------------------------------------|-----------|-----------|---------|-------|
-|edgent.connectors.common.jar                   | yes       | yes       | yes     |       |
-|edgent.connectors.command.jar                  | yes       |           |         |       |
-|edgent.connectors.csv.jar                      | yes       |           |         |       |
-|edgent.connectors.file.jar                     | yes       |           |         |       |
-|edgent.connectors.http.jar                     | yes       | yes       | yes     |       |
-|edgent.connectors.iotf.jar                     | yes       | yes       | yes     |       |
-|edgent.connectors.iot.jar                      | yes       | yes       | yes     |       |
-|edgent.connectors.jdbc.jar                     | yes       |           |         |       |
-|edgent.connectors.kafka.jar                    | yes       |           |         |       |
-|edgent.connectors.mqtt.jar                     | yes       |           |         |       |
-|edgent.connectors.pubsub.jar                   | yes       | yes       | yes     |       |
-|edgent.connectors.serial.jar                   | yes       |           |         |       |
-|edgent.connectors.wsclient.jar                 | yes       |           |         |       |
-|edgent.connectors.wsclient-javax.websocket.jar | yes       |           |         |       |
-|edgent.javax.websocket.jar                     | yes       |           |         |       |
+| Jar                                                 | Java 8 SE | Java 7 SE | Android | Notes |
+|-----------------------------------------------------|-----------|-----------|---------|-------|
+|edgent-connectors-common-<ver>.jar                   | yes       | yes       | yes     |       |
+|edgent-connectors-command-<ver>.jar                  | yes       | yes       |         |       |
+|edgent-connectors-csv-<ver>.jar                      | yes       | yes       |         |       |
+|edgent-connectors-file-<ver>.jar                     | yes       | yes       |         |       |
+|edgent-connectors-http-<ver>.jar                     | yes       | yes       | yes     |       |
+|edgent-connectors-iotf-<ver>.jar                     | yes       | yes       | yes     |       |
+|edgent-connectors-iot-<ver>.jar                      | yes       | yes       | yes     |       |
+|edgent-connectors-jdbc-<ver>.jar                     | yes       | yes       |         |       |
+|edgent-connectors-kafka-<ver>.jar                    | yes       | yes       |         |       |
+|edgent-connectors-mqtt-<ver>.jar                     | yes       | yes       |         |       |
+|edgent-connectors-pubsub-<ver>.jar                   | yes       | yes       | yes     |       |
+|edgent-connectors-serial-<ver>.jar                   | yes       | yes       |         |       |
+|edgent-connectors-websocket-<ver>.jar                | yes       | yes       |         |       |
+|edgent-connectors-websocket-base-<ver>.jar           | yes       | yes       |         |       |
+|edgent-connectors-websocket-jetty-<ver>.jar          | yes       | yes       |         |       |
+|edgent-connectors-websocket-misc-<ver>.jar           | yes       | yes       |         |       |
 
 ## Applications
-| Jar                    | Java 8 SE | Java 7 SE | Android | Notes |
-|------------------------|-----------|-----------|---------|-------|
-|edgent.apps.iot.jar     | yes       | yes       | yes     |       | 
-|edgent.apps.runtime.jar | yes       | yes       | yes     |       | 
+| Jar                          | Java 8 SE | Java 7 SE | Android | Notes |
+|------------------------------|-----------|-----------|---------|-------|
+|edgent-apps-iot-<ver>.jar     | yes       | yes       | yes     |       | 
+|edgent-apps-runtime-<ver>.jar | yes       | yes       | yes     |       | 
 
 ### Analytics
 
-| Jar                         | Java 8 SE | Java 7 SE | Android | Notes |
-|-----------------------------|-----------|-----------|---------|-------|
-|edgent.analytics.math3.jar   | yes       |           |         |       |
-|edgent.analytics.sensors.jar | yes       | yes       | yes     |       |
+| Jar                               | Java 8 SE | Java 7 SE | Android | Notes |
+|-----------------------------------|-----------|-----------|---------|-------|
+|edgent-analytics-math3-<ver>.jar   | yes       | yes       |         |       |
+|edgent-analytics-sensors-<ver>.jar | yes       | yes       | yes     |       |
 
 ### Utilities
 
-| Jar                         | Java 8 SE | Java 7 SE | Android | Notes |
-|-----------------------------|-----------|-----------|---------|-------|
-|edgent.utils.metrics.jar     | yes       |           |         |       |
-|edgent.utils.streamscope.jar | yes       |           |         |       |
+| Jar                               | Java 8 SE | Java 7 SE | Android | Notes |
+|-----------------------------------|-----------|-----------|---------|-------|
+|edgent-utils-metrics-<ver>.jar     | yes       | yes       |         |       |
+|edgent-utils-streamscope-<ver>.jar | yes       | yes       |         |       |
 
 ### Development Console
 
-| Jar                         | Java 8 SE | Java 7 SE | Android | Notes |
-|-----------------------------|-----------|-----------|---------|-------|
-|edgent.console.servlets.jar  | yes       |           | no      | Uses JMX, Servlet |
-|edgent.console.server.jar    | yes       |           | no      | Uses JMX, Servlet |
+| Jar                               | Java 8 SE | Java 7 SE | Android | Notes |
+|-----------------------------------|-----------|-----------|---------|-------|
+|edgent-console-server-<ver>.jar    | yes       | yes       | no      | Uses JMX, Servlet |
+|edgent-console-servlets-<ver>.war  | yes       | yes       | no      | Uses JMX, Servlet |
 
 ### Android
-| Jar                         | Java 8 SE | Java 7 SE | Android | Notes |
-|-----------------------------|-----------|-----------|---------|-------|
-|edgent.android.topology.jar  | no        | no        | yes     |       |
-|edgent.android.hardware.jar  | no        | no        | yes     |       |
+
+| Jar                               | Java 8 SE | Java 7 SE | Android | Notes |
+|-----------------------------------|-----------|-----------|---------|-------|
+|edgent-android-topology-<ver>.jar  | no        | no        | yes     |       |
+|edgent-android-hardware-<ver>.jar  | no        | no        | yes     |       |
 
 
 ## Java API Usage
 
-Documented use of Java packages outside of the Java core packages.
+Documented use of Java packages outside of the Java core packages-
 Java core has a number of definitions, but at least those outside
-of the Java 8 compact1 definition.
+of the Java 8 compact1 definition-
 
 | Feature  | Packages              | Edgent Usage      | Notes |
 |----------|-----------------------|-------------------|-------|
-|JMX       | `java.lang.management, javax.managment*` |     | JMX not supported on Android |
+|JMX       | `java-lang-management, javax-managment*` |     | JMX not supported on Android |
 |JMX       |                       | utils/metrics     | Optional utility methods |
 |JMX       |                       | console/servlets, runtime/jmxcontrol | 
-|Servlet   | `javax.servlet*`      | console/servlets  |
-|Websocket | `javax.websocket`     | connectors/edgent.javax.websocket, connectors/wsclient-javax-websocket, connectors/javax.websocket-client |
-|JDBC      | `java.sql, javax.sql` | connectors/jdbc   |
+|Servlet   | `javax-servlet*`      | console/servlets  |
+|Websocket | `javax-websocket`     | connectors/websocket* |
+|JDBC      | `java-sql, javax-sql` | connectors/jdbc   |
 
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..52bc845
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,122 @@
+#!groovy
+
+/*
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+node('ubuntu') {
+
+    currentBuild.result = "SUCCESS"
+
+    echo 'Building Branch: ' + env.BRANCH_NAME
+
+    // Setup the required environment variables.
+    def mvnHome = "${tool 'Maven 3 (latest)'}"
+    env.JAVA_HOME="${tool 'JDK 1.8 (latest)'}"
+    env.PATH="${env.JAVA_HOME}/bin:${env.PATH}"
+
+    // Make sure the feature branches don't change the SNAPSHOTS in Nexus.
+    def mavenGoal = "install"
+    def mavenLocalRepo = ""
+    if(env.BRANCH_NAME == 'develop') {
+        mavenGoal = "sonar:sonar deploy"
+    } else {
+        mavenLocalRepo = "-Dmaven.repo.local=${env.WORKSPACE}/.repository"
+    }
+    def mavenFailureMode = "" // consider "--fail-at-end"? Odd ordering side effects?
+
+    try {
+        /*stage ('Cleanup') {
+            echo 'Cleaning up the workspace'
+            deleteDir()
+        }*/
+
+        stage ('Checkout') {
+            echo 'Checking out branch ' + env.BRANCH_NAME
+            checkout scm
+        }
+
+        stage ('Clean') {
+            echo 'Cleaning Edgent'
+            sh "${mvnHome}/bin/mvn ${mavenLocalRepo} -Pplatform-android,platform-java7,distribution clean"
+        }
+
+        stage ('Build Edgent') {
+            echo 'Building Edgent'
+            sh "${mvnHome}/bin/mvn ${mavenFailureMode} ${mavenLocalRepo} -Pplatform-android,platform-java7,distribution,toolchain -Djava8.home=${env.JAVA_HOME} -Dedgent.build.ci=true ${mavenGoal}"
+        }
+
+        stage ('Build Site') {
+            if(env.BRANCH_NAME == 'develop') {
+                echo 'Building Site'
+                sh "${mvnHome}/bin/mvn ${mavenLocalRepo} site site:stage"
+            } else {
+                echo 'Building Site (skipped for non develop branch)'
+            }
+        }
+
+/* ========================== TODO figure out what to do with samples now in a separate repo
+        stage ('Build Samples') {
+            echo 'Building samples'
+            sh "cd samples; ${mvnHome}/bin/mvn ${mavenFailureMode} ${mavenLocalRepo} clean package"
+            sh "cd samples/topology; ./run-sample.sh HelloEdgent"
+            sh "cd samples; ${mvnHome}/bin/mvn ${mavenFailureMode} ${mavenLocalRepo} -Pplatform-java7 clean package"
+            sh "cd samples/topology; ./run-sample.sh HelloEdgent"
+        }
+
+        stage ('Build Templates') {
+            echo 'Building templates'
+            sh "cd samples/template; ${mvnHome}/bin/mvn ${mavenFailureMode} ${mavenLocalRepo} clean package; ./app-run.sh"
+            sh "cd samples/template; ${mvnHome}/bin/mvn ${mavenFailureMode} ${mavenLocalRepo} -Pplatform-java7 clean package; ./app-run.sh"
+            sh "cd samples/template; ${mvnHome}/bin/mvn ${mavenFailureMode} ${mavenLocalRepo} -Pplatform-android clean package; ./app-run.sh"
+        }
+========================== */
+
+        /* There seems to be a problem with this (Here the output of the build log):
+
+        Verifying get-edgent-jars
+        [Pipeline] sh
+        [edgent-pipeline_develop-JN4DHO6BQV4SCTGBDJEOL4ZIC6T36DGONHH3VGS4DCDBO6UXH4MA] Running shell script
+        + cd samples/get-edgent-jars-project
+        + ./get-edgent-jars.sh
+        ./get-edgent-jars.sh: 111: [: java8: unexpected operator
+        ./get-edgent-jars.sh: 118: ./get-edgent-jars.sh: Syntax error: "(" unexpected
+
+        */
+        /*stage ('Verify get-engent-jars') {
+            if(env.BRANCH_NAME == 'develop') {
+                echo 'Verifying get-edgent-jars'
+                sh "cd samples/get-edgent-jars-project; ./get-edgent-jars.sh"
+            } else {
+                echo 'Verifying get-edgent-jars (skipped for non develop branch)'
+            }
+        }*/
+    }
+
+
+    catch (err) {
+        currentBuild.result = "FAILURE"
+/*            mail body: "project build error is here: ${env.BUILD_URL}" ,
+            from: 'xxxx@yyyy.com',
+            replyTo: 'dev@edgent.apache.org',
+            subject: 'Autobuild for Branch ' env.BRANCH_NAME
+            to: 'commits@edgent.apache.org'
+*/
+        throw err
+    }
+
+}
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index f4748e9..b56042a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -203,27 +203,37 @@
 ======================================
 APACHE EDGENT SUBCOMPONENTS:
 
-Apache Edgent source includes a number of subcomponents with
+Apache Edgent source a includes one or more subcomponents with
 separate copyright notices and license terms.  Your use of the
 source code for these components is subject to the terms and
 conditions of the following licenses.
 
-This product bundles d3-legend which is available under a MIT license.
-For details, see licenses/d3-legend.MIT
+This product bundles d3-legend which is available under a separate license.
 
-This product bundles portions of d3 which is available under a BSD license.
-For details, see licenses/d3.BSD
+console/servlets/src/main/webapp/js/ext/d3-legend/d3.legend.js
+https://gist.githubusercontent.com/ZJONSSON/3918369/raw/bf9bce6b68a3b70f87450f155436ca4a84af1ba4/d3.legend.js
+With Edgent specific modifications.
+License type: MIT
 
-This product bundles portions of d3-sankey which is available under a BSD license.
-For details, see licenses/d3-sankey.BSD
- 
-This product bundles portions of jquery-ui which is available under a MIT license.
-For details, see licenses/jquery-ui.MIT
- 
-The bundled jquery-ui bundles jquery which is available under a MIT license.
-For details, see licenses/jquery.MIT
+  Copyright (c) 2012 ziggy.jonsson.nyc@gmail.com
 
-The bundled jquery bundles Sizzle which is available under a MIT license.
-For details, see licenses/sizzle.MIT
+  Permission is hereby granted, free of charge, to any person obtaining
+  a copy of this software and associated documentation files (the
+  "Software"), to deal in the Software without restriction, including
+  without limitation the rights to use, copy, modify, merge, publish,
+  distribute, sublicense, and/or sell copies of the Software, and to
+  permit persons to whom the Software is furnished to do so, subject to
+  the following conditions:
+
+  The above copyright notice and this permission notice shall be
+  included in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 ======================================
diff --git a/PROBLEMS.md b/PROBLEMS.md
new file mode 100644
index 0000000..2b25c49
--- /dev/null
+++ b/PROBLEMS.md
@@ -0,0 +1,21 @@
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+
+1) Rename console/servlets to console/webapp
+
diff --git a/README b/README
index ebffb0a..40d08ad 100644
--- a/README
+++ b/README
@@ -1,32 +1,35 @@
-Building Edgent from a Source Distribution
+Building the Apache Edgent SDK from a Source Distribution
+
+Quickstart
+----------
+
+You must have Java 8 installed on your system and connectivity to Maven Central
+(for downloading external thrid party dependencies). Maven will be automatically
+downloaded and installed by the maven wrapper `mvnw`.
+
+Build the Edgent SDK Java8 jars and install them in your local maven repository
+
+$ ./mvnw clean install  # add -DskipTests to omit running the tests
+
+You can now construct applications that use Edgent.  The Edgent samples
+are a good place to start and are available as a separate download
+at https://github.com/apache/incubator-edgent-samples
+
+Additional Information
+----------------------
 
 Apache Edgent is supported on several Java target platforms.
 For more information see JAVA_SUPPORT.md in the source tree
 or in the ASF git repository https://git-wip-us.apache.org/repos/asf/incubator-edgent.git
 or in the repository mirror at github https://github.com/apache/incubator-edgent.
 
-Pre-requisites:
-- Java 8 is required to build an Edgent binary distribution
-- gradle (https://gradle.org/) only if building from a source release bundle
+See DEVELOPMENT.md in this folder or 
+https://github.com/apache/incubator-edgent/DEVELOPMENT.md for information
+about building the Edgent SDK for the Java 7 and Android platforms.
 
-Building from a source release bundle (lacking a ./gradlew) requires
-performing a one-time bootstrap step using an installed version of gradle:
 
-$ gradle          # one time gradle build bootstrap setup.
-
-Build an Edgent binary distribution:
-
-$ ./gradlew release   # .\gradlew.bat on Windows
-
-The build reports the location of the binary distribution bundle that can then
-be unpacked and used in building applications.
-
-See Getting Started https://edgent.apache.org/docs/edgent-getting-started
-
-For more information about the Edgent sources, testing, and
-contributing to Edgent runtime development see DEVELOPMENT.md in the source tree
-or in the ASF git repository https://git-wip-us.apache.org/repos/asf/incubator-edgent.git
-or in the repository mirror at github https://github.com/apache/incubator-edgent.
+Licensing
+---------
 
 Apache Edgent is released under the Apache License Version 2.0.
 
diff --git a/README.md b/README.md
index ff706d1..9dda318 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,30 @@
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
 # Welcome to Apache Edgent!
 
+[![Build Status](https://travis-ci.org/apache/incubator-edgent.svg?branch=develop)](https://travis-ci.org/apache/incubator-edgent)
+
 *Apache Edgent is an effort undergoing incubation at The Apache Software Foundation (ASF), sponsored by the Incubator PMC. Incubation is required of all newly accepted projects until a further review indicates that the infrastructure, communications, and decision making process have stabilized in a manner consistent with other successful ASF projects. While incubation status is not necessarily a reflection of the completeness or stability of the code, it does indicate that the project has yet to be fully endorsed by the ASF.*
 
 Apache Edgent is an open source programming model and runtime for edge devices that enables you to analyze data and events at the device.
 
-Please joins us by subscribing to the developer mailing list
+Please join us by subscribing to the developer mailing list
 [dev at edgent.incubator.apache.org](http://mail-archives.apache.org/mod_mbox/incubator-edgent-dev/).
 To subscribe, send an email to `dev-subscribe at edgent.incubator.apache.org`.
 
@@ -24,34 +44,12 @@
 
 Edgent is released under the [Apache License Version 2.0](LICENSE)
 
-# Renamed from Apache Quarks
-Apache Edgent is the new name. Things are in a state of transition until all
-of the pieces arrive.
-
-The "incubator-quarks" repository has been fully updated.
-
-Until the Apache infrastructure changes are done, continue to use
-the Quarks mailing list, website, and repositories:
-  * mailing list: dev at quarks.incubator.apache.org
-  * http://quarks.incubator.apache.org/
-  * https://git-wip-us.apache.org/repos/asf/incubator-quarks.git
-  * https://github.com/apache/incubator-quarks
-
-Code changes:
-  * package names have the prefix "org.apache.edgent"
-  * jar names have the prefix "edgent"
-  
-Users of Edgent will need to update their references to the above.
-It's recommended that developers of Edgent create a new workspace instead of
-reusing their Quarks workspace.
-
 # Edgent
 Devices and sensors are everywhere. And more are coming online every day. You need a way to analyze all of the data coming from your devices, but it can be expensive to transmit all of the data from a sensor to your central analytics engine.
 
 Edgent is an open source programming model and runtime for edge devices that enables you to analyze data and events at the device. When you analyze on the edge, you can:
 
 * Reduce the amount of data that you transmit to your analytics server
-
 * Reduce the amount of data that you store
 
 An Edgent application uses analytics to determine when data needs to be sent to a back-end system for further analysis, action, or storage. For example, you can use Edgent to determine whether a system is running outside of normal parameters, such as an engine that is running too hot.
@@ -102,3 +100,8 @@
 For details about the Edgent sources and contributing to 
 Edgent runtime development see [DEVELOPMENT.md](DEVELOPMENT.md)
 
+# Renamed from Apache Quarks
+Apache Quarks was renamed to Apache Edgent.
+
+See the [RELEASE_NOTES](RELEASE_NOTES] for change information if
+you are converting from "Apache Quarks".
diff --git a/RELEASE_NOTES b/RELEASE_NOTES
index d9dba00..fc2bbd8 100644
--- a/RELEASE_NOTES
+++ b/RELEASE_NOTES
@@ -1,3 +1,89 @@
+Apache Edgent (incubating) 1.2.0
+==================
+
+The release includes a number of minor SDK bugfixes and enhancements listed below.
+
+A release now includes two source bundles:
+  - Edgent SDK bundle
+  - Edgent Samples bundle
+
+A release now publishes the Edgent SDK jars in Apache Nexus Release
+repository which are automatically mirrored to the Maven Central repository.
+
+A binary bundle (tgz) is no longer released.  The new `get-edgent-jars.sh`
+tool included in the Samples source bundle can be used to create such
+a bundle.
+
+A user now builds the samples from the released Samples source bundle
+much as a real application developer might.
+Pre-built samples jars are no longer released.  
+The Samples bundle includes an Edgent Application project template
+and some tools.
+
+There have been many updates to the Edgent website (https://edgent.apache.org/)
+related to the above:
+  - updated Getting Started Guide
+  - updated Downloads page
+  - a new The Power of Edgent page
+  - updated FAQ page
+  - a new Quickstart with Edgent Samples page
+  - a new Edgent Application Development page
+
+There are some additional changes affecting the
+use of Edgent in developing Edgent applications:
+
+The names of the Edgent jars now conform to standard
+practices: "." has been replaced with "-" and the Edgent SDK
+versionId is now included.  
+
+Almost all of the Edgent SDK jars are now available for the Java7 and Android platforms.
+
+The websocket connector jar names have been changed:
+* `edgent-connectors-websocket-<ver>.jar` was `edgent.connectors.websocket.wsclient-javax.websocket.jar`
+* `edgent-connectors-websocket-base-<ver>.jar` was  `edgent.connectors.wsclient.jar`
+* `edgent-connectors-websocket-misc-<ver>.jar` was  `edgent.javax.websocket.jar`
+* `edgent-connectors-websocket-jetty-<ver>.jar` was  `javax.websocket-client.jar`
+
+See the `JAVA_SUPPORT` documentation on the Downloads page details.
+
+From the perspective of building, development, and releasing
+Edgent, Maven is now used for the build tooling.
+The DEVELOPMENT.md file has been updated accordingly.
+
+
+New Features
+--------------------
+EDGENT-393  Add Ranges.outsideOf()
+EDGENT-273  Add scripts, etc to enable building samples
+
+Incompatible API changes
+------------------------
+NONE
+    
+Known Issues
+--------
+Open JIRAs may be found at https://issues.apache.org/jira/browse/EDGENT
+
+Bug Fixes
+---------
+EDGENT-429  JobMonitorApp.closeJob() doesn't wait for close
+EDGENT-423  Range.toStringUnsigned() not supported on Java7/Android
+
+Miscellaneous changes
+---------------------
+EDGENT-438  Improve WebSocketClientTest skip-if-cant-connect
+EDGENT-436  Change tests that use complete() TMO for successful runs
+EDGENT-435  CME in TrackingScheduledExecutor seen with testMultiTopologyPollWithError()
+EDGENT-434  Desensitize PlumbingTest.testParallelBalanced
+EDGENT-433  Automatically bump some TMO values when running CI test context
+EDGENT-432  Reduce runtime of some WindowsTest tests
+EDGENT-431  Cleanup some console code hygiene warnings
+EDGENT-418  LICENSE/NOTICE in .war are wrong
+EDGENT-413  Simplify download experience
+EDGENT-219  WindowsTest.keyedTimeBatchWIndowTest isn't robust
+EDGENT-31   FileStreamsTextFileWriterTest.testRetainAgeBased test fails
+
+
 Apache Edgent (incubating) 1.1.0
 ==================
 
diff --git a/analytics/.classpath b/analytics/.classpath
deleted file mode 100644
index 82ecf3d..0000000
--- a/analytics/.classpath
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" path="math3/src/main/java"/>
-	<classpathentry kind="src" path="math3/src/test/java"/>
-	<classpathentry kind="src" path="sensors/src/main/java"/>
-	<classpathentry kind="src" path="sensors/src/test/java"/>
-	<classpathentry exported="true" kind="lib" path="../externalJars/java8/analytics/math3/ext/commons-math3-3.4.1.jar"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/api"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/ext"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/providers"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/spi"/>
-	<classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/analytics/.gitignore b/analytics/.gitignore
deleted file mode 100644
index 948fe44..0000000
--- a/analytics/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-/classes/
-**/test.classes/
-**/classes/
-**/unittests/
-/bin/
diff --git a/analytics/.project b/analytics/.project
deleted file mode 100644
index 726a29c..0000000
--- a/analytics/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>analytics</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-	</natures>
-</projectDescription>
diff --git a/analytics/math3/build.gradle b/analytics/math3/build.gradle
deleted file mode 100644
index 9842e9c..0000000
--- a/analytics/math3/build.gradle
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:topology'
-  addProjectExtDependency 'compile', 'org.apache.commons:commons-math3:3.4.1'
-  addTargetDirCoreExtJarDependencies 'compile'
-  
-  addTargetDirProjectJarDependency 'testCompile', ':providers:direct'
-  // N.B. root project adds test common dependencies
-}
-
-addCompileTestDependencies ':api:topology', ':providers:direct'
diff --git a/analytics/math3/pom.xml b/analytics/math3/pom.xml
new file mode 100644
index 0000000..d4017a1
--- /dev/null
+++ b/analytics/math3/pom.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-analytics</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-analytics-math3</artifactId>
+
+  <name>Apache Edgent (Java 8): Analytics: Math3</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-math3</artifactId>
+      <version>3.4.1</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/analytics/math3/src/main/java/org/apache/edgent/analytics/math3/Aggregations.java b/analytics/math3/src/main/java/org/apache/edgent/analytics/math3/Aggregations.java
index bcc9ea0..af4cdfb 100644
--- a/analytics/math3/src/main/java/org/apache/edgent/analytics/math3/Aggregations.java
+++ b/analytics/math3/src/main/java/org/apache/edgent/analytics/math3/Aggregations.java
@@ -23,6 +23,7 @@
 import org.apache.edgent.analytics.math3.json.JsonAnalytics;
 import org.apache.edgent.analytics.math3.stat.Regression2;
 import org.apache.edgent.analytics.math3.stat.Statistic2;
+import org.apache.edgent.analytics.math3.utils.Java7Helper;
 import org.apache.edgent.function.ToDoubleFunction;
 import org.apache.edgent.topology.TWindow;
 
@@ -195,6 +196,7 @@
    * An aggregation result may be null under other conditions,
    * e.g., a Regression2.SLOPE where the minimum number of samples has not been met.
    * 
+   * @param <T> Tuple type
    * @param c the Collection to aggregate
    * @param getter function that returns the variable to aggregate from a {@code T}
    * @param aggregate the aggregation to perform
@@ -213,6 +215,7 @@
    * The ResultMap does not contain an entry for an aggregation with a null,
    * e.g., a Regression2.SLOPE where the minimum number of samples has not been met.
    * 
+   * @param <T> Tuple type
    * @param c the Collection to aggregate
    * @param getter function that returns the variable to aggregate from a {@code T}
    * @param aggregates the aggregations to perform
@@ -242,8 +245,8 @@
           // do as JsonAnalytics did and omit Nan/Inf results from the map.
           double rv = agg.getResult();
           
-          if (Double.isFinite(rv))
-              result.put(agg.getAggregate(), Double.valueOf(rv));
+          if (Java7Helper.doubleIsFinite(rv))
+              result.put(agg.getAggregate(), rv);
       }
     }
 
diff --git a/analytics/math3/src/main/java/org/apache/edgent/analytics/math3/json/JsonAnalytics.java b/analytics/math3/src/main/java/org/apache/edgent/analytics/math3/json/JsonAnalytics.java
index 8335757..27f6202 100644
--- a/analytics/math3/src/main/java/org/apache/edgent/analytics/math3/json/JsonAnalytics.java
+++ b/analytics/math3/src/main/java/org/apache/edgent/analytics/math3/json/JsonAnalytics.java
@@ -249,6 +249,7 @@
      *    });
      * }</pre>
      * 
+     * @param <K> Partition type
      * @param window the window to compute aggregations over
      * @param resultPartitionKeyProperty name of the partition key property in the result
      * @param resultProperty name of the aggregation results property in the result
diff --git a/analytics/math3/src/main/java/org/apache/edgent/analytics/math3/stat/JsonOLS.java b/analytics/math3/src/main/java/org/apache/edgent/analytics/math3/stat/JsonOLS.java
index c1d351b..bf5e38c 100644
--- a/analytics/math3/src/main/java/org/apache/edgent/analytics/math3/stat/JsonOLS.java
+++ b/analytics/math3/src/main/java/org/apache/edgent/analytics/math3/stat/JsonOLS.java
@@ -23,6 +23,7 @@
 
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
+import org.apache.edgent.analytics.math3.utils.Java7Helper;
 
 class JsonOLS implements JsonUnivariateAggregator {
     
@@ -66,7 +67,7 @@
             // [0] is the constant (zero'th order)
             // [1] is the first order , which we use as the slope.
             final double slope = regressionParams[1];
-            if (Double.isFinite(slope))
+            if (Java7Helper.doubleIsFinite(slope))
                 result.addProperty(type.name(), slope);
         }
         values = null;
diff --git a/analytics/math3/src/main/java/org/apache/edgent/analytics/math3/stat/JsonStorelessStatistic.java b/analytics/math3/src/main/java/org/apache/edgent/analytics/math3/stat/JsonStorelessStatistic.java
index 702f7c3..8c43c84 100644
--- a/analytics/math3/src/main/java/org/apache/edgent/analytics/math3/stat/JsonStorelessStatistic.java
+++ b/analytics/math3/src/main/java/org/apache/edgent/analytics/math3/stat/JsonStorelessStatistic.java
@@ -23,6 +23,7 @@
 
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
+import org.apache.edgent.analytics.math3.utils.Java7Helper;
 
 /**
  * JSON univariate aggregator implementation wrapping a {@code StorelessUnivariateStatistic}.
@@ -51,7 +52,7 @@
     public void result(JsonElement partition, JsonObject result) {        
         double rv = statImpl.getResult();
         
-        if (Double.isFinite(rv))
+        if (Java7Helper.doubleIsFinite(rv))
             result.addProperty(stat.name(), rv);
     }
 
diff --git a/samples/topology/src/main/java/org/apache/edgent/samples/topology/package-info.java b/analytics/math3/src/main/java/org/apache/edgent/analytics/math3/utils/Java7Helper.java
similarity index 76%
rename from samples/topology/src/main/java/org/apache/edgent/samples/topology/package-info.java
rename to analytics/math3/src/main/java/org/apache/edgent/analytics/math3/utils/Java7Helper.java
index 000cb03..0a2e7f4 100644
--- a/samples/topology/src/main/java/org/apache/edgent/samples/topology/package-info.java
+++ b/analytics/math3/src/main/java/org/apache/edgent/analytics/math3/utils/Java7Helper.java
@@ -16,9 +16,12 @@
 specific language governing permissions and limitations
 under the License.
 */
+package org.apache.edgent.analytics.math3.utils;
 
-/**
- * Samples showing creating and executing basic topologies .
- */
-package org.apache.edgent.samples.topology;
+public class Java7Helper {
 
+    public static boolean doubleIsFinite(double value) {
+        return (Double.NEGATIVE_INFINITY < value) && (value < Double.POSITIVE_INFINITY);
+    }
+
+}
diff --git a/analytics/math3/src/test/java/org/apache/edgent/test/analytics/math3/Statistics2Test.java b/analytics/math3/src/test/java/org/apache/edgent/test/analytics/math3/Statistics2Test.java
index 6995b30..779c9b7 100644
--- a/analytics/math3/src/test/java/org/apache/edgent/test/analytics/math3/Statistics2Test.java
+++ b/analytics/math3/src/test/java/org/apache/edgent/test/analytics/math3/Statistics2Test.java
@@ -28,7 +28,6 @@
 import java.util.List;
 import java.util.Map;
 
-import org.apache.commons.math3.util.Pair;
 import org.apache.edgent.analytics.math3.Aggregations;
 import org.apache.edgent.analytics.math3.MvResultMap;
 import org.apache.edgent.analytics.math3.ResultMap;
diff --git a/analytics/pom.xml b/analytics/pom.xml
new file mode 100644
index 0000000..ac75ad6
--- /dev/null
+++ b/analytics/pom.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-parent</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-analytics</artifactId>
+  <packaging>pom</packaging>
+
+  <name>Apache Edgent (Java 8): Analytics</name>
+
+  <modules>
+    <module>sensors</module>
+    <module>math3</module>
+  </modules>
+
+</project>
diff --git a/analytics/sensors/build.gradle b/analytics/sensors/build.gradle
deleted file mode 100644
index 6a86d26..0000000
--- a/analytics/sensors/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:topology'
-  addTargetDirCoreExtJarDependencies 'compile'
-  
-  addTargetDirProjectJarDependency 'testCompile', ':providers:direct'
-  // N.B. root project adds test common dependencies
-}
-
-addCompileTestDependencies ':api:topology', ':providers:direct'
diff --git a/analytics/sensors/pom.xml b/analytics/sensors/pom.xml
new file mode 100644
index 0000000..81e5fc3
--- /dev/null
+++ b/analytics/sensors/pom.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-analytics</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-analytics-sensors</artifactId>
+
+  <name>Apache Edgent (Java 8): Analytics: Sensors</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/analytics/sensors/src/main/java/org/apache/edgent/analytics/sensors/Range.java b/analytics/sensors/src/main/java/org/apache/edgent/analytics/sensors/Range.java
index 65430e7..7ba2b56 100644
--- a/analytics/sensors/src/main/java/org/apache/edgent/analytics/sensors/Range.java
+++ b/analytics/sensors/src/main/java/org/apache/edgent/analytics/sensors/Range.java
@@ -25,6 +25,8 @@
 import org.apache.edgent.function.Function;
 import org.apache.edgent.function.Predicate;
 
+import static org.apache.edgent.analytics.sensors.utils.Java7Helper.*;
+
 /**
  * A generic immutable range of values and a way to 
  * check a value for containment in the range.
@@ -162,6 +164,11 @@
      * Create a new Range&lt;T&gt;
      * <p>
      * See {@link Ranges} for a collection of convenience constructors.
+     * <p>
+     * While not enforced, for a Range to be useful/sensible,
+     * the following must be true: {@code lowerEndpoint <= upperEndpoint}.
+     * Otherwise the Range will be returned but test() and contains()
+     * can never return true.
      * 
      * @param <T> a Comparable type
      * @param lowerEndpoint null for an infinite value (and lbt must be OPEN)
@@ -172,6 +179,9 @@
      */
     public static <T extends Comparable<?>> Range<T> range(T lowerEndpoint, BoundType lbt, T upperEndpoint, BoundType ubt) {
         // matchs Guava Range.range param order
+        // Note: the lowerEndpoint <= upperEndpoint "requirement" is the same as Guava
+        // don't know if Guava Range.range() enforces that.
+        // Since we didn't originally enforce that, leave it that way. 
         return new Range<T>(lowerEndpoint, lbt, upperEndpoint, ubt);
     }
 
@@ -261,7 +271,7 @@
      * <pre>
      * Comparator&lt;Byte&gt; unsignedByteComparator = new Comparator&lt;Byte&gt;() {
      *     public int compare(Byte b1, Byte b2) {
-     *         return Integer.compareUnsigned(Byte.toUnsignedInt(b1), Byte.toUnsignedInt(b2));
+     *         return intCompareUnsigned(byteToUnsignedInt(b1), byteToUnsignedInt(b2));
      *     }
      *     public boolean equals(Object o2) { return o2==this; }
      *     };
@@ -329,7 +339,7 @@
     /**
      * Parse a String from {@link #toString()}
      * 
-     * @param str the String
+     * @param s the String
      * @return Four element array with the range's component Strings
      * @throws IllegalArgumentException
      */
@@ -457,14 +467,14 @@
     
     private static <T> String toUnsignedString(T v) {
         if (v instanceof Byte)
-            return Integer.toUnsignedString(Byte.toUnsignedInt((Byte)v));
+            return intToUnsignedString(byteToUnsignedInt((Byte)v));
         else if (v instanceof Short)
-            return Integer.toUnsignedString(Short.toUnsignedInt((Short)v));
+            return intToUnsignedString(shortToUnsignedInt((Short)v));
         else if (v instanceof Integer)
-            return Integer.toUnsignedString((Integer)v);
+            return intToUnsignedString((Integer)v);
         else if (v instanceof Long)
-            return Long.toUnsignedString((Long)v);
+            return longToUnsignedString((Long)v);
         throw new IllegalArgumentException("Not Range of Byte,Short,Integer, or Long"+v.getClass());
     }
-    
+
 }
diff --git a/analytics/sensors/src/main/java/org/apache/edgent/analytics/sensors/Ranges.java b/analytics/sensors/src/main/java/org/apache/edgent/analytics/sensors/Ranges.java
index c4f485f..e272e4b 100644
--- a/analytics/sensors/src/main/java/org/apache/edgent/analytics/sensors/Ranges.java
+++ b/analytics/sensors/src/main/java/org/apache/edgent/analytics/sensors/Ranges.java
@@ -22,6 +22,7 @@
 import java.math.BigInteger;
 
 import org.apache.edgent.analytics.sensors.Range.BoundType;
+import org.apache.edgent.function.Predicate;
 
 /**
  * Convenience functions and utility operations on {@link Range}.
@@ -136,6 +137,23 @@
     }
     
     /**
+     * Create a Predicate whose {@code test(v)} behaves like {@code ! range.test(v)}
+     * <p>
+     * This can be useful in a situation such as filtering to include only values
+     * that are outside of a range.
+     * <pre>{@code
+     *     TStream<?> outsideRange = readings.filter(Ranges.outsideOf(Ranges.open(10,20)));
+     * }</pre>
+     * 
+     * @param <T> Endpoint type
+     * @param range the Range
+     * @return the Predicate
+     */
+    public static <T extends Comparable<?>> Predicate<T> outsideOf(Range<T> range) {
+        return t -> ! range.test(t);
+    }
+    
+    /**
      * Create a Range from a Range&lt;Integer&gt;.toString() value.
      * @param str the String
      * @return the Range
diff --git a/analytics/sensors/src/main/java/org/apache/edgent/analytics/sensors/utils/Java7Helper.java b/analytics/sensors/src/main/java/org/apache/edgent/analytics/sensors/utils/Java7Helper.java
new file mode 100644
index 0000000..e425c15
--- /dev/null
+++ b/analytics/sensors/src/main/java/org/apache/edgent/analytics/sensors/utils/Java7Helper.java
@@ -0,0 +1,62 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+package org.apache.edgent.analytics.sensors.utils;
+
+public class Java7Helper {
+
+    public static int byteToUnsignedInt(byte input) {
+        return ((int) input) & 0xff;
+    }
+
+    public static int shortToUnsignedInt(short input) {
+        return ((int) input) & 0xffff;
+    }
+
+    public static long intToUnsignedLong(int input) {
+        return ((long) input) & 0xffffffffL;
+    }
+
+    public static String intToUnsignedString(int input) {
+        return Long.toString(intToUnsignedLong(input));
+    }
+
+    public static String longToUnsignedString(long input) {
+        // In case of a positive signed value, just output that.
+        if (input >= 0) {
+            return Long.toString(input, 10);
+        }
+        // If the value is negative, the most significant bit is
+        // 1 and Java is interpreting the number as negative value.
+        else {
+            // Shift everything right one bit (filling up with 0)
+            long quot = (input >>> 1) / 5;
+            long rem = input - quot * 10;
+            return Long.toString(quot) + rem;
+        }
+    }
+
+    public static int intCompareUnsigned(int x, int y) {
+        return Integer.compare(x + Integer.MIN_VALUE, y + Integer.MIN_VALUE);
+    }
+
+    public static int longCompareUnsigned(long x, long y) {
+        return Long.compare(x + Long.MIN_VALUE, y + Long.MIN_VALUE);
+    }
+
+}
diff --git a/analytics/sensors/src/test/java/org/apache/edgent/test/analytics/sensors/RangeTest.java b/analytics/sensors/src/test/java/org/apache/edgent/test/analytics/sensors/RangeTest.java
index 7d4b316..92298d1 100644
--- a/analytics/sensors/src/test/java/org/apache/edgent/test/analytics/sensors/RangeTest.java
+++ b/analytics/sensors/src/test/java/org/apache/edgent/test/analytics/sensors/RangeTest.java
@@ -8,7 +8,7 @@
 with the License.  You may obtain a copy of the License at
 
   http://www.apache.org/licenses/LICENSE-2.0
-  
+
 Unless required by applicable law or agreed to in writing,
 software distributed under the License is distributed on an
 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertTrue;
+import static org.apache.edgent.analytics.sensors.utils.Java7Helper.*;
 
 import java.lang.reflect.Type;
 import java.math.BigDecimal;
@@ -31,6 +32,7 @@
 import org.apache.edgent.analytics.sensors.Range;
 import org.apache.edgent.analytics.sensors.Ranges;
 import org.apache.edgent.function.Function;
+import org.apache.edgent.function.Predicate;
 import org.junit.Test;
 
 import com.google.gson.Gson;
@@ -40,13 +42,18 @@
  * Test Range and Ranges
  */
 public class RangeTest {
+  
+  private <T extends Comparable<?>> void testOutsideOf(Range<T> range, T v) {
+      Predicate<T> notPredicate = Ranges.outsideOf(range); 
+      assertEquals("outsideOf-"+range+".test("+v+")", !range.test(v), notPredicate.test(v));
+  }
     
     private <T extends Comparable<?>> void testContains(Range<T> range, T v, Boolean expected) {
-        assertEquals("range"+range+".contains(range"+v+")", expected, range.contains(v));
+        assertEquals("range"+range+".contains("+v+")", expected, range.contains(v));
     }
     
     private <T extends Comparable<?>> void testPredicate(Range<T> range, T v, Boolean expected) {
-        assertEquals("range"+range+".test(range"+v+")", expected, range.test(v));
+        assertEquals("range"+range+".test("+v+")", expected, range.test(v));
     }
     
     private <T extends Comparable<?>> void testToString(Range<T> range, String expected) {
@@ -162,6 +169,15 @@
         testPredicate(Ranges.closed(2,4), 4, true);
         testPredicate(Ranges.closed(2,4), 5, false);
     }
+    
+    @Test
+    public void testOutsideOf() {
+        testOutsideOf(Ranges.closed(2,4), 1);
+        testOutsideOf(Ranges.closed(2,4), 2);
+        testOutsideOf(Ranges.closed(2,4), 3);
+        testOutsideOf(Ranges.closed(2,4), 4);
+        testOutsideOf(Ranges.closed(2,4), 5);
+    }
 
     @Test
     public void testEquals() {
@@ -372,14 +388,13 @@
      * Test unsigned handling.
      * toUnsignedString() and compare(T, Comparator<T>)
      */
-
     @Test
     public void testToUnsignedString() {
         testToStringUnsigned(Ranges.open((byte)0,(byte)255), "(0..255)");
         testToStringUnsigned(Ranges.closed((byte)0,(byte)255), "[0..255]");
         testToStringUnsigned(Ranges.open((short)0,(short)0xFFFF), "(0..65535)");
-        testToStringUnsigned(Ranges.open(0,0xFFFFFFFF), "(0.."+Integer.toUnsignedString(0xFFFFFFFF)+")");
-        testToStringUnsigned(Ranges.open(0L,0xFFFFFFFFFFFFFFFFL), "(0.."+Long.toUnsignedString(0xFFFFFFFFFFFFFFFFL)+")");
+        testToStringUnsigned(Ranges.open(0,0xFFFFFFFF), "(0.."+ intToUnsignedString(0xFFFFFFFF)+")");
+        testToStringUnsigned(Ranges.open(0L,0xFFFFFFFFFFFFFFFFL), "(0.."+longToUnsignedString(0xFFFFFFFFFFFFFFFFL)+")");
     }
 
     @Test
@@ -387,7 +402,7 @@
         // Unsigned Byte ======================
         Comparator<Byte> unsignedByteComparator = new Comparator<Byte>() {
             public int compare(Byte v1, Byte v2) {
-                return Integer.compareUnsigned(Byte.toUnsignedInt(v1), Byte.toUnsignedInt(v2));
+                return intCompareUnsigned(byteToUnsignedInt(v1), byteToUnsignedInt(v2));
             }
             public boolean equals(Object o2) { return o2==this; }
             };
@@ -401,7 +416,7 @@
         // Unsigned Short ======================
         Comparator<Short> unsignedShortComparator = new Comparator<Short>() {
             public int compare(Short v1, Short v2) {
-                return Integer.compareUnsigned(Short.toUnsignedInt(v1), Short.toUnsignedInt(v2));
+                return intCompareUnsigned(shortToUnsignedInt(v1), shortToUnsignedInt(v2));
             }
             public boolean equals(Object o2) { return o2==this; }
             };
@@ -414,7 +429,7 @@
         // Unsigned Integer ======================
         Comparator<Integer> unsignedIntegerComparator = new Comparator<Integer>() {
             public int compare(Integer v1, Integer v2) {
-                return Integer.compareUnsigned(v1, v2);
+                return intCompareUnsigned(v1, v2);
             }
             public boolean equals(Object o2) { return o2==this; }
             };
@@ -427,7 +442,7 @@
         // Unsigned Long ======================
         Comparator<Long> unsignedLongComparator = new Comparator<Long>() {
             public int compare(Long v1, Long v2) {
-                return Long.compareUnsigned(v1, v2);
+                return longCompareUnsigned(v1, v2);
             }
             public boolean equals(Object o2) { return o2==this; }
             };
diff --git a/android/.gitignore b/android/.gitignore
deleted file mode 100644
index ffb09f3..0000000
--- a/android/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-/classes/
-**/test.classes/
-**/classes/
-**/unittests/
diff --git a/android/hardware/build.gradle b/android/hardware/build.gradle
deleted file mode 100644
index 0368f95..0000000
--- a/android/hardware/build.gradle
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-
-ext.androidJarSpec = 'com.google.android:android:4.1.1.4@jar'
-
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:topology'
-  addProjectExtDependency 'compile', androidJarSpec
-  
-  // N.B. root project adds test common dependencies
-}
-
-test {
-  // this project lacks tests and this task fails if attempted 
-  enabled = false
-}
diff --git a/android/topology/build.gradle b/android/topology/build.gradle
deleted file mode 100644
index f5e9106..0000000
--- a/android/topology/build.gradle
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:topology'
-  addTargetDirProjectJarDependency 'compile', ':api:oplet'
-  addProjectExtDependency 'compile', project(':android:hardware').androidJarSpec
-
-  // N.B. root project adds test common dependencies
-}
-
-test {
-  // this project lacks tests and this task fails if attempted 
-  enabled = false
-}
diff --git a/api/.classpath b/api/.classpath
deleted file mode 100644
index 661cdab..0000000
--- a/api/.classpath
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" path="function/src/main/java"/>
-	<classpathentry kind="src" path="function/src/test/java"/>
-	<classpathentry kind="src" path="execution/src/main/java"/>
-	<classpathentry kind="src" path="execution/src/test/java"/>
-	<classpathentry kind="src" path="graph/src/main/java"/>
-	<classpathentry kind="src" path="graph/src/test/java"/>
-	<classpathentry kind="src" path="oplet/src/main/java"/>
-	<classpathentry kind="src" path="oplet/src/test/java"/>
-	<classpathentry kind="src" path="topology/src/main/java"/>
-	<classpathentry kind="src" path="topology/src/test/java"/>
-	<classpathentry kind="src" path="window/src/main/java"/>
-	<classpathentry kind="src" path="window/src/test/java"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/ext"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
-	<classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/api/.gitignore b/api/.gitignore
deleted file mode 100644
index 948fe44..0000000
--- a/api/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-/classes/
-**/test.classes/
-**/classes/
-**/unittests/
-/bin/
diff --git a/api/.project b/api/.project
deleted file mode 100644
index 0df4729..0000000
--- a/api/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>api</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-	</natures>
-</projectDescription>
diff --git a/api/execution/build.gradle b/api/execution/build.gradle
deleted file mode 100644
index 19b441d..0000000
--- a/api/execution/build.gradle
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:function'
-  addTargetDirCoreExtJarDependencies 'compile'
-
-  // N.B. root project adds test common dependencies
-}
diff --git a/api/execution/pom.xml b/api/execution/pom.xml
new file mode 100644
index 0000000..e0b0e5b
--- /dev/null
+++ b/api/execution/pom.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-api</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-api-execution</artifactId>
+
+  <name>Apache Edgent (Java 8): API: Execution</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-function</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.code.gson</groupId>
+      <artifactId>gson</artifactId>
+      <version>2.8.0</version>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/api/function/build.gradle b/api/function/build.gradle
deleted file mode 100644
index f5aa243..0000000
--- a/api/function/build.gradle
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-
-dependencies {
-  // none
-
-  // N.B. root project adds test common dependencies
-}
diff --git a/api/function/pom.xml b/api/function/pom.xml
new file mode 100644
index 0000000..0f2e38b
--- /dev/null
+++ b/api/function/pom.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-api</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-api-function</artifactId>
+
+  <name>Apache Edgent (Java 8): API: Function</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+</project>
diff --git a/api/graph/build.gradle b/api/graph/build.gradle
deleted file mode 100644
index 4931d57..0000000
--- a/api/graph/build.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:oplet'
-  addTargetDirCoreExtJarDependencies 'compile'
-
-  // N.B. root project adds test common dependencies
-}
-
-test {
-  // this project lacks non-abstract test classes and this task fails if attempted 
-  enabled = false
-}
diff --git a/api/graph/pom.xml b/api/graph/pom.xml
new file mode 100644
index 0000000..94fa3de
--- /dev/null
+++ b/api/graph/pom.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-api</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-api-graph</artifactId>
+
+  <name>Apache Edgent (Java 8): API: Graph</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-oplet</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/api/oplet/build.gradle b/api/oplet/build.gradle
deleted file mode 100644
index b97572d..0000000
--- a/api/oplet/build.gradle
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:function'
-  addTargetDirProjectJarDependency 'compile', ':api:execution'
-  addTargetDirProjectJarDependency 'compile', ':api:window'
-  addTargetDirCoreExtJarDependencies 'compile'
-
-  // N.B. root project adds test common dependencies
-}
diff --git a/api/oplet/pom.xml b/api/oplet/pom.xml
new file mode 100644
index 0000000..e8ef37e
--- /dev/null
+++ b/api/oplet/pom.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-api</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-api-oplet</artifactId>
+
+  <name>Apache Edgent (Java 8): API: Oplet</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-function</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-execution</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-window</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/api/pom.xml b/api/pom.xml
new file mode 100644
index 0000000..481c4ee
--- /dev/null
+++ b/api/pom.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-parent</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-api</artifactId>
+  <packaging>pom</packaging>
+
+  <name>Apache Edgent (Java 8): API</name>
+
+  <modules>
+    <module>execution</module>
+    <module>function</module>
+    <module>graph</module>
+    <module>oplet</module>
+    <module>topology</module>
+    <module>window</module>
+  </modules>
+
+</project>
diff --git a/api/topology/build.gradle b/api/topology/build.gradle
deleted file mode 100644
index ac33fc3..0000000
--- a/api/topology/build.gradle
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:execution'
-  addTargetDirProjectJarDependency 'compile', ':api:function'
-  addTargetDirProjectJarDependency 'compile', ':api:graph'
-  addTargetDirProjectJarDependency 'compile', ':api:oplet'
-  addTargetDirCoreExtJarDependencies 'compile'
-
-  // N.B. root project adds test common dependencies
-}
-
-//Build a jar file containing the applications to test the ApplicationService
-task testApplicationJar{
-  doLast{
-    ant.jar(destfile: "${buildDir}/lib/test/edgent.api.topology.APPS.TEST.jar") {
-      service(type: 'org.apache.edgent.topology.services.TopologyBuilder') {
-        provider(classname: 'org.apache.edgent.test.topology.services.TestApplications$AppOne')
-        provider(classname: 'org.apache.edgent.test.topology.services.TestApplications$AppTwo')
-        provider(classname: 'org.apache.edgent.test.topology.services.TestApplications$AppThree')
-      }
-    fileset (dir: "${buildDir}/classes/test/", includes: '**/*TestApplications$App*.class')
-    }
-  }
-}
-
-testClasses.dependsOn testApplicationJar
diff --git a/api/topology/pom.xml b/api/topology/pom.xml
new file mode 100644
index 0000000..ab8fc53
--- /dev/null
+++ b/api/topology/pom.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-api</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-api-topology</artifactId>
+
+  <name>Apache Edgent (Java 8): API: Topology</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-execution</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-function</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-graph</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-oplet</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/api/topology/src/main/java/org/apache/edgent/topology/plumbing/PlumbingStreams-withComcurrentMapSingleOp.JAVA b/api/topology/src/main/java/org/apache/edgent/topology/plumbing/PlumbingStreams-withComcurrentMapSingleOp.JAVA
deleted file mode 100644
index f470ad0..0000000
--- a/api/topology/src/main/java/org/apache/edgent/topology/plumbing/PlumbingStreams-withComcurrentMapSingleOp.JAVA
+++ /dev/null
@@ -1,494 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-package org.apache.edgent.topology.plumbing;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.apache.edgent.function.Function;
-import org.apache.edgent.oplet.plumbing.Barrier;
-import org.apache.edgent.oplet.plumbing.Isolate;
-import org.apache.edgent.oplet.plumbing.PressureReliever;
-import org.apache.edgent.oplet.plumbing.UnorderedIsolate;
-import org.apache.edgent.topology.TStream;
-import org.apache.edgent.topology.TopologyProvider;
-
-/**
- * Plumbing utilities for {@link TStream}.
- * Methods that manipulate the flow of tuples in a streaming topology,
- * but are not part of the logic of the application.
- */
-public class PlumbingStreams {
-  
-    /**
-     * Insert a blocking delay between tuples.
-     * Returned stream is the input stream delayed by {@code delay}.
-     * <p>
-     * Delays less than 1msec are translated to a 0 delay.
-     * <p>
-     * This function always adds the {@code delay} amount after receiving
-     * a tuple before forwarding it.  
-     * <p>
-     * Downstream tuple processing delays will affect
-     * the overall delay of a subsequent tuple.
-     * <p>
-     * e.g., the input stream contains two tuples t1 and t2 and
-     * the delay is 100ms.  The forwarding of t1 is delayed by 100ms.
-     * Then if a downstream processing delay of 80ms occurs, this function
-     * receives t2 80ms after it forwarded t1 and it will delay another
-     * 100ms before forwarding t2.  Hence the overall delay between forwarding
-     * t1 and t2 is 180ms.
-     * See {@link #blockingThrottle(long, TimeUnit) blockingThrottle}.
-     * 
-     * @param stream Stream t
-     * @param delay Amount of time to delay a tuple.
-     * @param unit Time unit for {@code delay}.
-     * 
-     * @return Stream that will be delayed.
-     */
-    public static <T> TStream<T> blockingDelay(TStream<T> stream, long delay, TimeUnit unit) {
-        return stream.map(t -> {try {
-            Thread.sleep(unit.toMillis(delay));
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            throw new RuntimeException(e);
-        } return t;}) ;
-    }
-    
-    /**
-     * Maintain a constant blocking delay between tuples.
-     * The returned stream is the input stream throttled by {@code delay}.
-     * <p>
-     * Delays less than 1msec are translated to a 0 delay.
-     * <p>
-     * Sample use:
-     * <pre>{@code
-     * TStream<String> stream = topology.strings("a", "b, "c");
-     * // Create a stream with tuples throttled to 1 second intervals.
-     * TStream<String> throttledStream = blockingThrottle(stream, 1, TimeUnit.SECOND);
-     * // print out the throttled tuples as they arrive
-     * throttledStream.peek(t -> System.out.println(new Date() + " - " + t));
-     * }</pre>
-     * <p>
-     * The function adjusts for downstream processing delays.
-     * The first tuple is not delayed.  If {@code delay} has already
-     * elapsed since the prior tuple was forwarded, the tuple 
-     * is forwarded immediately.
-     * Otherwise, forwarding the tuple is delayed to achieve
-     * a {@code delay} amount since forwarding the prior tuple.
-     * <p>
-     * e.g., the input stream contains two tuples t1 and t2 and
-     * the delay is 100ms.  The forwarding of t1 is delayed by 100ms.
-     * Then if a downstream processing delay of 80ms occurs, this function
-     * receives t2 80ms after it forwarded t1 and it will only delay another
-     * 20ms (100ms - 80ms) before forwarding t2.  
-     * Hence the overall delay between forwarding t1 and t2 remains 100ms.
-     * 
-     * @param <T> tuple type
-     * @param stream the stream to throttle
-     * @param delay Amount of time to delay a tuple.
-     * @param unit Time unit for {@code delay}.
-     * @return the throttled stream
-     */
-    public static <T> TStream<T> blockingThrottle(TStream<T> stream, long delay, TimeUnit unit) {
-        return stream.map( blockingThrottle(delay, unit) );
-    }
-
-    private static <T> Function<T,T> blockingThrottle(long delay, TimeUnit unit) {
-        long[] nextTupleTime = { 0 };
-        return t -> {
-            long now = System.currentTimeMillis();
-            if (nextTupleTime[0] != 0) {
-                if (now < nextTupleTime[0]) {
-                    try {
-                        Thread.sleep(nextTupleTime[0] - now);
-                    } catch (InterruptedException e) {
-                        Thread.currentThread().interrupt();
-                        throw new RuntimeException(e);
-                    }
-                    now = System.currentTimeMillis();
-                }
-            }
-            nextTupleTime[0] = now + unit.toMillis(delay);
-            return t;
-        };
-    }
-    
-    /**
-     * Insert a blocking delay before forwarding the first tuple and
-     * no delay for subsequent tuples.
-     * <p>
-     * Delays less than 1msec are translated to a 0 delay.
-     * <p>
-     * Sample use:
-     * <pre>{@code
-     * TStream<String> stream = topology.strings("a", "b, "c");
-     * // create a stream where the first tuple is delayed by 5 seconds. 
-     * TStream<String> oneShotDelayedStream =
-     *      stream.map( blockingOneShotDelay(5, TimeUnit.SECONDS) );
-     * }</pre>
-     * 
-     * @param <T> tuple type
-     * @param stream input stream
-     * @param delay Amount of time to delay a tuple.
-     * @param unit Time unit for {@code delay}.
-     * @return the delayed stream
-     */
-    public static <T> TStream<T> blockingOneShotDelay(TStream<T> stream, long delay, TimeUnit unit) {
-        return stream.map( blockingOneShotDelay(delay, unit) );
-    }
-
-    private static <T> Function<T,T> blockingOneShotDelay(long delay, TimeUnit unit) {
-        long[] initialDelay = { unit.toMillis(delay) };
-        return t -> {
-            if (initialDelay[0] != -1) {
-                try {
-                    Thread.sleep(initialDelay[0]);
-                } catch (InterruptedException e) {
-                    Thread.currentThread().interrupt();
-                    throw new RuntimeException(e);
-                }
-                initialDelay[0] = -1;
-            }
-            return t;
-            };
-    }
-    
-    /**
-     * Relieve pressure on upstream processing by discarding tuples.
-     * This method ensures that upstream processing is not
-     * constrained by any delay in downstream processing,
-     * for example by a connector not being able to connect
-     * to its external system.
-     * <P>
-     * Any downstream processing of the returned stream is isolated
-     * from {@code stream} so that any slow down does not affect {@code stream}.
-     * When the downstream processing cannot keep up with rate of
-     * {@code stream} tuples will be dropped from returned stream.
-     * <BR>
-     * Up to {@code count} of the most recent tuples per key from {@code stream}
-     * are maintained when downstream processing is slow, any older tuples
-     * that have not been submitted to the returned stream will be discarded.
-     * <BR>
-     * Tuple order is maintained within a partition but is not guaranteed to
-     * be maintained across partitions.
-     * </P>
-     * 
-     * @param stream Stream to be isolated from downstream processing.
-     * @param keyFunction Function defining the key of each tuple.
-     * @param count Maximum number of tuples to maintain when downstream processing is backing up.
-     * @return Stream that is isolated from and thus relieves pressure on {@code stream}.
-     * 
-     * @param <T> Tuple type.
-     * @param <K> Key type.
-     * @see #isolate(TStream, int) isolate
-     */
-    public static <T,K> TStream<T> pressureReliever(TStream<T> stream, Function<T,K> keyFunction, int count) {
-        return stream.pipe(new PressureReliever<>(count, keyFunction));
-    }
-    
-    /**
-     * Isolate upstream processing from downstream processing.
-     * <BR>
-     * Implementations may throw {@code OutOfMemoryExceptions} 
-     * if the processing against returned stream cannot keep up
-     * with the arrival rate of tuples on {@code stream}.
-     *  
-     * @param stream Stream to be isolated from downstream processing.
-     * @param ordered {@code true} to maintain arrival order on the returned stream,
-     * {@code false} to not guaranteed arrival order.
-     * @return Stream that is isolated from {@code stream}.
-     */
-    public static <T> TStream<T> isolate(TStream<T> stream, boolean ordered) {
-        return stream.pipe(
-                ordered ? new Isolate<T>() : new UnorderedIsolate<T>());
-    }
-    
-    /**
-     * Isolate upstream processing from downstream processing.
-     * <P>
-     * If the processing against the returned stream cannot keep up
-     * with the arrival rate of tuples on {@code stream}, upstream
-     * processing will block until there is space in the queue between
-     * the streams.
-     * </P><P>
-     * Processing of tuples occurs in the order they were received.
-     * </P>
-     *  
-     * @param stream Stream to be isolated from downstream processing.
-     * @param queueCapacity size of the queue between {@code stream} and
-     *        the returned stream.
-     * @return Stream that is isolated from {@code stream}.
-     * @see #pressureReliever(TStream, Function, int) pressureReliever
-     */
-    public static <T> TStream<T> isolate(TStream<T> stream, int queueCapacity) {
-      return stream.pipe(new Isolate<T>(queueCapacity));
-    }
-    
-    /**
-     * Perform analytics concurrently.
-     * <P>
-     * This is a convenience function that calls
-     * {@link #concurrent(TStream, List, Function)} after
-     * creating {@code pipeline} and {@code combiner} functions
-     * from the supplied {@code mappers} and {@code combiner} arguments.
-     * </P><P>
-     * That is, it is logically, if not exactly, the same as:
-     * <pre>{@code
-     * List<Function<TStream<T>,TStream<U>>> pipelines = new ArrayList<>();
-     * for (Function<T,U> mapper : mappers)
-     *   pipelines.add(s -> s.map(mapper));
-     * concurrent(stream, pipelines, s -> s.map(combiner));
-     * }</pre>
-     * </P>
-     * 
-     * @param <T> Tuple type on input stream.
-     * @param <U> Tuple type generated by mappers.
-     * @param <R> Tuple type of the result.
-     * 
-     * @param stream input stream
-     * @param mappers functions to be run concurrently.  Each mapper MUST
-     *                 return a non-null result.
-     *                 A runtime error will be generated if a null result
-     *                 is returned.
-     * @param combiner function to create a result tuple from the list of
-     *                 results from {@code mappers}.
-     *                 The input list order is 1:1 with the {@code mappers} list.
-     *                 I.e., list entry [0] is the result from mappers[0],
-     *                 list entry [1] is the result from mappers[1], etc.
-     * @return result stream
-     */
-    public static <T,U,R> TStream<R> concurrentMap(TStream<T> stream, List<Function<T,U>> mappers, Function<List<U>,R> combiner) {
-      Objects.requireNonNull(stream, "stream");
-      Objects.requireNonNull(mappers, "mappers");
-      Objects.requireNonNull(combiner, "combiner");
-      
-      List<Function<TStream<T>,TStream<U>>> pipelines = new ArrayList<>();
-      for (Function<T,U> mapper : mappers) {
-        pipelines.add(s -> s.map(mapper));
-      }
-      
-      return concurrent(stream, pipelines, s -> s.map(combiner));
-    }
-    
-    // Q: is there any value to this implementation approach?  Or just dispose of it?
-    @SuppressWarnings("unused")
-    private static <T,U,R> TStream<R> concurrentMapSingleOp(TStream<T> stream, List<Function<T,U>> mappers, Function<List<U>,R> combiner) {
-      Objects.requireNonNull(stream, "stream");
-      Objects.requireNonNull(mappers, "mappers");
-      Objects.requireNonNull(combiner, "combiner");
-      
-      // INITIAL IMPL TO GET STARTED - validate interface and test
-      // explore an impl with no new oplets
-      //
-      // This is the most lightweight impl possible wrt no intermediate streams
-      // i.e., all of the processing is handled within a single injected map()
-      // 
-      // TODO: want to create ExecutorService using provider's ThreadFactory service.
-      //       Can't get RuntimeServicesSupplier from a stream.
-      //
-      // Note, we impose this "non-null mapper result" requirement so as
-      // to enable alternative implementations that might be burdened if
-      // null results were allowed.
-      // The implementation below could easily handle null results w/o
-      // losing synchronization, with the combiner needing to deal with
-      // a null result in the list it's given.
-      
-      AtomicReference<ExecutorService> executorRef = new AtomicReference<>();
-      
-      return stream.map(tuple -> {
-        if (executorRef.get() == null) {
-          executorRef.compareAndSet(null, Executors.newFixedThreadPool(Math.min(mappers.size(), 20)));
-        }
-        ExecutorService executor = executorRef.get();
-        List<U> results = new ArrayList<>(Collections.nCopies(mappers.size(), null));
-        List<Future<?>> futures = new ArrayList<>(mappers.size());
-
-        // Submit a task for each mapper invocation
-        int ch = 0;
-        for (Function<T,U> mapper : mappers) {
-          final int resultIndx = ch++;
-          Future<?> future = executor.submit(() -> {
-            U result = mapper.apply(tuple);
-            if (result == null)
-              throw new IllegalStateException("mapper index "+resultIndx+" returned null");
-            results.set(resultIndx, result); 
-          });
-          futures.add(future);
-        }
-        // Await completion of all
-        for (Future<?> future : futures) {
-          try {
-            future.get();
-          } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            throw new RuntimeException("mapper interrupted", e);
-          } catch (Exception e) {
-            throw new RuntimeException("mapper threw", e);
-          }
-        }
-        // Run the combiner
-        R result = combiner.apply(results);
-        return result;
-      });
-      
-    }
-
-    /**
-     * Perform analytics concurrently.
-     * <P>
-     * Process input tuples one at at time, invoking the specified
-     * analytics ({@code pipelines}) concurrently, combine the results,
-     * and then process the next input tuple in the same manner.
-     * </P><P>
-     * Logically, instead of doing this:
-     * <pre>{@code
-     * sensorReadings<T> -> A1pipeline -> A2pipeline -> A3pipeline -> results<R>
-     * }</pre>
-     * create a graph that's logically like this:
-     * <pre>{@code
-     * - 
-     *                      |->  A1pipeline  ->|
-     * sensorReadings<T> -> |->  A2pipeline  ->| -> result<R>
-     *                      |->  A3pipeline  ->|
-     * }</pre>
-     * </P><P>
-     * The typical use case for this is when an application has a collection
-     * of independent analytics to perform on each tuple and the analytics
-     * are sufficiently long running such that performing them concurrently
-     * is desired.
-     * </P><P>
-     * Note, this is in contrast to "parallel" stream processing,
-     * which in Java8 Streams and other contexts means processing multiple
-     * tuples in parallel, each on a replicated processing pipeline.
-     * </P><P>
-     * Threadsafety - one of the following must be true:
-     * <ul>
-     * <li>the tuples from {@code stream} are threadsafe</li>
-     * <li>the {@code pipelines} do not modify the input tuples</li>
-     * <li>the {@code pipelines} provide their own synchronization controls
-     *     to protect concurrent modifications of the input tuples</li>
-     * </ul>
-     * </P><P>
-     * Logically, a thread is allocated for each of the {@code pipelines}.
-     * The actual degree of concurrency may be {@link TopologyProvider} dependent.
-     * </P>
-     * 
-     * @param <T> Tuple type on input stream.
-     * @param <U> Tuple type generated by pipelines.
-     * @param <R> Tuple type of the result.
-     * 
-     * @param stream input stream
-     * @param pipelines a list of functions to add a pipeline to the topology.
-     *                 Each {@code pipeline.apply()} is called with {@code stream}
-     *                 as the input, yielding the pipeline's result stream.
-     *                 For each input tuple, a pipeline MUST create exactly one output tuple.
-     *                 Tuple flow into the pipelines will cease if that requirement
-     *                 is not met.
-     * @param combiner a function that creates a result stream from a stream
-     *                 whose tuples are the list of each pipeline's result.
-     *                 The input tuple list's order is 1:1 with the {@code pipelines} list.
-     *                 I.e., list entry [0] is the result from pipelines[0],
-     *                 list entry [1] is the result from pipelines[1], etc.
-     * @return result stream
-     */
-    public static <T,U,R> TStream<R> concurrent(TStream<T> stream, List<Function<TStream<T>,TStream<U>>> pipelines, Function<TStream<List<U>>,TStream<R>> combiner) {
-      Objects.requireNonNull(stream, "stream");
-      Objects.requireNonNull(pipelines, "pipelines");
-      Objects.requireNonNull(combiner, "combiner");
-      
-      // Summary of what's below:
-      //           |-> isolate(1) -> p1 -> |
-      // stream -> |-> isolate(1) -> p2 -> |-> barrier(10) -> combiner 
-      //           |-> isolate(1) -> p3 -> |
-      //                . . .
-
-      int barrierQueueCapacity = 10;  // don't preclude pipelines from getting ahead some.
-      
-      // Add concurrent (isolated) fanouts
-      List<TStream<T>> fanouts = new ArrayList<>(pipelines.size());
-      for (int i = 0; i < pipelines.size(); i++)
-        fanouts.add(isolate(stream, 1).tag("concurrent.isolated-ch"+i));
-      
-      // Add pipelines
-      List<TStream<U>> results = new ArrayList<>(pipelines.size());
-      int ch = 0;
-      for (Function<TStream<T>,TStream<U>> pipeline : pipelines) {
-        results.add(pipeline.apply(fanouts.get(ch)).tag("concurrent-ch"+ch));
-        ch++;
-      }
-      
-      // Add the barrier
-      TStream<List<U>> barrier = barrier(results, barrierQueueCapacity).tag("concurrent.barrier");
-      
-      // Add the combiner
-      return combiner.apply(barrier);
-    }
-
-    /**
-     * A tuple synchronization barrier.
-     * <P>
-     * Same as {@code barrier(others, 1)}
-     * </P>
-     * @see #barrier(List, int)
-     */
-    public static <T> TStream<List<T>> barrier(List<TStream<T>> streams) {
-      return barrier(streams, 1);
-    }
-
-    /**
-     * A tuple synchronization barrier.
-     * <P>
-     * A barrier has n input streams with tuple type {@code T}
-     * and one output stream with tuple type {@code List<T>}.
-     * Once the barrier receives one tuple on each of its input streams,
-     * it generates an output tuple containing one tuple from each input stream.
-     * It then waits until it has received another tuple from each input stream.
-     * </P><P>
-     * Input stream 0's tuple is in the output tuple's list[0],
-     * stream 1's tuple in list[1], and so on.
-     * </P><P>
-     * The barrier's output stream is isolated from the input streams.
-     * </P><P>
-     * The barrier has a queue of size {@code queueCapacity} for each
-     * input stream.  When a tuple for an input stream is received it is
-     * added to its queue.  The stream will block if the queue is full.
-     * </P>
-     *
-     * @param <T> Type of the tuple.
-     * 
-     * @param streams the list of input streams
-     * @param queueCapacity the size of each input stream's queue
-     * @return the output stream
-     * @see Barrier
-     */
-    public static <T> TStream<List<T>> barrier(List<TStream<T>> streams, int queueCapacity) {
-      List<TStream<T>> others = new ArrayList<>(streams);
-      TStream<T> s1 = others.remove(0);
-      return s1.fanin(new Barrier<T>(queueCapacity), others);
-    }
- 
-}
diff --git a/api/topology/src/test/java/org/apache/edgent/test/topology/PlumbingTest.java b/api/topology/src/test/java/org/apache/edgent/test/topology/PlumbingTest.java
index 4a347ed..644ac4f 100644
--- a/api/topology/src/test/java/org/apache/edgent/test/topology/PlumbingTest.java
+++ b/api/topology/src/test/java/org/apache/edgent/test/topology/PlumbingTest.java
@@ -64,7 +64,7 @@
         // delay stream
         starts = PlumbingStreams.blockingDelay(starts, 300, TimeUnit.MILLISECONDS);
         
-        // calculate display
+        // calculate delay
         starts = starts.modify(v -> System.currentTimeMillis() - v);
         
         starts = starts.filter(v -> v >= 300);
@@ -161,67 +161,27 @@
     }
     
     @Test
-    public void testPressureReliever() throws Exception {
-		// Timing variances on shared machines can cause this test to fail
-		assumeTrue(!Boolean.getBoolean("edgent.build.ci"));
+    public void testPressureRelieverDrop() throws Exception {
 
         Topology topology = newTopology();
         
-        TStream<TimeAndId> raw = topology.poll(() -> new TimeAndId(), 10, TimeUnit.MILLISECONDS);
-           
+        // Verify the pressureReliever drops and retains the most recent when
+        // backpressure exists.
+        //
+        // Here, all the tuples hit the reliever at once, the downstream processing (oneShotDelay)
+        // causes a backup causing the reliever's queue to become full and drop tuples.
+        // The first tuple should be processed, then the last (most recent) N (N==queue depth).
         
-        TStream<TimeAndId> pr = PlumbingStreams.pressureReliever(raw, Functions.unpartitioned(), 5);
+        String[] tuples = {"A", "B", "C", "D", "E", "F", "G", "H"};
+        String[] expTuples = {"A", "F", "G", "H"};  // with queue depth of 3
+        TStream<String> raw = topology.strings(tuples);
         
-        // insert a blocking delay acting as downstream operator that cannot keep up
-        TStream<TimeAndId> slow = PlumbingStreams.blockingDelay(pr, 200, TimeUnit.MILLISECONDS);
+        TStream<String> pr = PlumbingStreams.pressureReliever(raw, Functions.unpartitioned(), 3);
         
-        // calculate the delay
-        TStream<TimeAndId> slowM = slow.modify(v -> new TimeAndId(v));
+        TStream<String> pr2 = PlumbingStreams.blockingOneShotDelay(pr, 1, TimeUnit.SECONDS);
         
-        // Also process raw that should be unaffected by the slow path
-        TStream<String> processed = raw.asString();
-        
-        
-        Condition<Long> tcSlowCount = topology.getTester().atLeastTupleCount(slow, 20);
-        Condition<List<TimeAndId>> tcRaw = topology.getTester().streamContents(raw);
-        Condition<List<TimeAndId>> tcSlow = topology.getTester().streamContents(slow);
-        Condition<List<TimeAndId>> tcSlowM = topology.getTester().streamContents(slowM);
-        Condition<List<String>> tcProcessed = topology.getTester().streamContents(processed);
-        complete(topology, tcSlowCount);
-        
-        assertTrue(tcProcessed.getResult().size() > tcSlowM.getResult().size());
-        for (TimeAndId delay : tcSlowM.getResult())
-            assertTrue("delay:"+delay, delay.ms < 300);
-
-        // Must not lose any tuples in the non relieving path
-        Set<TimeAndId> uniq = new HashSet<>(tcRaw.getResult());
-        assertEquals(tcRaw.getResult().size(), uniq.size());
-
-        // Must not lose any tuples in the non relieving path
-        Set<String> uniqProcessed = new HashSet<>(tcProcessed.getResult());
-        assertEquals(tcProcessed.getResult().size(), uniqProcessed.size());
-        
-        assertEquals(uniq.size(), uniqProcessed.size());
-           
-        // Might lose tuples, but must not have send duplicates
-        uniq = new HashSet<>(tcSlow.getResult());
-        assertEquals(tcSlow.getResult().size(), uniq.size());
-    }
-    
-    @Test
-    public void testPressureRelieverWithInitialDelay() throws Exception {
-
-        Topology topology = newTopology();
-        
-        
-        TStream<String> raw = topology.strings("A", "B", "C", "D", "E", "F", "G", "H");
-        
-        TStream<String> pr = PlumbingStreams.pressureReliever(raw, v -> 0, 100);
-        
-        TStream<String> pr2 = PlumbingStreams.blockingOneShotDelay(pr, 5, TimeUnit.SECONDS);
-        
-        Condition<Long> tcCount = topology.getTester().tupleCount(pr2, 8);
-        Condition<List<String>> contents = topology.getTester().streamContents(pr2, "A", "B", "C", "D", "E", "F", "G", "H");
+        Condition<Long> tcCount = topology.getTester().tupleCount(pr2, expTuples.length);
+        Condition<List<String>> contents = topology.getTester().streamContents(pr2, expTuples);
         complete(topology, tcCount);
         
         assertTrue(tcCount.valid());
@@ -229,6 +189,102 @@
     }
     
     @Test
+    public void testPressureRelieverNoDrop() throws Exception {
+
+        Topology topology = newTopology();
+        
+        // Same pipeline config as testPressureRelieverDrop but the reliever queue is
+        // big enough to avoid drops
+        String[] tuples = {"A", "B", "C", "D", "E", "F", "G", "H"};
+        TStream<String> raw = topology.strings(tuples);
+        
+        TStream<String> pr = PlumbingStreams.pressureReliever(raw, Functions.unpartitioned(), 100);
+        
+        TStream<String> pr2 = PlumbingStreams.blockingOneShotDelay(pr, 1, TimeUnit.SECONDS);
+        
+        Condition<Long> tcCount = topology.getTester().tupleCount(pr2, tuples.length);
+        Condition<List<String>> contents = topology.getTester().streamContents(pr2, tuples);
+        complete(topology, tcCount);
+        
+        assertTrue(tcCount.valid());
+        assertTrue(contents.valid());
+    }
+    
+    @Test
+    public void testPressureRelieverContinuous() throws Exception {
+		// Timing variances on shared machines can cause this test to fail
+		//assumeTrue(!Boolean.getBoolean("edgent.build.ci"));
+
+		// Try to verify more continuous reliever behavior instead of just the
+		// the other pressure reliever tests where the backpressure only exists
+		// at the beginning.
+		//
+		// Generate @ 100tps, consume @ 5tps.  
+		// With reliever depth=1, roughly should process every 20th tuple, with essentially
+		// no delay in the queue (certainly less than say 50% of the consumer delay, hence < 0.5 * 200ms)
+		
+        Topology topology = newTopology();
+
+        TStream<TimeAndId> raw = topology.poll(() -> new TimeAndId(), 10, TimeUnit.MILLISECONDS);
+        
+        TStream<TimeAndId> pr = PlumbingStreams.pressureReliever(raw, Functions.unpartitioned(), 1);
+        
+        TStream<TimeAndId> slow = PlumbingStreams.blockingDelay(pr, 200, TimeUnit.MILLISECONDS);
+        
+        // calculate the delay (queue time + consumer processing)
+        TStream<TimeAndId> slowM = slow.modify(v -> new TimeAndId(v));
+        
+        // Also process raw that should be unaffected by the slow path
+        TStream<TimeAndId> processed = raw.filter(t -> true);
+        
+        
+        Condition<Long> tcSlowMCount = topology.getTester().atLeastTupleCount(slowM, 10);
+        Condition<List<TimeAndId>> tcSlowM = topology.getTester().streamContents(slowM);
+        Condition<List<TimeAndId>> tcProcessed = topology.getTester().streamContents(processed);
+        complete(topology, tcSlowMCount);
+        
+        System.out.println(String.format("testPressureRelieverContinuous() fastCnt:%d slowCnt:%d",
+                tcProcessed.getResult().size(), tcSlowM.getResult().size()));
+        System.out.println("slow: "+tcSlowM.getResult());
+
+        // No lost tuples in the fast path (successive Ids, starting @ 1)
+        assertEquals("fastpath tuples dropped",
+                tcProcessed.getResult().size(),
+                tcProcessed.getResult().get(tcProcessed.getResult().size()-1).id);
+
+        // No dup tuples in the fast path
+        Set<TimeAndId> uniqRaw = new HashSet<>(tcProcessed.getResult());
+        assertEquals("fastpath tuples duplicated", tcProcessed.getResult().size(), uniqRaw.size());
+
+        // fastpath count should be roughly 20x the slow delayed/relieved count
+        assertTrue("rawCnt:"+tcProcessed.getResult().size()+" slowMCnt:"+tcSlowM.getResult().size(),
+                tcProcessed.getResult().size() >= 15 * +tcSlowM.getResult().size());
+        
+        // slow should process roughly every 20th tuple... not successive ones
+        TimeAndId prevId = null;
+        for (TimeAndId id : tcSlowM.getResult()) {
+            if (prevId == null) {
+                // should have processed the 1st tuple
+                assertEquals("slow firstId", 1, id.id);
+            }
+            else {
+                // seems like this could be sensitive to host load
+                assertTrue("slow ids prevId:"+prevId+" id:"+id,
+                        id.id >= prevId.id + 15
+                        && id.id <= prevId.id + 25);
+            }
+            prevId = id;
+        }
+        
+        // every slow tuple should be processed near instantaneously - shouldn't wait
+        // long in the queue.
+        for (TimeAndId id : tcSlowM.getResult()) {
+            assertTrue("slow delays prevId:"+prevId+" id:"+id,
+                    id.ms <= 300);  // 200ms consumer processing + up to %50 of that waiting  
+        }
+    }
+    
+    @Test
     public void testValveState() throws Exception {
         Valve<Integer> valve = new Valve<>();
         assertTrue(valve.isOpen());
@@ -561,10 +617,6 @@
     
     @Test
     public void testParallelBalanced() throws Exception {
-        // May need tweak validation sensitivity or add this:
-        // Timing variances on shared machines can cause this test to fail
-        // assumeTrue(!Boolean.getBoolean("edgent.build.ci"));
-
         Topology top = newTopology("testParallelBalanced");
         
         // arrange for even channels to process ~2x as many as odd channels.
@@ -576,6 +628,7 @@
         
         int width = 4;
         int tupCnt = 60;
+        int expEvenChCnt = 2 * (tupCnt / 3);   // even:2/3rds, odd:1/3rd 
         Integer[] resultTuples = new Integer[tupCnt];
         for (int i = 0; i < tupCnt; i++)
           resultTuples[i] = i;
@@ -616,17 +669,19 @@
           assertTrue("expMinSerialDuration="+expMinSerialDuration+" actDuration="+actDuration, 
               actDuration < 0.5 * expMinSerialDuration);
         
-        int evenChCounts = 0;
-        int oddChCounts = 0;
+        // Verify the balancing seemed to work.
+        // On loaded systems we've seen eCnt:37 oCnt:23.  Settle for expEvenCnt +- 15%
+        int thresholdCnt = (int) (expEvenChCnt * 0.15);
+        int evenChCnt = 0;
         for (int ch = 0; ch < width; ch++) {
-          assertTrue(chCounts[ch].get() != 0);
+          assertTrue("ch:"+ch, chCounts[ch].get() != 0);
           if (ch % 2 == 0)
-            evenChCounts += chCounts[ch].get();
-          else
-            oddChCounts += chCounts[ch].get();
+            evenChCnt += chCounts[ch].get();
         }
-        assertTrue(oddChCounts > 0.4 * evenChCounts
-            && oddChCounts < 0.6 * evenChCounts);
+        assertTrue(
+                String.format("evenChCnt:%d expEvenChCnt:%d +-:%d", evenChCnt, expEvenChCnt, thresholdCnt),
+                evenChCnt >= (expEvenChCnt - thresholdCnt)
+                && evenChCnt <= (expEvenChCnt + thresholdCnt)); 
     }
     
 //    @Test
diff --git a/api/topology/src/test/java/org/apache/edgent/test/topology/TStreamTest.java b/api/topology/src/test/java/org/apache/edgent/test/topology/TStreamTest.java
index 7625667..9fae5ed 100644
--- a/api/topology/src/test/java/org/apache/edgent/test/topology/TStreamTest.java
+++ b/api/topology/src/test/java/org/apache/edgent/test/topology/TStreamTest.java
@@ -37,6 +37,7 @@
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 
 import org.apache.edgent.topology.TSink;
@@ -135,7 +136,7 @@
     /**
      * Test Peek. This will only work with an embedded setup.
      * 
-     * @throws Exception
+     * @throws Exception on failure
      */
     @Test
     public void testPeek() throws Exception {
@@ -600,7 +601,7 @@
     /**
      * Test Union with itself.
      * 
-     * @throws Exception
+     * @throws Exception on failure
      */
     @Test
     public void testUnionWithSelf() throws Exception {
@@ -745,14 +746,26 @@
                 Topology t = newTopology();
                 TStream<String> s = t.strings("a", "b", "c", "d", "e", "f", "g", "h");
                 // Throw on the 8th tuple
-                s.sink((tuple) -> { if ("h".equals(tuple)) throw new RuntimeException("Expected Test Exception");});
+                s.sink((tuple) -> { if ("h".equals(tuple)) throw new RuntimeException("MTWE Expected Test Exception");});
                 // Expect 7 tuples out of 8
                 Condition<Long> tc = t.getTester().tupleCount(s, 7);
-                complete(t, tc);
+//                complete(t, tc);
+                try {
+                    complete(t, tc);
+                } catch (Exception e) {
+                    System.err.println("MTWE complete() threw e:"+e);
+                    throw e;
+                }
                 return true;
             });
         }
-        waitForCompletion(completer, executions);
+//        waitForCompletion(completer, executions);
+        try {
+            waitForCompletion(completer, executions);
+        } catch (Exception e) {
+            System.err.println("MTWE waitForCompletion() threw e:"+e);
+            throw e;
+        }
     }
     
     /**
@@ -762,24 +775,58 @@
     @Test
     public void testMultiTopologyPollWithError() throws Exception {
 
+        /*
+         * It's unclear exactly what this test is supposed to achieve
+         * (hence unclear how to ensure its achieving it).
+         * Is it just trying to verify that a failure in one topology/job
+         * doesn't affect the execution of another?
+         * 
+         * The way the test is written I'm not sure there's any guarantee
+         * that the "Expected Exception" will be generated the appropriate
+         * number of times.
+         * Is it possible the completion condition could get evaluated
+         * true (having seen the 7th tuple) before the 8th tuple is generated
+         * and processed by the sink fn raising the exception, resulting in
+         * the job being closed... before the 8th is generated and processed?
+         * I'm also seeing more "Expected Test Exception" traces than I expected.
+         * 
+         * Annotate this and the MPWE test a bit to help understand what we're seeing
+         * in the output.
+         */
         int executions = 4;
         ExecutorCompletionService<Boolean> completer = new ExecutorCompletionService<>(
                 Executors.newFixedThreadPool(executions));
+        final AtomicInteger excCnt = new AtomicInteger();
         for (int i = 0; i < executions; i++) {
             completer.submit(() -> {
                 Topology t = newTopology();
                 AtomicLong n = new AtomicLong(0);
                 TStream<Long> s = t.poll(() -> n.incrementAndGet(), 10, TimeUnit.MILLISECONDS);
                 // Throw on the 8th tuple
-                s.sink((tuple) -> { if (8 == n.get()) throw new RuntimeException("Expected Test Exception");});
+                s.sink((tuple) -> { if (8 == n.get()) throw new RuntimeException("MTPWE Expected Test Exception # "+excCnt.incrementAndGet());});
                 // Expect 7 tuples out of 8
                 Condition<Long> tc = t.getTester().tupleCount(s, 7);
-                complete(t, tc);
+//              complete(t, tc);
+                try {
+                    complete(t, tc);
+                } catch (Exception e) {
+                    // we're receiving the CancellationException here (and it percolates through waitForCompletion
+                    System.err.println("MTPWE complete() threw e:"+e);
+                    e.printStackTrace();
+                    throw e;
+                }
                 return true;
             });
         }
-        waitForCompletion(completer, executions);
-    }
+//      waitForCompletion(completer, executions);
+        try {
+            waitForCompletion(completer, executions);
+        } catch (Exception e) {
+            System.err.println("MTPWE waitForCompletion() threw e:"+e);
+            e.printStackTrace();
+            throw e;
+        }
+   }
     
     @Test
     public void testJoinWithWindow() throws Exception{
@@ -853,15 +900,29 @@
         Condition<Long> tc = t.getTester().tupleCount(joinsHappened, 100);
         complete(t, tc);      
     }
+    
+    private static long getTimeoutValue(long timeout, TimeUnit units) {
+        // try to protect the tests from timing out prematurely
+        // in the face of overloaded/slow build/test servers.
+        if (Boolean.getBoolean("edgent.build.ci")) {
+            // could do something like base the decision of the current value of timeout and/or units
+            return timeout * 2;  // try to minimize
+        }
+        return timeout;
+    }
 
     private void waitForCompletion(ExecutorCompletionService<Boolean> completer, int numtasks) throws ExecutionException {
         int remainingTasks = numtasks;
+        long getFutureTimeout = 4;
+        TimeUnit getFutureTimeoutUnits = TimeUnit.SECONDS;
+        getFutureTimeout = getTimeoutValue(getFutureTimeout, getFutureTimeoutUnits);
         while (remainingTasks > 0) {
             try {
-                Future<Boolean> completed = completer.poll(4, TimeUnit.SECONDS);
+                Future<Boolean> completed = completer.poll(getFutureTimeout, getFutureTimeoutUnits);
                 if (completed == null) {
-                    System.err.println("Completer timed out");
-                    throw new RuntimeException(new TimeoutException());
+                    String msg = String.format("Completer timed out: %d%s timeout", getFutureTimeout, getFutureTimeoutUnits.toString());
+                    System.err.println(msg);
+                    throw new RuntimeException(new TimeoutException(msg));
                 }
                 else {
                     completed.get();
diff --git a/api/topology/src/test/java/org/apache/edgent/test/topology/TopologyTest.java b/api/topology/src/test/java/org/apache/edgent/test/topology/TopologyTest.java
index cc1c059..033baa9 100644
--- a/api/topology/src/test/java/org/apache/edgent/test/topology/TopologyTest.java
+++ b/api/topology/src/test/java/org/apache/edgent/test/topology/TopologyTest.java
@@ -134,23 +134,23 @@
             job = jf.get();
             assertEquals(Job.State.RUNNING, job.getCurrentState());
             
-            setPollFrequency(s, 100, TimeUnit.MILLISECONDS);
+            setPollFrequency(s, 10, TimeUnit.MILLISECONDS);
             cnt.set(0);
-            Thread.sleep(TimeUnit.SECONDS.toMillis(3));
+            Thread.sleep(TimeUnit.SECONDS.toMillis(1));
             int curCnt = cnt.get();
-            assertTrue("curCnt="+curCnt, curCnt >= 20);
-            
-            setPollFrequency(s, 1, TimeUnit.SECONDS);
-            cnt.set(0);
-            Thread.sleep(TimeUnit.SECONDS.toMillis(3));
-            curCnt = cnt.get();
-            assertTrue("curCnt="+curCnt, curCnt >= 2 && curCnt <= 4);
+            assertTrue("curCnt="+curCnt, curCnt >= 70);
             
             setPollFrequency(s, 100, TimeUnit.MILLISECONDS);
             cnt.set(0);
-            Thread.sleep(TimeUnit.SECONDS.toMillis(3));
+            Thread.sleep(TimeUnit.SECONDS.toMillis(1));
             curCnt = cnt.get();
-            assertTrue("curCnt="+curCnt, curCnt >= 20);
+            assertTrue("curCnt="+curCnt, curCnt >= 5 && curCnt <= 20);
+            
+            setPollFrequency(s, 10, TimeUnit.MILLISECONDS);
+            cnt.set(0);
+            Thread.sleep(TimeUnit.SECONDS.toMillis(1));
+            curCnt = cnt.get();
+            assertTrue("curCnt="+curCnt, curCnt >= 70);
         }
         finally {
             if (job != null)
diff --git a/api/window/build.gradle b/api/window/build.gradle
deleted file mode 100644
index 19b441d..0000000
--- a/api/window/build.gradle
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:function'
-  addTargetDirCoreExtJarDependencies 'compile'
-
-  // N.B. root project adds test common dependencies
-}
diff --git a/api/window/pom.xml b/api/window/pom.xml
new file mode 100644
index 0000000..0f44025
--- /dev/null
+++ b/api/window/pom.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-api</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-api-window</artifactId>
+
+  <name>Apache Edgent (Java 8): API: Window</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-function</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/api/window/src/test/java/org/apache/edgent/test/window/WindowTest.java b/api/window/src/test/java/org/apache/edgent/test/window/WindowTest.java
index 45d4c61..98f04ea 100644
--- a/api/window/src/test/java/org/apache/edgent/test/window/WindowTest.java
+++ b/api/window/src/test/java/org/apache/edgent/test/window/WindowTest.java
@@ -20,6 +20,7 @@
 
 import static org.apache.edgent.function.Functions.unpartitioned;
 import static org.apache.edgent.window.Policies.alwaysInsert;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
@@ -35,6 +36,7 @@
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.edgent.function.BiConsumer;
 import org.apache.edgent.window.InsertionTimeList;
@@ -293,7 +295,7 @@
         
         window.registerScheduledExecutorService(new ScheduledThreadPoolExecutor(5));
         
-        long endTime = System.currentTimeMillis() + 8000;
+        long endTime = System.currentTimeMillis() + 4000;
         List<Thread> threads = new ArrayList<>();
         int NUM_THREADS = 10;
         // Create 10 threads. Each inserts at 1,000 Hz
@@ -375,7 +377,7 @@
             window.insert(1);
         }, 0, 10, TimeUnit.MILLISECONDS);
 
-        Thread.sleep(11000);
+        Thread.sleep(4000);
         sf.cancel(true);
         double tolerance = .08;
         for(int i = 0; i < numBatches.size(); i++){
@@ -415,7 +417,7 @@
         		window.insert(i);
         }, 0, 1, TimeUnit.MILLISECONDS);
 
-        Thread.sleep(11000);
+        Thread.sleep(4000);
         sf.cancel(true);
         try {
           sf.get();
@@ -457,19 +459,22 @@
         
         window.registerScheduledExecutorService(new ScheduledThreadPoolExecutor(5));
 
+        AtomicInteger count = new AtomicInteger();
+        int MAX_TUP_CNT = 300;
         ScheduledFuture<?> sf = ses.scheduleAtFixedRate(new Runnable(){
-            private int count = 0;
             @Override
             public void run() {
-                if(count < 1000){
-                    window.insert(count++);
+                if(count.get() < MAX_TUP_CNT){
+                    window.insert(count.incrementAndGet());
                 }
             }
             
         }, 0, 10, TimeUnit.MILLISECONDS);
 
-        Thread.sleep(11000);
+        long insertMsec = MAX_TUP_CNT * 10 /*10msec/tup*/;
+        Thread.sleep(insertMsec + 1000/*extra sec*/);
         sf.cancel(true);
+        assertEquals("Invalid test", MAX_TUP_CNT, count.get());
         int numTuples = 0;
         for(int i = 0; i < batches.size() - 1; i++){
             List<Integer> batch = batches.get(i);
@@ -480,7 +485,9 @@
         }
         
         numTuples += batches.get(batches.size() -1).size();
-        assertTrue("Number of tuples submitted (1000) != number of tuples processed in batch (" + numTuples + ")", numTuples == 1000);
+        assertEquals("Number of batch tuples", count.get(), numTuples);
+        assertTrue("Number of batches exp:"+MAX_TUP_CNT/100+" got:"+batches.size(),
+                withinToleranceAmt((double)MAX_TUP_CNT/100, (double)batches.size(), 1)); // +/- 1
     }
     
     private void assertOnTimeEvictions(List<Long> diffs) {
@@ -497,4 +504,10 @@
         return (actual < highBound && actual > lowBound);
     }
 
+    public static boolean withinToleranceAmt(double expected, Double actual, double toleranceAmt) {
+        double lowBound = expected - toleranceAmt;
+        double highBound = expected + toleranceAmt;
+        return (actual <= highBound && actual >= lowBound);
+    }
+
 }
diff --git a/apps/.classpath b/apps/.classpath
deleted file mode 100644
index a639293..0000000
--- a/apps/.classpath
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" path="iot/src/main/java"/>
-	<classpathentry kind="src" path="iot/src/test/java"/>
-    <classpathentry kind="src" path="runtime/src/main/java"/>
-    <classpathentry kind="src" path="runtime/src/test/java"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/ext"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/api"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/connectors"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/providers"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/spi"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/runtime"/>
-	<classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/apps/.project b/apps/.project
deleted file mode 100644
index 142fc54..0000000
--- a/apps/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>apps</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-	</natures>
-</projectDescription>
diff --git a/apps/iot/build.gradle b/apps/iot/build.gradle
deleted file mode 100644
index 0242187..0000000
--- a/apps/iot/build.gradle
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:topology'
-  addTargetDirProjectJarDependency 'compile', ':connectors:pubsub'
-  addTargetDirProjectJarDependency 'compile', ':connectors:iot'
-  
-  addTargetDirProjectJarDependency 'testCompile', ':providers:direct'
-  // N.B. root project adds test common dependencies
-}
diff --git a/apps/iot/pom.xml b/apps/iot/pom.xml
new file mode 100644
index 0000000..116117f
--- /dev/null
+++ b/apps/iot/pom.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-apps</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-apps-iot</artifactId>
+
+  <name>Apache Edgent (Java 8): Apps: IoT</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-connectors-iot</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-connectors-pubsub</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/apps/pom.xml b/apps/pom.xml
new file mode 100644
index 0000000..8c83e22
--- /dev/null
+++ b/apps/pom.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-parent</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-apps</artifactId>
+  <packaging>pom</packaging>
+
+  <name>Apache Edgent (Java 8): Apps</name>
+
+  <modules>
+    <module>iot</module>
+    <module>runtime</module>
+  </modules>
+
+</project>
diff --git a/apps/runtime/build.gradle b/apps/runtime/build.gradle
deleted file mode 100644
index fefbe85..0000000
--- a/apps/runtime/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:topology'
-  addTargetDirProjectJarDependency 'compile', ':runtime:jobregistry'
-  addTargetDirProjectJarDependency 'compile', ':runtime:appservice'
-  
-  addTargetDirProjectJarDependency 'testCompile', ':providers:direct'
-  addTargetDirProjectJarDependency 'testCompile', ':runtime:jmxcontrol'
-  // N.B. root project adds test common dependencies
-}
diff --git a/apps/runtime/pom.xml b/apps/runtime/pom.xml
new file mode 100644
index 0000000..bcbbd86
--- /dev/null
+++ b/apps/runtime/pom.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-apps</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-apps-runtime</artifactId>
+
+  <name>Apache Edgent (Java 8): Apps: Runtime</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-runtime-jobregistry</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-runtime-jmxcontrol</artifactId>
+      <version>1.2.0</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/apps/runtime/src/main/java/org/apache/edgent/apps/runtime/JobMonitorApp.java b/apps/runtime/src/main/java/org/apache/edgent/apps/runtime/JobMonitorApp.java
index 2a90cc1..afe9575 100644
--- a/apps/runtime/src/main/java/org/apache/edgent/apps/runtime/JobMonitorApp.java
+++ b/apps/runtime/src/main/java/org/apache/edgent/apps/runtime/JobMonitorApp.java
@@ -160,7 +160,7 @@
             // Wait for the job to complete
             long startWaiting = System.currentTimeMillis();
             for (long waitForMillis = Controls.JOB_HOLD_AFTER_CLOSE_SECS * 1000;
-                    waitForMillis < 0;
+                    waitForMillis >= 0;
                     waitForMillis -= 100) {
                 if (jobMbean.getCurrentState() == Job.State.CLOSED)
                     break;
diff --git a/binary-release/LICENSE b/binary-release/LICENSE
deleted file mode 100644
index 39e4bcd..0000000
--- a/binary-release/LICENSE
+++ /dev/null
@@ -1,401 +0,0 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) 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. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "{}"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright {yyyy} {name of copyright owner}
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
-======================================
-APACHE EDGENT SUBCOMPONENTS:
-
-This Apache Edgent binary release includes a number of subcomponents with
-separate copyright notices and license terms.  Your use of these
-components is subject to the terms and conditions of the following licenses.
-
-The binary release includes some binary content with associated
-Eclipse Public License ("EPL-1.0") and 
-Common Development and Distribution License ("CDDL-1.0" and CDDL-1.1")
-licenses as noted below.
-
---------------------------------
-ext/    # common dependencies for all Edgent components
-  gson-2.2.4.jar                    com.google.code.gson:gson:2.2.4
-        LICENSE: AL-2.0             For details, see licenses/binary-release/gson-2.2.4.AL-2.0
-        
-  metrics-core-3.1.2.jar            io.dropwizard.metrics:metrics-core:3.1.2
-        LICENSE: AL-2.0             For details, see licenses/binary-release/metrics-core-3.1.2.AL-2.0
-        
-  slf4j-api-1.7.12.jar              org.slf4j:slf4j-api:1.7.12
-  slf4j-jdk14-1.7.12.jar
-        LICENSE: MIT                For details, see licenses/binary-release/slf4j-1.7.12.MIT
-
---------------------------------
-analytics/math3/ext/
-  commons-math3-3.4.1.jar           org.apache.commons:commons-math3:3.4.1
-        LICENSE: AL-2.0             For details, see licenses/binary-release/commons-math3-3.4.1.AL-2.0
-
---------------------------------
-connectors/http/ext
-  httpclient-4.5.1.jar              org.apache.httpcomponents:httpclient:4.5.1
-        LICENSE: AL-2.0             For details, see licenses/binary-release/httpclient-4.5.1.AL-2.0
-        
-  httpcore-4.4.4.jar                org.apache.httpcomponents:httpcore:4.4.4
-        LICENSE: AL-2.0             For details, see licenses/binary-release/httpcore-4.4.4.AL-2.0
-        
-  commons-codec-1.9.jar             org.apache.commons:commons-codec:1.9
-        LICENSE: AL-2.0             For details, see licenses/binary-release/commons-codec-1.9.AL-2.0
-        
-  commons-logging-1.2.jar           org.apache.commons:commons-logging:1.2
-        LICENSE: AL-2.0             For details, see licenses/binary-release/commons-logging-1.2.AL-2.0
-        
---------------------------------
-connectors/iotp/ext
-  watson-iot-0.2.2.jar              com.ibm.messaging:watson-iot:0.2.2
-        LICENSE: EPL-1.0            For details, see licenses/binary-release/watson-iot-0.2.2.EPL-1.0
-                                    Only binary form content is included.
-                                    https://mvnrepository.com/artifact/com.ibm.messaging/watson-iot/0.2.2
-        
-  commons-codec-1.10.jar            org.apache.commons:commons-codec:1.10
-        LICENSE: AL-2.0             For details, see licenses/binary-release/commons-codec-1.10.AL-2.0
-        
-  commons-lang3-3.4.jar             org.apache.commons:commons-lang3:3.4
-        LICENSE: AL-2.0             For details, see licenses/binary-release/commons-lang3-3.4.AL-2.0
-        
-  commons-logging-1.2.jar           org.apache.commons:commons-logging:1.2
-        LICENSE: AL-2.0             For details, see licenses/binary-release/commons-logging-1.2.AL-2.0
-        
-  commons-net-3.3.jar               org.apache.commons:commons-net:3.3
-        LICENSE: AL-2.0             For details, see licenses/binary-release/commons-net-3.3.AL-2.0
-        
-  gson-2.6.1.jar                    com.google.code.gson:gson:2.6.1
-        LICENSE: AL-2.0             For details, see licenses/binary-release/gson-2.6.1.AL-2.0
-        
-  httpclient-4.5.1.jar              org.apache.httpcomponents:httpclient:4.5.1
-        LICENSE: AL-2.0             For details, see licenses/binary-release/httpclient-4.5.1.AL-2.0
-        
-  httpcore-4.4.3.jar                org.apache.httpcomponents:httpcore:4.4.3
-        LICENSE: AL-2.0             For details, see licenses/binary-release/httpcore-4.4.3.AL-2.0
-        
-  joda-time-2.9.2.jar               joda-time:joda-time:2.9.2
-        LICENSE: AL-2.0             For details, see licenses/binary-release/joda-time-2.9.2.AL-2.0
-        
-  log4j-1.2.17.jar                  log4j:log4j:1.2.17
-        LICENSE: AL-2.0             For details, see licenses/binary-release/log4j-1.2.17.AL-2.0
-        
-  org.eclipse.paho.client.mqttv3-1.1.0.jar  org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0
-        LICENSE: EDL-1.0            For details, see licenses/binary-release/mqttv3-1.1.0.EDL-1.0
-        
---------------------------------
-connectors/kafka/ext
-  kafka-clients-0.8.2.2.jar         org.apache.kafka:kafka-clients:0.8.2.2
-        LICENSE: AL-2.0             For details, see licenses/binary-release/kafka-clients-0.8.2.2.AL-2.0
-        
-  kafka_2.10-0.8.2.2.jar            org.apache.kafka:kafka_2.10:0.8.2.2
-        LICENSE: AL-2.0             For details, see licenses/binary-release/kafka-2.10-0.8.2.2.AL-2.0
-        
-  log4j-1.2.16.jar                  log4j:log4j:1.2.16
-        LICENSE: AL-2.0             For details, see licenses/binary-release/log4j-1.2.16.AL-2.0
-        
-  metrics-core-2.2.0.jar            com.yammer.metrics:metrics-core:2.2.0
-        LICENSE: AL-2.0             For details, see licenses/binary-release/metrics-core-2.2.0.AL-2.0
-        
-  scala-library-2.10.4.jar          org.scala-lang:scala-library:2.10.4
-        LICENSE: BSD                For details, see licenses/binary-release/scala-library-2.10.4.BSD
-        
-  zkclient-0.3.jar                  com.101tec:zkclient:0.3
-        LICENSE: AL-2.0             For details, see licenses/binary-release/zkclient-0.3.AL-2.0
-        
-  zookeeper-3.4.6.jar               org.apache.zookeeper:zookeeper:3.4.6
-        LICENSE: AL-2.0             For details, see licenses/binary-release/zookeeper-3.4.6.AL-2.0
-
---------------------------------
-connectors/mqtt/ext
-  org.eclipse.paho.client.mqttv3-1.1.0.jar  org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0
-        LICENSE: EDL-1.0            For details, see licenses/binary-release/mqttv3-1.1.0.EDL-1.0
-
---------------------------------
-connectors/edgent.javax.websocket/ext
-  javax.websocket-api-1.0.jar       javax.websocket:javax.websocket-api:1.0
-        LICENSE: CDDL-1.1           For details, see licenses/binary-release/javax.websocket-api-1.0.CDDL-1.1
-                                    Only binary form content is included.
-                                    https://mvnrepository.com/artifact/javax.websocket/javax.websocket-api/1.0
-
---------------------------------
-connectors/javax.websocket-client/ext
-  javax-websocket-client-impl-9.3.6.v20151106.jar  org.eclipse.jetty.websocket:javax-websocket-client-impl:9.3.6.v20151106
-        LICENSE: AL-2.0               For details, see licenses/binary-release/javax-websocket-client-impl-9.3.6.AL-2.0
-        
-  javax.websocket-api-1.0.jar         javax.websocket:javax.websocket-api:1.0
-        LICENSE: CDDL-1.1             For details, see licenses/binary-release/javax.websocket-api-1.0.CDDL-1.1
-                                      Only binary form content is included.
-                                      https://mvnrepository.com/artifact/javax.websocket/javax.websocket-api/1.0
-        
-  jetty-io-9.3.6.v20151106.jar        org.eclipse.jetty.websocket:javax-websocket-client-impl:9.3.6.v20151106
-        LICENSE: AL-2.0               For details, see licenses/binary-release/jetty-io-9.3.6.AL-2.0
-        
-  jetty-util-9.3.6.v20151106.jar      org.eclipse.jetty.websocket:javax-websocket-client-impl:9.3.6.v20151106              
-        LICENSE: AL-2.0               For details, see licenses/binary-release/jetty-util-9.3.6.AL-2.0
-        
-  websocket-api-9.3.6.v20151106.jar   org.eclipse.jetty.websocket:javax-websocket-client-impl:9.3.6.v20151106
-        LICENSE: AL-2.0               For details, see licenses/binary-release/websocket-api-9.3.6.AL-2.0
-        
-  websocket-client-9.3.6.v20151106.jar  org.eclipse.jetty.websocket:javax-websocket-client-impl:9.3.6.v20151106
-        LICENSE: AL-2.0               For details, see licenses/binary-release/websocket-client-9.3.6.AL-2.0
-        
-  websocket-common-9.3.6.v20151106.jar  org.eclipse.jetty.websocket:javax-websocket-client-impl:9.3.6.v20151106
-        LICENSE: AL-2.0               For details, see licenses/binary-release/websocket-common-9.3.6.AL-2.0
-
---------------------------------
-console/server/ext
-  javax.servlet-api-3.1.0.jar        javax.servlet:javax.servlet-api:3.1.0
-        LICENSE: CDDL-1.0            For details, see licenses/binary-release/javax.servlet-api-3.1.0.CDDL-1.0
-                                     Only binary form content is included.
-                                     https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api/3.1.0
-        
-  jetty-http-9.3.6.v20151106.jar     org.eclipse.jetty:jetty-http:9.3.6.v20151106
-        LICENSE: AL-2.0              For details, see licenses/binary-release/jetty-http-9.3.6.AL-2.0
-        
-  jetty-io-9.3.6.v20151106.jar       org.eclipse.jetty:jetty-io:9.3.6.v20151106
-        LICENSE: AL-2.0              For details, see licenses/binary-release/jetty-io-9.3.6.AL-2.0
-        
-  jetty-security-9.3.6.v20151106.jar org.eclipse.jetty:jetty-security:9.3.6.v20151106
-        LICENSE: AL-2.0              For details, see licenses/binary-release/jetty-security-9.3.6.AL-2.0
-        
-  jetty-server-9.3.6.v20151106.jar   org.eclipse.jetty:jetty-server:9.3.6.v20151106
-        LICENSE: AL-2.0              For details, see licenses/binary-release/jetty-server-9.3.6.AL-2.0
-        
-  jetty-servlet-9.3.6.v20151106.jar  org.eclipse.jetty:jetty-servlet:9.3.6.v20151106
-        LICENSE: AL-2.0              For details, see licenses/binary-release/jetty-servlet-9.3.6.AL-2.0
-        
-  jetty-util-9.3.6.v20151106.jar     org.eclipse.jetty:jetty-util:9.3.6.v20151106
-        LICENSE: AL-2.0              For details, see licenses/binary-release/jetty-util-9.3.6.AL-2.0
-        
-  jetty-webapp-9.3.6.v20151106.jar   org.eclipse.jetty:jetty-webapp:9.3.6.v20151106
-        LICENSE: AL-2.0              For details, see licenses/binary-release/jetty-webapp-9.3.6.AL-2.0
-        
-  jetty-xml-9.3.6.v20151106.jar      org.eclipse.jetty:jetty-xml:9.3.6.v20151106
-        LICENSE: AL-2.0              For details, see licenses/binary-release/jetty-xml-9.3.6.AL-2.0
-  
---------------------------------
-console/servlets/ext
-  javax.servlet-api-3.1.0.jar        javax.servlet:javax.servlet-api:3.1.0
-        LICENSE: CDDL-1.0            For details, see licenses/binary-release/javax.servlet-api-3.1.0.CDDL-1.0
-                                     https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api/3.1.0
-
---------------------------------
-console/webapps
-  console.war
-        The file bundles d3-legend which is available under a MIT license.
-        For details, see licenses/d3-legend.MIT
-
-        The file bundles portions of d3 which is available under a BSD license.
-        For details, see licenses/d3.BSD
-
-        The file bundles portions of d3-sankey which is available under a BSD license.
-        For details, see licenses/d3-sankey.BSD
- 
-        The file bundles portions of jquery-ui which is available under a MIT license.
-        For details, see licenses/jquery-ui.MIT
- 
-        The bundled jquery-ui bundles jquery which is available under a MIT license.
-        For details, see licenses/jquery.MIT
-    
-        The bundled jquery bundles Sizzle which is available under a MIT license.
-        For details, see licenses/sizzle.MIT
-    
---------------------------------
diff --git a/binary-release/NOTICE b/binary-release/NOTICE
deleted file mode 100644
index 48d09e1..0000000
--- a/binary-release/NOTICE
+++ /dev/null
@@ -1,85 +0,0 @@
-Apache Edgent
-Copyright 2016-2017 The Apache Software Foundation
-
-Apache Edgent is currently undergoing Incubation at the Apache Software Foundation.
-
-This bundle includes software developed at
-The Apache Software Foundation (http://www.apache.org/).
-===============================================================================
-
-Portions of this bundle were developed by IBM Corp.
-Copyright IBM Corp. 2015, 2016
-
-===============================================================================
-
-APACHE EDGENT SUBCOMPONENTS:
-
-This Apache Edgent binary release includes a number of subcomponents with
-separate copyright notices and license terms.  The following notices apply.
-
---------------------------------
-ext/    # common dependencies for all Edgent components
-  metrics-core-3.1.2.jar            io.dropwizard.metrics:metrics-core:3.1.2
-                                    See licenses/binary-release/metrics-core-3.1.2.NOTICE
-
---------------------------------
-analytics/math3/ext/
-  commons-math3-3.4.1.jar           org.apache.commons:commons-math3:3.4.1
-                                    See licenses/binary-release/commons-math3-3.4.1.NOTICE
-
---------------------------------
-connectors/http/ext
-  httpclient-4.5.1.jar              org.apache.httpcomponents:httpclient:4.5.1
-                                    See licenses/binary-release/httpclient-4.5.1.NOTICE
-                                    
-  httpcore-4.4.4.jar                org.apache.httpcomponents:httpcore:4.4.4
-                                    See licenses/binary-release/httpcore-4.4.4.NOTICE
-                                    
-  commons-codec-1.9.jar             org.apache.commons:commons-codec:1.9
-                                    See licenses/binary-release/commons-codec-1.9.NOTICE
-                                    
-  commons-logging-1.2.jar           org.apache.commons:commons-logging:1.2
-                                    See licenses/binary-release/commons-logging-1.2.NOTICE
-        
---------------------------------
-connectors/iotp/ext
-  commons-codec-1.10.jar            org.apache.commons:commons-codec:1.10
-                                    See licenses/binary-release/commons-codec-1.10.NOTICE
-                                    
-  commons-lang3-3.4.jar             org.apache.commons:commons-lang3:3.4
-                                    See licenses/binary-release/commons-lang3-3.4.NOTICE
-                                    
-  commons-logging-1.2.jar           org.apache.commons:commons-logging:1.2
-                                    See licenses/binary-release/commons-logging-1.2.NOTICE
-                                    
-  commons-net-3.3.jar               org.apache.commons:commons-net:3.3
-                                    See licenses/binary-release/commons-net-3.3.NOTICE
-                                    
-  httpclient-4.5.1.jar              org.apache.httpcomponents:httpclient:4.5.1
-                                    See licenses/binary-release/httpclient-4.5.1.NOTICE
-                                    
-  httpcore-4.4.3.jar                org.apache.httpcomponents:httpcore:4.4.3
-                                    See licenses/binary-release/httpcore-4.4.3.NOTICE
-                                    
-  joda-time-2.9.2.jar               joda-time:joda-time:2.9.2
-                                    See licenses/binary-release/joda-time-2.9.2.NOTICE
-                                    
-  log4j-1.2.17.jar                  log4j:log4j:1.2.17
-                                    See licenses/binary-release/log4j-1.2.17.NOTICE
-        
---------------------------------
-connectors/kafka/ext
-  kafka-clients-0.8.2.2.jar         org.apache.kafka:kafka-clients:0.8.2.2
-                                    See licenses/binary-release/kafka-clients-0.8.2.2.NOTICE
-                                    
-  kafka_2.10-0.8.2.2.jar            org.apache.kafka:kafka_2.10:0.8.2.2
-                                    See licenses/binary-release/kafka_2.10-0.8.2.2.NOTICE
-                                    
-  log4j-1.2.16.jar                  log4j:log4j:1.2.16
-                                    See licenses/binary-release/log4j-1.2.16.NOTICE
-                                    
-  metrics-core-2.2.0.jar            com.yammer.metrics:metrics-core:2.2.0
-                                    See licenses/binary-release/metrics-core-2.2.0.NOTICE
-    
---------------------------------
-    
\ No newline at end of file
diff --git a/binary-release/README b/binary-release/README
deleted file mode 100644
index aae76d3..0000000
--- a/binary-release/README
+++ /dev/null
@@ -1,21 +0,0 @@
-Using an Apache Edgent binary release bundle
-
-Apache Edgent is supported on several Java target platforms.
-For more information see JAVA_SUPPORT.md 
-
-See Getting Started https://edgent.apache.org/docs/edgent-getting-started for
-information about using this binary release to develop applications that
-use Edgent.
-
-Apache Edgent is released under the Apache License Version 2.0.
-This Apache Edgent binary release includes a number of subcomponents with
-separate copyright notices and license terms. It includes some binary content
-with associated EPL and CDDL licenses.  See LICENSE for details.
-
-Apache Edgent is an effort undergoing incubation at The Apache Software Foundation (ASF),
-sponsored by the Incubator PMC. Incubation is required of all newly accepted
-projects until a further review indicates that the infrastructure, communications,
-and decision making process have stabilized in a manner consistent with other
-successful ASF projects. While incubation status is not necessarily a reflection
-of the completeness or stability of the code, it does indicate that the project
-has yet to be fully endorsed by the ASF.
diff --git a/binary-release/apache-notice b/binary-release/apache-notice
deleted file mode 100644
index 2684968..0000000
--- a/binary-release/apache-notice
+++ /dev/null
@@ -1,8 +0,0 @@
-Apache Edgent
-Copyright 2016 The Apache Software Foundation
-
-Apache Edgent is currently undergoing Incubation at the Apache Software Foundation.
-
-This product includes software developed at
-The Apache Software Foundation (http://www.apache.org/).
-===============================================================================
diff --git a/binary-release/apache-v2_0-license b/binary-release/apache-v2_0-license
deleted file mode 100644
index 8dada3e..0000000
--- a/binary-release/apache-v2_0-license
+++ /dev/null
@@ -1,201 +0,0 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) 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. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "{}"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright {yyyy} {name of copyright owner}
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
diff --git a/binary-release/ibm-contrib-notice b/binary-release/ibm-contrib-notice
deleted file mode 100644
index 9ddbaa5..0000000
--- a/binary-release/ibm-contrib-notice
+++ /dev/null
@@ -1,11 +0,0 @@
-Apache Edgent
-Copyright 2016 The Apache Software Foundation
-
-Apache Edgent is currently undergoing Incubation at the Apache Software Foundation.
-
-This product includes software developed at
-The Apache Software Foundation (http://www.apache.org/).
-===============================================================================
-
-Portions of this software were developed by IBM Corp.
-Copyright IBM Corp. 2015, 2016
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index 450a54c..0000000
--- a/build.gradle
+++ /dev/null
@@ -1,930 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
- 
-apply from: 'gradle/wrapper.gradle'
-apply from: 'gradle/other.gradle'
-
-import org.gradle.plugins.signing.Sign
-import java.io.Console
- 
-/* Configure root project */
-allprojects {
-  apply plugin: 'idea'
-  apply plugin: 'eclipse'
-
-  repositories {
-    mavenCentral()
-  }
-  
-  project.version = build_version
-}
-
-apply from: 'gradle/rat.gradle'
-
-rat {
-  excludes = [
-    '*.patch',
-    '**/.classpath',
-    '**/.cache/**',
-    '**/.git/**',
-    '**/.gitignore',
-    '**/.gradle/**',
-    '.gradle-wrapper/**',  // historical instead of gradle/wrapper
-    '**/.idea/**',
-    '**/.project',
-    '**/.settings/**',
-    '**/bin/**',           // generated by Eclipse builds
-    '**/build/**',
-    '**/META-INF/services/**',
-    '**/README.md',
-    'externalJars/**',     // generated by setupExternalJars for Eclipse use
-    'gradlew',
-    'gradlew.bat',
-    'DEVELOPMENT.md',
-    'JAVA_SUPPORT.md',
-    'CONTRIBUTORS', '.mailmap',
-    'binary-release/apache-notice',
-    'binary-release/ibm-contrib-notice',
-    'connectors/jdbc/JdbcStreamsTestDb/**',
-    'connectors/jdbc/derby.log',
-    'connectors/mqtt/src/test/keystores/**',
-    'console/**/js/ext/**/jquery.js',         // has header w/MIT license ref
-    'console/**/js/ext/**/jquery-ui.min.css', // has header w/MIT license ref
-    'console/**/js/ext/**/jquery-ui.min.js',  // has header w/MIT license ref
-    'console/**/js/ext/**/jquery-ui.structure.min.css', // has header w/MIT license ref
-    'console/**/js/ext/**/jquery-ui.theme.min.css', // has header w/MIT license ref
-    'console/**/js/ext/d3.legend.js',         // has header w/MIT license ref
-    'console/**/js/ext/d3.min.js',            // no header. included in LICENSE
-    'console/**/js/ext/sankey_edgent.js',     // no header. included in LICENSE
-    'licenses/d3-sankey.BSD',                 // OK, BSD 3-clause
-    'licenses/d3.BSD',                        // OK, BSD 3-clause
-    'licenses/binary-release/javax.servlet-api-3.1.0.CDDL-1.0',  // OK, only binary form content
-    'licenses/binary-release/javax.websocket-api-1.0.CDDL-1.1',  // OK, only binary form content
-    'licenses/binary-release/mqttv3-1.1.0.EDL-1.0',  // OK, EDL-1.0
-    'licenses/binary-release/mqttv3-1.1.0.EDL-1.0',  // OK, EDL-1.0
-    'licenses/binary-release/watson-iot-0.2.2.EPL-1.0',  // OK, only binary form content
-    'samples/**/*.properties',
-    'samples/**/*.cfg',
-    'scripts/**/*.properties',
-    'scripts/**/*.cfg',
-    'scripts/connectors/jdbc/persondata.txt',
-    'scripts/connectors/kafka/README-kafka',
-    'test/**/*.properties',
-    'test/**/device.cfg',
-    'test/**/*.txt',
-  ]
-}
-
-apply plugin: 'java'
-jar {
-  deleteAllActions()  // Avoid creating/staging an empty jar for the "root"
-}
-apply plugin: 'signing'
-
-
-ext {
-  commithash_error = ''
-  commithash = {
-    try {
-      return "git rev-parse --short HEAD".execute().text.trim()
-    } catch (Exception e) {
-      ext.commithash_error = e
-      return ''
-    }
-  }()
-  now = new Date()
-  DSTAMP = String.format('%tY%<tm%<td', now)
-  TSTAMP = String.format('%tH%<tM', now)
-  COPYRIGHT_YEAR = String.format('%tY', now)
-  
-  snapshotId = "-SNAPSHOT-${DSTAMP}-${TSTAMP}"
-  if (System.properties['edgent.snapshotId'] != null) {
-    snapshotId = System.properties['edgent.snapshotId']
-  }
-                   
-  external_jars_dir = "$rootProject.projectDir/externalJars/java8"
-  
-  target_dir = "$distsDir"
-  target_java8_dir = "$target_dir/java8"
-  target_java8_ext_dir = "$target_java8_dir/ext"
-  target_java7_dir = "$target_dir/java7"
-  target_android_dir = "$target_dir/android"
-  target_docs_dir = "$target_dir/docs"
-  target_javadoc_dir = "$target_docs_dir/javadoc"
-  target_report_dir = "$target_dir/reports"
- 
-  // project groups whose jars are to be placed in target_java8_lib
-  // instead of the default "$target_java8_dir/$simpleProjectGroup/$project.name/lib"
-  //         e.g., "$target_java8_dir/lib" for api/topology
-  //         "$target_java8_dir/connectors/iotp/lib" for connectors/iotp
-  //
-  target_java8_lib_groups = ["api", "providers", "runtime", "spi"]
-  
-  // TODO can these be deduced by the absence of a build.gradle for the project?
-  aggregatorOnlyProjects = [
-    ':android',
-    ':analytics', ':api', ':apps',
-    ':connectors', ':console',
-    ':platform', ':providers',
-    ':runtime', ':samples', ':spi',
-    ':test', ':utils'
-  ]
-  
-  filteredSubprojects = subprojects.findAll { 
-    project -> !aggregatorOnlyProjects.contains(project.path)
-  }
-  
-  // Edgent core external dependencies
-  core_ext_dependencies = ['com.google.code.gson:gson:2.2.4',
-                   'org.slf4j:slf4j-api:1.7.12',
-                   'io.dropwizard.metrics:metrics-core:3.1.2']
-  
-  // Edgent Samples external dependencies
-  samples_ext_dependencies = ['org.slf4j:slf4j-jdk14:1.7.12']
-
-  // Edgent tests external dependencies
-  test_common_dependencies = ['org.slf4j:slf4j-jdk14:1.7.12']
-  
-  common_ext_dependencies = [
-    core_ext_dependencies,
-    samples_ext_dependencies,
-    // test_common_dependencies, // omit as tests aren't included in release tgz
-  ].flatten()
-
-  ext."signing.keyId" = null
-  ext."signing.secretKeyRingFile" = null
-  ext."signing.password" = null
-}
-
-// Declare the common_ext_dependencies as dependencies of the root project
-// to easily copy them (their resolved paths) to the target dir
-dependencies {
-  compile common_ext_dependencies
-}
-ext.copyCommonExtJarsFn = { targetDir ->
-  copy {
-    from configurations.compile.files
-    into targetDir
-  }
-}
-task setupCommonExtJars << {
-  copyCommonExtJarsFn "$external_jars_dir/ext"
-}
-task copyCommonExtJars << {
-  copyCommonExtJarsFn target_java8_ext_dir
-}
-
-def String mkJarNameFromSpec(String jarSpec) {
-  // e.g. 'com.google.code.gson:gson:2.2.4' => gson-2.2.4.jar
-  // e.g. 'com.google.code.gson:gson:2.2.4@jar' => gson-2.2.4.jar
-  def sfx = jarSpec.endsWith('@jar') ? "" : '.jar'
-  return jarSpec.split(':')[1] + '-' + jarSpec.split(':')[2].replace('@','.') + sfx
-}
-
-def getProjectExtDepFiles(Project proj) { // project's direct ext deps and their transitive deps
-  // TODO suspect this is picking up ext dependencies of transitive **project** dependencies???
-  
-  // handle ext jar deps expressed via "compile <external-dependency-spec>"
-  def allExtDepFiles = proj.configurations.runtime.files { it instanceof ExternalDependency }
-  
-  // handle ext jar deps expressed via addTargetDirExtJarDependency
-  allExtDepFiles.addAll proj.files(proj.directTargetDirExtJarDependencies)
-  
-  logger.info "$proj.path allExtDepFiles: "+allExtDepFiles
-  return allExtDepFiles
-}
- 
-def getProjectNonCommonExtDepFiles(Project proj) {
-  // filter out "common" (target_java8_ext_dir) external dependencies
-  def commonExtJarNames = common_ext_dependencies.collect {
-    mkJarNameFromSpec it
-  }
-  def filteredExtDepFiles = getProjectExtDepFiles(proj).findAll {
-    ! commonExtJarNames.contains(it.getName())
-  }
-  return filteredExtDepFiles
-}
-
-def String mkManifestClassPath(Project proj) {
-  // The manifest's classpath needs to include the project's:
-  // - immediate-only dependant edgent jars (not transitive and not their ext deps
-  //   since our project jars are build with a manifest-classpath that
-  //   handles the project's "private" dependencies)
-  // - immediate dependant external jars and their transitive deps (since
-  //   these don't seem to have a manifest classpath that takes care of their
-  //   dependencies)
-  // - common_ext_dependencies jars when declared as dependencies
-  //
-  // proj.configurations.runtime.files (mostly) captures all of the above
-  // since do to our project build.gradle use of our various add*Dependency().
-
-  def depJars = proj.configurations.runtime.files
-    
-  // assume that any deps still in the gradle cache are project private ext deps
-  // (that will-get/have-been copied into the project's ext dir in the targetdir) 
-  def projExtDir = "$target_java8_dir/$proj.targetRelProjExtDir"
-  depJars = depJars.collect { file ->
-    if (file.toString().contains('/.gradle/caches/')) {
-      return proj.file("$projExtDir/"+file.getName())
-    }
-    return file
-  }
-    
-  def cp = proj.mkRelativePaths(depJars).join(' ')
-  logger.info "$proj.path manifest-classPath: $cp"
-  return cp
-}
-
-gradle.taskGraph.whenReady {taskGraph ->
-  if (taskGraph.hasTask(test)) {
-    println "\nHINTs: Use the '--tests <testClassNamePattern>[.<testMethodNamePattern>]' option to select specific test classes or methods."
-    println "    ./gradlew :api:topology:test --tests '*JsonFunctionsTest'"
-    println "    ./gradlew :api:topology:test --tests '*JsonFunctionsTest.testBytes'"
-    println "Use the 'cleanTest' task to force a rerun of a previously successful test task:"
-    println "    ./gradlew :api:topology:cleanTest :api:topology:test"
-    println "    ./gradlew cleanTest test"
-    println ""
-    sleep 2
-  }
-}
-
-/* Configure subprojects */
-subprojects {
-
-  // ignore aggregator only projects so we don't assemble empty jars, etc for them.
-  if (aggregatorOnlyProjects.contains(project.path)) {
-    return
-  }
-  
-  if(!project.group.equals("edgent.platform")){
-  	apply plugin: 'maven-publish'
-  }
-  apply plugin: 'java'
-  apply plugin: "jacoco"
- 
-  if (buildFile.isFile() && !buildFile.exists()) {
-    configurations.create('default')
-    return
-  }
-  
-  ext.simpleGroupName = project.group.replace('edgent.', '') // e.g., 'edgent.api' => 'api'
-
-  ext.mkRelativePaths = { Collection files ->
-    // make all files paths relative to the project's lib dir in targetdir
-    // well... unless this is for a war, which resides in the group's "webapps"
-    // dir instead of project's lib dir.  See :console:servlets build.gradle.
-    def projLibDir = project.file("$target_java8_dir/$targetRelProjLibDir")
-    if (project.pluginManager.hasPlugin('war')) {
-      projLibDir = project.file("$target_java8_dir/$project.simpleGroupName/webapps")
-    }
-    files.collect {  projLibDir.toPath().relativize(it.toPath()) }
-  }
-
-  ext.targetRelProjDir = { String kind ->  // kind: "lib", "ext"
-    // use targetRelProject{Lib,Ext}Dir
-    // e.g., =>  "lib" or "<component>/<subcomponent>/lib"
-    // the general case location
-    def relProjDir = "$simpleGroupName/$project.name/$kind"
-   
-    // special cases
-    if (target_java8_lib_groups.contains(simpleGroupName)) {
-      relProjDir = "$kind"
-    }
-    else if ('samples' == simpleGroupName) {
-      relProjDir = "samples/$kind"
-    }
-   
-    return relProjDir
-  }
-  ext.targetRelProjLibDir = targetRelProjDir('lib')
-  ext.targetRelProjExtDir = targetRelProjDir('ext')
-  
-  // N.B. regarding the various add*Dependency() methods
-  //
-  // The methods need to be used in project build.gradle "dependencies" declarations.
-  // e.g.,
-  //   dependencies {
-  //     addTargetDirProjectJarDependency 'compile', ':api:topology' # NOT compile project(':api:topology')
-  //     addProjectExtDependency 'compile', 'com.ibm.messaging:watson-iot:0.2.2'  # NOT compile 'com.ibm.messaging:watson-iot:0.2.2'
-  //     addProjectExtDependency 'compile', 'org.apache.kafka:kafka_2.10:0.8.2.2@jar'
-  //     addTargetDirCoreExtDependencies 'compile'
-  //     addMyTargetDirProjectJarDependency 'testCompile'
-  //     // N.B. root project adds test common dependencies
-  //   }
-  // 
-  // These methods play a role in the policies:
-  // - Edgent projects depend on other project's jars in the target-dir, not their classes
-  // - Edgent project jars have a manifest-classpath that handles
-  //   the project's "private" inter-project dependencies
-  //   as well as the project's "private" external component dependencies.
-  // - We build a target dir that includes the project's jar as well as
-  //   the project's external dependency jars
-  // - The tests compile and run against the project jars in the target dir
-  //   (as external/integration test code would).
-  // - The samples compile and run against the project jars in the target dir
-  //   (as user code would).
-  
-  ext.directTargetDirExtJarDependencies = [] 
-
-  ext.addTargetDirProjectJarDependency = { config,proj ->
-    // add a dependency on a project's jar in the target-dir
-    def jarPath = project(proj).jar.archivePath
-    
-    // add the jar as a dependency and ensure it's present when we need it
-    // ? script error with: dependencies { "$config" files(jarPath) builtBy "${proj}:assemble" }
-    dependencies { "$config" files(jarPath) }
-    def task = "${config}Java"
-    if (config == "testCompile") {
-      task = "compileTestJava"
-    }
-    else if (config == "providedCompile") {
-      task = "compileJava"
-    }
-    "$task" { dependsOn "${proj}:assemble" }
-  }
-
-  ext.addMyTargetDirProjectJarDependency = { config ->
-    // add a dependency on my project's jar in the target dir
-    addTargetDirProjectJarDependency(config, project.path)
-  }
-  
-  ext.addTargetDirExtJarDependency = { config,jarName ->
-    // add a dependency on a target_java8_ext_dir jarName
-    // record the addition
-    def jar = "$target_java8_ext_dir/$jarName"
-    if (!directTargetDirExtJarDependencies.contains(jar)) {
-      directTargetDirExtJarDependencies.add jar
-    }
-    
-    // add the jar as a dependency
-    dependencies { "$config" files(jar) }
-    compileJava { dependsOn ':copyCommonExtJars' }
-  }
-  
-  ext.addTargetDirCoreExtJarDependencies = { config ->
-    core_ext_dependencies.collect { depSpec ->
-      mkJarNameFromSpec(depSpec)
-    }.each { jarName ->
-      addTargetDirExtJarDependency config, jarName
-    }
-  }
-
-  ext.addProjectExtDependency = { config,externalDepSpec ->
-    // for declaring project private external dependencies
-    // ends up (transitively) copying the dependency to the project's ext dir
-    dependencies { "$config" externalDepSpec }
-  }
-
-  ext.addCompileTestDependencies = { String... deps ->
-    // add a dependency on other project's testClasses
-    deps.each { dep ->
-      dependencies {
-        testCompile project(dep).sourceSets.test.output
-      }
-      compileTestJava {
-        dependsOn "${dep}:testClasses"
-      }
-    }
-  }
-
-  sourceCompatibility = '1.8'
-  targetCompatibility = '1.8'
-
-  def compileOptions = {
-    options.debugOptions.debugLevel = 'source,lines,vars'
-    options.verbose = logger.isDebugEnabled()
-    options.listFiles = logger.isInfoEnabled()
-    options.deprecation = true
-    options.encoding = 'UTF-8'
-  }
-  compileJava {
-    configure compileOptions
-  }
-  compileTestJava {
-    configure compileOptions
-  }
-  
-  dependencies {
-    // common dependencies for tests
-    testCompile 'junit:junit:4.10'
-    addMyTargetDirProjectJarDependency 'testCompile'
-    if (project.path != ':api:function') {
-      addTargetDirExtJarDependency 'testRuntime', 'slf4j-jdk14-1.7.12.jar'
-    }
-    else {
-      // the add... induces UnsupportedOperationException elsewhere in script when processing :api:function:jar ???
-      // can't figure it out but cleaning directTargetDirExtJarDependencies
-      // avoids it ???... with seemingly no other consequences.
-      addTargetDirExtJarDependency 'testRuntime', 'slf4j-jdk14-1.7.12.jar'
-      project.directTargetDirExtJarDependencies = []
-    }
-
-    // common dependencies for samples
-    if (project.path ==~ '^:samples.*') {
-      addTargetDirProjectJarDependency 'compile', ':providers:development'
-      addTargetDirProjectJarDependency 'compile', ':providers:direct'
-
-      addTargetDirCoreExtJarDependencies 'compile'      
-      addTargetDirExtJarDependency 'runtime', 'slf4j-jdk14-1.7.12.jar'
-    }
-  }
-  
-  ext.copyProjectExtJarsFn = { targetDir ->
-    // Copy the project jar's "private" external dependencies (transitively)
-    // into the project's ext dir in the target-dir.
-
-    // If our project's jar task lacks any actions (e.g., platform:android)
-    // there's nothing to do.
-    if (!jar.actions)
-      return
-    
-    // FYI we're getting more transitive ext deps than the ant build
-    // in some cases - e.g., for watson iot we "knew" we only needed a subset
-    // of all watson iot deps known to maven
-    
-    def projectExtDir = project.targetRelProjExtDir
-    def nonCommonExtFiles = getProjectNonCommonExtDepFiles(project)
-    logger.info "$project.path copying projExtDepFiles jars: "+nonCommonExtFiles.collect { it.getName() }
-    copy {
-      from nonCommonExtFiles
-      includeEmptyDirs = false
-      into "$targetDir/$projectExtDir"
-    }
-  }
-  
-  ext.copyProjectExtJarsFn2 = { targetDir ->
-    // Copy the project jar's "private" external dependencies (transitively)
-    // into target-dir.
-    
-    def nonCommonExtFiles = getProjectNonCommonExtDepFiles(project)
-    logger.info "$project.path copying projExtDepFiles jars: "+nonCommonExtFiles.collect { it.getName() }
-    copy {
-      from nonCommonExtFiles
-      includeEmptyDirs = false
-      into "$targetDir"
-    }
-  }
-  
-  task setupProjectExtJars << {
-    // Helper for setupExternalJars task
-    copyProjectExtJarsFn external_jars_dir
-  }
-
-  jar {
-    // adjust jar task config and also augment the task to do our additional processing
-    
-    // generate the project's jar into the target dir location
-    // with the appropriate name and manifest.
-    // TODO - gradle/maven best practice has version in jarname
-    
-    archiveName = "${project.group}.${project.name}.${extension}"
-    if (["javax.websocket-client", "javax.websocket-server", "edgent.javax.websocket"].contains(project.name)) {
-      archiveName = "${project.name}.${extension}"
-    }
-    destinationDir = file("$target_java8_dir/" + targetRelProjLibDir)
-
-    doFirst {
-      configure jarOptions
-    }
-    
-    doLast {
-      copyProjectExtJarsFn target_java8_dir
-    }
-  }
-
-  ext.jarOptions = {
-    manifest {
-      attributes(
-        'Implementation-Title': "${-> baseName}",
-        'Implementation-Vendor': build_vendor,
-        // TODO inclusion of DSTAMP/TSTAMP results in regeneration
-        // of a jar when none of its contents/dependencies have changed.
-        // If possible use a canned DSTAMP/TSTAMP for non-"release" tasks
-        // to make the dev cycle more efficient at the expense of the TSTAMP.
-        'Implementation-Version': "${commithash}-${DSTAMP}-${TSTAMP}",
-        'Class-Path': mkManifestClassPath(project),
-      )
-    }
-    metaInf {
-      with( copySpec {
-        rename { 'LICENSE' }
-        from rootProject.file('binary-release/apache-v2_0-license')
-      })
-      with( copySpec {
-        rename { 'NOTICE' }
-        from rootProject.file(
-          projectsWithPreApacheContribs.contains(project.path)
-            ? 'binary-release/ibm-contrib-notice'
-            : 'binary-release/apache-notice')
-      })
-    }
-  }
-  
-  ext.printFile = { path ->
-    ant.concat { fileset(file: path) }
-  }
-  ext.adjustTest7Classpath = false
-  
-  task testSummaryFinalizer << {
-    def testTask = tasks.getByName('test')
-    if (System.properties['edgent.build.ci'] != null) {
-      testTask.failedTestResultPaths.each { path ->
-        println path
-        printFile path
-      }
-    }
-    def result = testTask.summaryResult
-    if (result != null) {
-      def duration = String.format('%.3fsec', (result.endTime - result.startTime) / 1000)
-      println "$project.path $duration $result.resultType ($result.testCount tests, $result.failedTestCount failures, $result.skippedTestCount skipped)"
-    }
-  }
-
-  test {
-    filter {
-      includeTestsMatching '*Test'  // can override via --tests command line option
-    }
-
-    systemProperty 'edgent.test.top.dir.file.path', rootProject.projectDir
-    systemProperty 'edgent.test.root.dir', rootProject.projectDir
-    systemProperty 'edgent.build.ci', System.properties['edgent.build.ci']
-    
-    // pass along any org.apache.edgent system props
-    systemProperties System.properties.findAll { it.key.startsWith("org.apache.edgent") }
-    
-    testLogging {
-      exceptionFormat 'full'
-      showStandardStreams = System.properties['edgent.test.showOutput'] != null
-    }
-    beforeSuite { desc ->
-      if (!desc.parent) { // will match the outermost suite
-        println "$project.path testing ..."
-      }
-    }
-    ext.failedTestResultPaths = []
-    ext.summaryResult = null
-    afterSuite { desc, result ->
-      // make failures in edgent.build.ci runs more debuggable
-      // wish the junit xml files existed at this moment but they don't
-      if (desc.parent && desc.className != null) {  // individual test class result
-        if (result.resultType == TestResult.ResultType.FAILURE) {
-          def resultFile = "$testResultsDir/test/TEST-${desc.className}.xml"
-          println "\nFailed testrun results: $resultFile"
-          failedTestResultPaths.add resultFile
-        }
-      }
-      else if (!desc.parent) { // project's overall results
-        summaryResult = result 
-      }
-    }
-    finalizedBy "testSummaryFinalizer" // a doLast isn't invoked if there's a test failure
-    reports {
-      junitXml.enabled = true  // generate build/test-results/test/TEST-*.xml
-      // individual <project>/build/reports junit/jacoco html reports not needed with aggregate report
-      html.enabled = System.properties['edgent.test.project.htmlReports'] != null
-    }
-    doFirst {
-      // The project's tests are supposed to run against its target-dir jar.
-      // We must remove the project's $buildDir/{classes,resources}/main
-      // from the classpath so they're not used.  
-
-      classpath = project.sourceSets.test.runtimeClasspath
-      classpath -= project.sourceSets.main.output
-
-      // Hmm... for some reason the classpath (when printed here) also includes
-      // the project's src build/libs/ jar and by the default name
-      // (e.g., build/libs/oplets-0.4.1.jar) yet we've configured the jar task
-      // to generate the jar in the target-dir with a different name.  
-      // It also lacks that target-dir jar we added as a dependency 
-      // via addMyTargetDirProjectJarDependency 'testCompile'
-      // ???  
-      // Adjust accordingly.
-      
-      classpath = classpath.filter { ! it.path.startsWith(project.libsDir.path) } 
-      classpath = files(project.jar.archivePath) + classpath
-      
-      if (adjustTest7Classpath) {
-        // Add special java7 processing... (on top of the other test.doFirst classpath manipulations)
-      
-        // Change from using the normal test classes dir to the java7 test classes dir
-        classpath -= files(sourceSets.test.output.classesDir)
-        classpath = files(sourceSets.test.output.classesDir.toString().replace('test', 'java7Test')) + classpath
-        
-        // Some of the tests have dependencies on other tests, adjust those classpaths too
-        classpath = files(classpath.collect { it.toString().replace('build/classes/test', 'build/classes/java7Test') })
-      
-        // Switch from java8 jars to java7 jars
-        classpath = files(classpath.collect { it.toString().replace('java8', 'java7') })
-      }
-      logger.debug "$project.path test.classpath: " + classpath.collect { it.toString() }
-    }
-  }
-  
-  ext.j7TestClassesDir = file("$project.buildDir/classes/java7Test")
-  
-  task test7AdjustTestTask << {
-    if (! tasks.getByName('test').enabled
-        || unsupportedJava7TestProjects.contains(project.path)
-        || sourceSets.test.allSource.isEmpty()) {
-      test.enabled = false
-      return
-    }
-    adjustTest7Classpath = true
-    if (!j7TestClassesDir.exists()) {
-      // implicit dependency: :platform:java7:test7Compile
-      logger.error " ERROR: Run the test7Compile task.  $j7TestClassesDir does not exist."
-      throw new TaskExecutionException()
-    }
-    test {
-      testClassesDir = j7TestClassesDir
-      outputs.upToDateWhen { false } // always run - task is never "up to date"
-    }
-  }
-
-  task test7Run() {
-    description = "Run the test7Compile'd tests against the java7 target jars - run after :platform:java7:test7Compile and with JAVA_HOME==java7-VM"
-    // fwiw trying to leverage :platform:java7:ant_test7.run was problematic
-    
-    dependsOn ':platform:java7:verifyJava7Built', test7AdjustTestTask, test
-    // implicit dependency: :platform:java7:test7Compile
-    test.mustRunAfter = [ test7AdjustTestTask, ':platform:java7:verifyJava7Built' ]
-
-    outputs.upToDateWhen { false } // always run - never "up to date"
-  }
-
-  assemble.doLast {
-    // augment assemble with our additional target dir update processing
-    
-    // Copy SRC into target dir when appropriate
-    if (project.path ==~ '^:samples.*') {
-      copy {
-        from(sourceSets.main.allSource.srcDirs) { include '**/*.java' }
-        into "$target_java8_dir/$project.simpleGroupName/src/$project.name/src/main/java/"
-      }
-    }
-  }
-  
-  task sourceJar(type: Jar) {
-    // baseName-appendix-version-classifier.extension
-    from sourceSets.main.allJava
-    classifier = 'sources'
-  }  
-
-  // support for 'gradle publishToMavenLocal' etc 
-  // TODO publishing test.{fvt,svt} and samples ... doesn't seem desirable? e.g., we're excluding test.{fvt,svt} jars from the tgz
-  if (project.pluginManager.hasPlugin('publishing')) {
-    publishing {
-      publications {
-        mavenJava(MavenPublication) {
-          // specify dependencies like: org.apache.edgent:edgent.api.topology:0.4.0
-          groupId = build_group
-          artifactId = "${project.group}.${project.name}" 
-          artifact sourceJar
-          if (project.pluginManager.hasPlugin('war')) {
-            from components.web
-          }
-          else {
-            from components.java
-          }
-        }
-      }
-    }
-  }
-    
-}
-
-task copyScripts(type: Copy) {
-  description = 'Copy scripts to target_java8_dir'
-  includeEmptyDirs = false
-  from("scripts/") { include "**/*" }
-  into "$target_java8_dir/scripts/"
-}
-
-//Create Junit Report
-// need to setup classpath to junit/jacoco for ant.junitreport task
-configurations {
-  junitLibs
-}
-dependencies { // versions with gradle 3.1
-  junitLibs 'org.apache.ant:ant-junit:1.9.6'
-  junitLibs 'org.apache.ant:ant-junit4:1.9.6'
-  junitLibs 'org.jacoco:org.jacoco.ant:0.7.7.201606060606'
-}
-
-task createJunitReport << {
-  description = "Generates a Junit report from all subprojects (use after 'test')"
-
-  ant.delete(dir: "${target_report_dir}/tests")
-  ant.taskdef(name: 'junitreport',
-          classname: 'org.apache.tools.ant.taskdefs.optional.junit.XMLResultAggregator',
-          classpath: configurations.junitLibs.asPath)
-  ant.junitreport(todir: './') {
-    fileset(dir: './', includes: '**/test-results/test/TEST-*.xml')
-    report(format: 'frames', todir: "${target_report_dir}/tests")
-  }
-  ant.move(file: "TESTS-TestSuites.xml", tofile: "${target_report_dir}/TESTS-TestSuites.xml")
-}
-
-apply from: 'gradle/jacoco.gradle'
-apply from: 'gradle/javadoc.gradle'
-
-task addVersionDotTxt {
-  description = 'Add version.txt in target_dir'
-  doLast {
-    def map = [
-      DSTAMP: "$DSTAMP",
-      TSTAMP: "$TSTAMP",
-      commithash: "$commithash",
-      'commithash.error': "$commithash_error",
-      'edgent.version': "$build_version",
-      ]
-    def f = new File("$target_dir/version.txt");
-    def d = new File(target_dir);
-    if( !d.exists() ) { d.mkdirs() }
-    f.createNewFile()
-    map.forEach { k,v -> f.append "$k=$v\n" }
-  }
-}
-
-task releaseTarGz(type: Tar) {
-  description = 'Create binary release tgz in target_dir'
-  archiveName = "apache-${build_name}-${build_version}-incubating${snapshotId}-bin.tgz"
-  compression = Compression.GZIP
-  destinationDir = new File("${target_dir}/../release-edgent")
-  duplicatesStrategy 'exclude'
-  into "${build_name}-${build_version}${snapshotId}"
-  // make some things first in the tgz
-  from rootProject.file('binary-release/LICENSE')
-  from rootProject.file('binary-release/NOTICE')
-  into ('licenses') { from 'licenses' }
-  from 'DISCLAIMER', 'JAVA_SUPPORT.md'
-  from rootProject.file('binary-release/README')
-  from 'RELEASE_NOTES', 'CONTRIBUTORS'
-  from "$target_dir/version.txt"
-  from target_dir
-  exclude '**/test/svt/'
-  exclude '**/connectors/javax.websocket-server/' // just part of wsclient test harness
-  doLast {
-    ant.checksum algorithm: 'md5', file: archivePath
-    ant.checksum algorithm: 'sha-512', fileext: '.sha', file: archivePath
-    println "created $destinationDir/$archiveName"
-  }
-}  
-
-task srcReleaseTarGz(type: Tar) {
-  description = 'Create source release tgz in target_dir'
-  archiveName = "apache-${build_name}-${build_version}-incubating${snapshotId}-src.tgz"
-  compression = Compression.GZIP
-  destinationDir = new File("${target_dir}/../release-edgent")
-  duplicatesStrategy 'exclude'
-  into "${build_name}-${build_version}${snapshotId}-src"
-  // make some things first in the tgz
-  from 'LICENSE', 'NOTICE'
-  from 'DISCLAIMER', 'JAVA_SUPPORT.md'
-  from 'RELEASE_NOTES'
-  from 'README'
-  exclude 'README.md'
-  from 'DEVELOPMENT.md'
-  from '.'
-  exclude 'KEYS'
-  exclude '.git', '.gradle', '.settings'
-  exclude '.gradle-wrapper', 'gradlew', 'gradlew.bat'
-  exclude '**/build/'           // gradle generated artifacts
-  exclude '**/externalJars/'    // gradle generated artifacts for eclipse
-  exclude '**/bin/'             // eclipse generated artifacts
-  exclude '**/*.class'          // final backstop just in case
-  exclude 'connectors/jdbc/derby.log'         // test cruft
-  exclude 'connectors/jdbc/JdbcStreamsTestDb' // test cruft
-  doLast {
-    ant.checksum algorithm: 'md5', file: archivePath
-    ant.checksum algorithm: 'sha-512', fileext: '.sha', file: archivePath
-    println "created $destinationDir/$archiveName"
-  }
-}  
-
-gradle.taskGraph.whenReady { taskGraph ->
-    if (ext."signing.password"==null && taskGraph.allTasks.any { it instanceof Sign }) {
-        // Use Java console to read from the console (no good for a CI environment)
-        def Console console = System.console()
-        console.printf "\n\n#####################################" +
-                       "\nWe have to sign some things in this build." +
-                       "\nPlease enter your signing details.\n\n"
-        def id = System.env['GPG_ID']
-        try { 
-          def tmpId = console.readLine("PGP Code Signing Key Id (default: $id): ")
-          if (!tmpId.isEmpty())
-            id = tmpId
-        } catch (NullPointerException e) {
-          throw new GradleException("You must run 'signAll --no-daemon'")
-        }
-        def file = System.env['GPG_SECRING']
-        if (file == null) {
-          file = "${System.properties['user.home']}/.gnupg/secring.gpg"
-        }
-        def tmpFile = console.readLine("PGP Secret Key Ring File (default: $file): ")
-        if (!tmpFile.isEmpty()) {
-          file = tmpFile
-        }
-        def password = String.valueOf(console.readPassword("PGP Private Key Password: "))
-
-        allprojects { ext."signing.keyId" = id }
-        allprojects { ext."signing.secretKeyRingFile" = file }
-        allprojects { ext."signing.password" = password }
-
-        console.printf "\n#####################################\n"
-    }
-}
-
-task signAll(type: Sign) {
-    description='Sign existing release artifacts in ${target_dir}/../release-edgent (run separetely after "release")'
-    fileTree("${target_dir}/../release-edgent") {
-        include '**/*.tgz'
-    }.each {
-      sign it
-    }
-    outputs.upToDateWhen { false }
-    doFirst {
-      if (getFilesToSign().isEmpty()) {
-        throw new GradleException("No artifacts to sign. Run the 'release' task first.")
-      }
-      //println "### files to sign: " + getFilesToSign().collect { it.name }.join(",")
-    }
-    doLast {
-      println "\nCreated signature files: " + getSignatureFiles().collect { it.name }.join(", ")
-    }
-}
-
-assemble {
-  description = "Assemble distribution artifacts and populate the target_dir with jars, doc, etc. Like 'build' w/o 'test'"
-  dependsOn filteredSubprojects.assemble, aggregateJavadoc, copyScripts
-  aggregateJavadoc.mustRunAfter filteredSubprojects*.assemble
-}
-
-task all(dependsOn: assemble) {
-  description = "alias for 'assemble'"
-}
-
-task cleanAll(type: Delete) {
-  description = 'clean aggregator'  // "release dependsOn clean" only does top-level clean
-  dependsOn clean, filteredSubprojects*.clean
-  // purge old ant build artifacts
-  delete 'target'
-  delete 'reports'
-  delete fileTree(dir: '.', includes:['**/classes/', '**/test.classes/'])
-  // retro7 processing cruft 
-  delete fileTree(dir: '.', includes:['**/classes.in/', '**/classes.out/']) 
-}
-
-task release {
-  description = 'Assemble distribution artifacts, populate target_dir, and create a release tgz'
-  dependsOn cleanAll, addVersionDotTxt, assemble,
-       ':platform:java7:addJava7TargetDir', ':platform:android:addAndroidTargetDir',
-       srcReleaseTarGz, releaseTarGz
-  addVersionDotTxt.mustRunAfter cleanAll
-  assemble.mustRunAfter addVersionDotTxt
-  releaseTarGz.mustRunAfter assemble,':platform:java7:addJava7TargetDir',':platform:android:addAndroidTargetDir'
-}
-
-task reports {
-  description = "Generate JUnit and Coverage reports of prior test run. Use after 'test'"
-  dependsOn createJunitReport, jacocoTestReport
-}
-
-task test7AdjustJacocoReport << {
-  jacocoTestReport.test7AdjustJacocoReport = true
-  logger.lifecycle "### NOTE: [WIP] test7 jacoco reporting ###"
-}
-
-task test7Reports {
-  description = "Generate JUnit and Coverage reports of prior test run. Use after 'test7Run'"
-  dependsOn createJunitReport, test7AdjustJacocoReport, jacocoTestReport
-  jacocoTestReport.mustRunAfter test7AdjustJacocoReport
-}
-
-// build: inject test report generation and javadoc generation (for early problem detection)
-// make 'build' like "all test reports"
-build {
-  dependsOn filteredSubprojects.build, reports
-  reports.mustRunAfter filteredSubprojects.build 
-}
-
-task setupExternalJars {
-  description = 'Add all of the dependant external jars to the target-dir (make available to Eclipse, etc)'
-  dependsOn setupCommonExtJars, filteredSubprojects.setupProjectExtJars
-}
diff --git a/connectors/.classpath b/connectors/.classpath
deleted file mode 100644
index b9abe61..0000000
--- a/connectors/.classpath
+++ /dev/null
@@ -1,74 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" path="common/src/main/java"/>
-	<classpathentry kind="src" path="common/src/test/java"/>
-	<classpathentry kind="src" path="command/src/main/java"/>
-	<classpathentry kind="src" path="command/src/test/java"/>
-	<classpathentry kind="src" path="csv/src/main/java"/>
-	<classpathentry kind="src" path="csv/src/test/java"/>
-	<classpathentry kind="src" path="file/src/main/java"/>
-	<classpathentry kind="src" path="file/src/test/java"/>
-	<classpathentry kind="src" path="http/src/main/java"/>
-	<classpathentry kind="src" path="http/src/test/java"/>
-	<classpathentry kind="src" path="iot/src/main/java"/>
-	<classpathentry kind="src" path="iot/src/test/java"/>
-	<classpathentry kind="src" path="iotp/src/main/java"/>
-	<classpathentry kind="src" path="jdbc/src/main/java"/>
-	<classpathentry kind="src" path="jdbc/src/test/java"/>
-	<classpathentry kind="src" path="kafka/src/main/java"/>
-	<classpathentry kind="src" path="kafka/src/test/java"/>
-	<classpathentry kind="src" path="mqtt/src/main/java"/>
-	<classpathentry kind="src" path="mqtt/src/test/java"/>
-	<classpathentry kind="src" path="pubsub/src/main/java"/>
-	<classpathentry kind="src" path="pubsub/src/test/java"/>
-	<classpathentry kind="src" path="serial/src/main/java"/>
-	<classpathentry kind="src" path="edgent.javax.websocket/src/main/java"/>
-	<classpathentry kind="src" path="javax.websocket-client/src/main/java"/>
-	<classpathentry kind="src" path="wsclient/src/main/java"/>
-	<classpathentry kind="src" path="wsclient-javax.websocket/src/main/java"/>
-	<classpathentry kind="src" path="wsclient-javax.websocket/src/test/java"/>
-    <classpathentry kind="src" path="javax.websocket-client/src/main/resources"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/http/ext/commons-codec-1.9.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/http/ext/commons-logging-1.2.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/http/ext/httpclient-4.5.1.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/http/ext/httpcore-4.4.4.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/iotp/ext/watson-iot-0.2.2.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/iotp/ext/commons-lang3-3.4.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/iotp/ext/commons-codec-1.10.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/iotp/ext/commons-logging-1.2.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/iotp/ext/commons-net-3.3.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/iotp/ext/httpclient-4.5.1.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/iotp/ext/httpcore-4.4.3.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/iotp/ext/joda-time-2.9.2.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/iotp/ext/org.eclipse.paho.client.mqttv3-1.1.0.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/kafka/ext/kafka_2.10-0.8.2.2.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/kafka/ext/kafka-clients-0.8.2.2.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/kafka/ext/log4j-1.2.16.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/kafka/ext/metrics-core-2.2.0.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/kafka/ext/scala-library-2.10.4.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/kafka/ext/zkclient-0.3.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/kafka/ext/zookeeper-3.4.6.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/mqtt/ext/org.eclipse.paho.client.mqttv3-1.1.0.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/javax.websocket-client/ext/javax-websocket-client-impl-9.3.6.v20151106.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/javax.websocket-client/ext/javax.websocket-api-1.0.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/javax.websocket-client/ext/jetty-util-9.3.6.v20151106.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/javax.websocket-client/ext/websocket-api-9.3.6.v20151106.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/javax.websocket-client/ext/websocket-client-9.3.6.v20151106.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/javax.websocket-client/ext/websocket-common-9.3.6.v20151106.jar"/>
-    <classpathentry kind="lib" path="../externalJars/java8/connectors/javax.websocket-server/ext/javax-websocket-server-impl-9.3.6.v20151106.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/javax.websocket-server/ext/jetty-http-9.3.6.v20151106.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/javax.websocket-server/ext/jetty-io-9.3.6.v20151106.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/javax.websocket-server/ext/jetty-security-9.3.6.v20151106.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/javax.websocket-server/ext/jetty-server-9.3.6.v20151106.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/javax.websocket-server/ext/jetty-servlet-9.3.6.v20151106.jar"/>
-    <classpathentry kind="lib" path="../externalJars/java8/connectors/javax.websocket-server/ext/javax.servlet-api-3.1.0.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/javax.websocket-server/ext/websocket-server-9.3.6.v20151106.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/connectors/javax.websocket-server/ext/websocket-servlet-9.3.6.v20151106.jar"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/api"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/ext"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/spi"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/providers"/>
-	<classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/connectors/.gitignore b/connectors/.gitignore
deleted file mode 100644
index 5ce6105..0000000
--- a/connectors/.gitignore
+++ /dev/null
@@ -1,7 +0,0 @@
-/classes/
-**/test.classes/
-**/classes/
-**/unittests/
-/bin/
-jdbc/derby.log
-jdbc/JdbcStreamsTestDb
diff --git a/connectors/.project b/connectors/.project
deleted file mode 100644
index a63c22e..0000000
--- a/connectors/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>connectors</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-	</natures>
-</projectDescription>
diff --git a/connectors/command/build.gradle b/connectors/command/build.gradle
deleted file mode 100644
index a5eb48c..0000000
--- a/connectors/command/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:topology'
-  addTargetDirProjectJarDependency 'compile', ':connectors:common'
-  
-  addTargetDirProjectJarDependency 'testCompile', ':providers:direct'
-  // N.B. root project adds test common dependencies
-}
-
-addCompileTestDependencies ':api:topology', ':providers:direct', ':connectors:common'
diff --git a/connectors/command/pom.xml b/connectors/command/pom.xml
new file mode 100644
index 0000000..32946b6
--- /dev/null
+++ b/connectors/command/pom.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-connectors</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-connectors-command</artifactId>
+
+  <name>Apache Edgent (Java 8): Connectors: Command</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-connectors-common</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-connectors-common</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/connectors/command/src/main/java/org/apache/edgent/connectors/command/runtime/CommandReader.java b/connectors/command/src/main/java/org/apache/edgent/connectors/command/runtime/CommandReader.java
index ec2bc31..b699e80 100644
--- a/connectors/command/src/main/java/org/apache/edgent/connectors/command/runtime/CommandReader.java
+++ b/connectors/command/src/main/java/org/apache/edgent/connectors/command/runtime/CommandReader.java
@@ -81,7 +81,7 @@
                     closeProcess();
                   }
                 }
-                else if (currentSupplierIterator == null && canStart()) {
+                else if (canStart()) {
                   start(); // and loop/retry
                 }
                 else {
diff --git a/connectors/command/src/main/java/org/apache/edgent/connectors/command/runtime/CommandWriter.java b/connectors/command/src/main/java/org/apache/edgent/connectors/command/runtime/CommandWriter.java
index e3308dc..ac55e69 100644
--- a/connectors/command/src/main/java/org/apache/edgent/connectors/command/runtime/CommandWriter.java
+++ b/connectors/command/src/main/java/org/apache/edgent/connectors/command/runtime/CommandWriter.java
@@ -72,7 +72,7 @@
             closeProcess(); // and loop/retry
           }
         }
-        else if (currentConsumer == null && canStart()) {
+        else if (canStart()) {
           logger.debug("STARTING for: {}", value);
           start();  // and loop/retry
         }
diff --git a/connectors/command/src/test/java/org/apache/edgent/test/connectors/command/CommandStreamsTest.java b/connectors/command/src/test/java/org/apache/edgent/test/connectors/command/CommandStreamsTest.java
index e2ad3c7..bc96ad2 100644
--- a/connectors/command/src/test/java/org/apache/edgent/test/connectors/command/CommandStreamsTest.java
+++ b/connectors/command/src/test/java/org/apache/edgent/test/connectors/command/CommandStreamsTest.java
@@ -41,8 +41,6 @@
 import org.apache.edgent.topology.tester.Condition;
 import org.junit.Test;
 
-import com.google.gson.JsonObject;
-
 public class CommandStreamsTest extends DirectTopologyTestBase {
     
     private String[] stdLines = new String[] {
@@ -176,9 +174,24 @@
       assertNotNull(sink);
       
       try {
-        // start the job, sleep for a bit (await the timeout) then validate sink output
-        Condition<?> never = t.getTester().tupleCount(s, Long.MAX_VALUE);
-        t.getTester().complete(getSubmitter(), new JsonObject(), never, 3, TimeUnit.SECONDS);
+        // complete when the sink has generated the expected results
+        Condition<Object> tc = new Condition<Object>() {
+            public boolean valid() { 
+                try {
+                    return FileUtil.validateFile(tempFile1, getLines(), true);
+                } catch (Exception e) {
+                    return false;
+                }
+            }
+            public Object getResult() { return "todo-files-lines"; }
+        };
+
+        // If we time out, still validate content to see what we did get
+        try {
+            complete(t, tc, 3, TimeUnit.SECONDS);
+        } catch (Exception e) {
+            System.out.println("test time out");
+        }
 
         FileUtil.validateFile(tempFile1, getLines());
       }
@@ -229,11 +242,25 @@
       assertNotNull(sink);
       
       try {
-        // start the job, sleep for a bit (await the timeout) then validate sink output
-        Condition<?> never = t.getTester().tupleCount(s, Long.MAX_VALUE);
-        t.getTester().complete(getSubmitter(), new JsonObject(), never,
-            6 + ((NUM_RUNS-1) * 1/*restart delay*/), TimeUnit.SECONDS);
-        
+        // complete when the sink has generated the expected results
+        Condition<Object> tc = new Condition<Object>() {
+            public boolean valid() { 
+                try {
+                    return FileUtil.validateFile(tempFile1, expLines.toArray(new String[0]), true);
+                } catch (Exception e) {
+                    return false;
+                }
+            }
+            public Object getResult() { return "todo-files-lines"; }
+        };
+
+        // If we time out, still validate content to see what we did get
+        try {
+            complete(t, tc, 6 + ((NUM_RUNS-1) * 1/*restart delay*/), TimeUnit.SECONDS);
+        } catch (Exception e) {
+            System.out.println("test time out");
+        }
+
         FileUtil.validateFile(tempFile1, expLines.toArray(new String[0]));
       }
       finally {
@@ -242,7 +269,7 @@
     }
     
     private String getCmdPath(String cmd) {
-      return TestRepoPath.getPath("connectors", "command", "src", "test", "scripts", cmd);
+      return TestRepoPath.getPath(cmd);
     }
 
 }
diff --git a/connectors/command/src/test/scripts/sinkcmd b/connectors/command/src/test/resources/sinkcmd
similarity index 100%
rename from connectors/command/src/test/scripts/sinkcmd
rename to connectors/command/src/test/resources/sinkcmd
diff --git a/connectors/common/build.gradle b/connectors/common/build.gradle
deleted file mode 100644
index 99f1553..0000000
--- a/connectors/common/build.gradle
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:topology'
-  addTargetDirCoreExtJarDependencies 'compile'
-  
-  addTargetDirProjectJarDependency 'testCompile', ':providers:direct'
-  // N.B. root project adds test common dependencies
-}
-
-addCompileTestDependencies ':api:topology', ':providers:direct'
-
-test {
-  // this project lacks tests and this task fails if attempted 
-  enabled = false
-}
diff --git a/connectors/common/pom.xml b/connectors/common/pom.xml
new file mode 100644
index 0000000..8809194
--- /dev/null
+++ b/connectors/common/pom.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-connectors</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-connectors-common</artifactId>
+
+  <name>Apache Edgent (Java 8): Connectors: Common</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/connectors/common/src/test/java/org/apache/edgent/test/connectors/common/ConnectorTestBase.java b/connectors/common/src/test/java/org/apache/edgent/test/connectors/common/ConnectorTestBase.java
index f5230a9..82a70c0 100644
--- a/connectors/common/src/test/java/org/apache/edgent/test/connectors/common/ConnectorTestBase.java
+++ b/connectors/common/src/test/java/org/apache/edgent/test/connectors/common/ConnectorTestBase.java
@@ -23,10 +23,12 @@
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 import org.apache.edgent.test.providers.direct.DirectTopologyTestBase;
 import org.apache.edgent.topology.TStream;
 import org.apache.edgent.topology.Topology;
+import org.apache.edgent.topology.tester.Condition;
 
 public class ConnectorTestBase extends DirectTopologyTestBase {
     
@@ -102,5 +104,23 @@
 
         super.completeAndValidate(ordered, msg, t, s, secTimeout, expected);
     }
+    
+    public static Condition<Object> newWaitTimeCondition(int seconds) {
+        return new Condition<Object>() {
+            private long startTime = 0;
+            private long endTime = 0;
+            private volatile boolean done = false;
+            public boolean valid() {
+                if (startTime==0) {
+                    startTime = System.currentTimeMillis();
+                    endTime = startTime + TimeUnit.SECONDS.toMillis(seconds);
+                }
+                long now = System.currentTimeMillis();
+                done = now >= endTime;
+                return done;
+            }
+            public Object getResult() { return done; }
+        };
+    }
 
 }
diff --git a/connectors/common/src/test/java/org/apache/edgent/test/connectors/common/FileUtil.java b/connectors/common/src/test/java/org/apache/edgent/test/connectors/common/FileUtil.java
index 0c36c0f..4902544 100644
--- a/connectors/common/src/test/java/org/apache/edgent/test/connectors/common/FileUtil.java
+++ b/connectors/common/src/test/java/org/apache/edgent/test/connectors/common/FileUtil.java
@@ -30,6 +30,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -39,8 +40,8 @@
 
   /**
    * Create a temp file with the specified name, extension and contents.
-   * @param name
-   * @param extension
+   * @param name filename prefix 
+   * @param extension filename extension
    * @param lines content for the file
    * @return {@code Path} to temp file
    * @throws Exception on failure
@@ -69,9 +70,11 @@
    * Validate that the file contains the specified content.
    * @param path file to validate
    * @param lines the expected content
-   * @throws Exception on failure
+   * @param silent control return vs throw
+   * @return false on failure - only applicable when silent==true
+   * @throws Exception on failure when silent==false
    */
-  public static void validateFile(Path path, String[] lines) throws Exception {
+  public static boolean validateFile(Path path, String[] lines, boolean silent) throws Exception {
     List<String> actLines = new ArrayList<>();
     try (BufferedReader reader = 
           new BufferedReader(new InputStreamReader(
@@ -81,8 +84,26 @@
       while ((line = reader.readLine()) != null) {
         actLines.add(line);
       }
-      assertArrayEquals(lines, actLines.toArray(new String[actLines.size()]));
+      if (!silent)
+          assertArrayEquals(lines, actLines.toArray(new String[actLines.size()]));
+      else if (!Arrays.equals(lines, actLines.toArray(new String[actLines.size()])))
+          return false;
+      return true;
     }
+    catch (Exception e) {
+        if (!silent) throw e;
+        return false;
+    }
+  }
+  
+  /**
+   * Validate that the file contains the specified content.
+   * @param path file to validate
+   * @param lines the expected content
+   * @throws Exception on failure
+   */
+  public static void validateFile(Path path, String[] lines) throws Exception {
+      validateFile(path, lines, false);
   }
 
 }
diff --git a/connectors/common/src/test/java/org/apache/edgent/test/connectors/common/TestRepoPath.java b/connectors/common/src/test/java/org/apache/edgent/test/connectors/common/TestRepoPath.java
index bb3daf6..162d264 100644
--- a/connectors/common/src/test/java/org/apache/edgent/test/connectors/common/TestRepoPath.java
+++ b/connectors/common/src/test/java/org/apache/edgent/test/connectors/common/TestRepoPath.java
@@ -19,8 +19,8 @@
 package org.apache.edgent.test.connectors.common;
 
 import java.io.File;
-import java.nio.file.Path;
-import java.nio.file.Paths;
+import java.net.URISyntaxException;
+import java.net.URL;
 
 /**
  * Utility for tests to get the path to something in the local git repository.
@@ -33,29 +33,17 @@
      * Deals with implications of the different execution contexts:
      * eclipse/junit and ant/junit.
      * 
-     * @param testProject the project (e.g., "connectors") that the
-     *        test belongs to.
-     * @param more more components
+     * @param classpathPath the absolute path of the resource in the applications classpath.
      * @return absolute path in the repository
      */
-    public static String getPath(String testProject, String... more) {
-        String pathStr = System.getProperty("user.dir");
-        // Under eclipse/junit: path to project in repo: <repo>/<testProject>
-        // Under ant/junit: <repo>/<testProject>/<project>/unittests/testrunxxxxxxx
-        // Get the path to the <repo>
-        Path path = new File(pathStr).toPath();
-        do {
-            if (path.endsWith(testProject)) {
-                path = path.getParent();
-                break;
-            }
-            path = path.getParent();
-        } while (path != null);
-        // add the components to the repo-path
-        path = path.resolve(Paths.get(testProject, more));
-        if (!path.toFile().exists())
-            throw new IllegalArgumentException("File does not exist: "+path);
-        return path.toString();
+    public static String getPath(String classpathPath) {
+        try {
+            URL resourceUrl = ClassLoader.getSystemResource(classpathPath);
+            File resource = new File(resourceUrl.toURI());
+            return resource.getAbsolutePath();
+        } catch (URISyntaxException e) {
+            throw new RuntimeException("Could not get path of resource; " + classpathPath, e);
+        }
     }
 
 }
diff --git a/connectors/csv/build.gradle b/connectors/csv/build.gradle
deleted file mode 100644
index 6a08147..0000000
--- a/connectors/csv/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:topology'
-  addTargetDirCoreExtJarDependencies 'compile'
-  
-  addTargetDirProjectJarDependency 'testCompile', ':providers:direct'
-  // N.B. root project adds test common dependencies
-}
-
-addCompileTestDependencies ':api:topology', ':providers:direct', ':connectors:common'
diff --git a/connectors/csv/pom.xml b/connectors/csv/pom.xml
new file mode 100644
index 0000000..20eddae
--- /dev/null
+++ b/connectors/csv/pom.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-connectors</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-connectors-csv</artifactId>
+
+  <name>Apache Edgent (Java 8): Connectors: CSV</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.google.code.gson</groupId>
+      <artifactId>gson</artifactId>
+      <version>2.8.0</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+      <scope>test</scope>
+      <type>test-jar</type>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/connectors/edgent.javax.websocket/build.gradle b/connectors/edgent.javax.websocket/build.gradle
deleted file mode 100644
index 9a06f08..0000000
--- a/connectors/edgent.javax.websocket/build.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-archivesBaseName = project.name
-
-dependencies {
-  addProjectExtDependency 'compile', 'javax.websocket:javax.websocket-api:1.0'
-
-  // N.B. root project adds test common dependencies
-}
diff --git a/connectors/file/build.gradle b/connectors/file/build.gradle
deleted file mode 100644
index 6a08147..0000000
--- a/connectors/file/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:topology'
-  addTargetDirCoreExtJarDependencies 'compile'
-  
-  addTargetDirProjectJarDependency 'testCompile', ':providers:direct'
-  // N.B. root project adds test common dependencies
-}
-
-addCompileTestDependencies ':api:topology', ':providers:direct', ':connectors:common'
diff --git a/connectors/file/pom.xml b/connectors/file/pom.xml
new file mode 100644
index 0000000..7d14da9
--- /dev/null
+++ b/connectors/file/pom.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-connectors</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-connectors-file</artifactId>
+
+  <name>Apache Edgent (Java 8): Connectors: File</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-function</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-oplet</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-connectors-common</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/connectors/file/src/test/java/org/apache/edgent/test/connectors/file/FileStreamsTest.java b/connectors/file/src/test/java/org/apache/edgent/test/connectors/file/FileStreamsTest.java
index 8636888..542a137 100644
--- a/connectors/file/src/test/java/org/apache/edgent/test/connectors/file/FileStreamsTest.java
+++ b/connectors/file/src/test/java/org/apache/edgent/test/connectors/file/FileStreamsTest.java
@@ -32,7 +32,6 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.TimeUnit;
-import java.util.stream.Stream;
 
 import org.apache.edgent.connectors.file.FileStreams;
 import org.apache.edgent.function.BiFunction;
@@ -185,17 +184,28 @@
             throw new RuntimeException(e);
         }
     }
+    
+    private String[] toUpperCase(String[] strs) {
+        List<String> ucstrs = new ArrayList<>();
+        for (String s : strs) {
+            ucstrs.add(s.toUpperCase());
+        }
+        return ucstrs.toArray(new String[0]);
+    }
+    private String[] concat(String[] a1, String[] a2) {
+        List<String> res = new ArrayList<>();
+        for (String s : a1) res.add(s);
+        for (String s : a2) res.add(s);
+        return res.toArray(new String[0]);
+    }
 
     @Test
     public void testTextFileReader() throws Exception {
         Topology t = newTopology("testTextFileReader");
         
         String[] lines = getLines();
-        String[] ucLines = Stream.of(lines)
-                .map(line -> line.toUpperCase())
-                .toArray(String[]::new);
-        String[] allLines = Stream.concat(Stream.of(lines), Stream.of(ucLines))
-                .toArray(String[]::new);
+        String[] ucLines = toUpperCase(lines);
+        String[] allLines = concat(lines, ucLines);
         
         Path tempFile1 = FileUtil.createTempFile("test1", "txt", lines);
         Path tempFile2 = FileUtil.createTempFile("test2", "txt", ucLines);
@@ -218,11 +228,8 @@
         Topology t = newTopology("testTextFileReaderProblemPaths");
         
         String[] lines = getLines();
-        String[] ucLines = Stream.of(lines)
-                .map(line -> line.toUpperCase())
-                .toArray(String[]::new);
-        String[] allLines = Stream.concat(Stream.of(lines), Stream.of(ucLines))
-                .toArray(String[]::new);
+        String[] ucLines = toUpperCase(lines);
+        String[] allLines = concat(lines, ucLines);
         
         Path tempFile1 = FileUtil.createTempFile("test1", "txt", lines);
         Path tempFile2 = FileUtil.createTempFile("test2", "txt", ucLines);
@@ -250,9 +257,7 @@
         Topology t = newTopology("testTextFileReaderPrePost");
         
         String[] lines = getLines();
-        String[] ucLines = Stream.of(lines)
-                .map(line -> line.toUpperCase())
-                .toArray(String[]::new);
+        String[] ucLines = toUpperCase(lines);
         
         Path tempFile1 = FileUtil.createTempFile("test1", "txt", lines);
         Path tempFile2 = FileUtil.createTempFile("test2", "txt", ucLines);
diff --git a/connectors/file/src/test/java/org/apache/edgent/test/connectors/file/FileStreamsTextFileWriterTest.java b/connectors/file/src/test/java/org/apache/edgent/test/connectors/file/FileStreamsTextFileWriterTest.java
index 576e677..fe3b54a 100644
--- a/connectors/file/src/test/java/org/apache/edgent/test/connectors/file/FileStreamsTextFileWriterTest.java
+++ b/connectors/file/src/test/java/org/apache/edgent/test/connectors/file/FileStreamsTextFileWriterTest.java
@@ -35,6 +35,9 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -83,6 +86,19 @@
     public String[] getLines() {
         return stdLines;
     }
+    
+    // Cover for Java8 Files.newBufferedReader() convenience fn
+    private static BufferedReader newBufferedReader(Path path) throws IOException {
+        return newBufferedReader(path, StandardCharsets.UTF_8);
+    }
+    private static BufferedReader newBufferedReader(Path path, Charset cs)
+            throws IOException
+        {
+            CharsetDecoder decoder = cs.newDecoder();
+            Reader reader = new InputStreamReader(Files.newInputStream(path), decoder);
+            return new BufferedReader(reader);
+        }
+
 
     @Test
     public void testFlushConfig() throws Exception {
@@ -301,6 +317,8 @@
         // default writer policy
         TSink<String> sink = FileStreams.textFileWriter(s, () -> basePath.toString());
 
+        // note, with only 4 tuples, default policy won't cycle (finalize the cur file)
+        // to make the expResults present until job stops (TMO)
         completeAndValidateWriter(t, TMO_SEC, basePath, expResults);
         
         assertNotNull(sink);
@@ -427,6 +445,14 @@
 
     @Test
     public void testRetainAgeBased() throws Exception {
+        // The mechanisms of this test no longer work with the
+        // recent changes to bump the tmo 2x when edgent.build.ci=true.
+        // So disable while we continue to work on this.
+        //
+        // With the general changes for completion condition checking,
+        // I think this has the chance of working again.
+//        assumeTrue(!Boolean.getBoolean("edgent.build.ci"));
+
         Topology t = newTopology("testRetainAgeBased");
         
         // establish a base path
@@ -436,9 +462,8 @@
         
         // build expected results
         int keepCnt = 2;  // only keep the last n files with throttling, age,
-                          // and TMO_SEC
         int ageSec = 5;
-        long periodMsec = TimeUnit.SECONDS.toMillis(1);
+        long periodMsec = TimeUnit.SECONDS.toMillis(1);  // age retention checking period
         // net one tuple per file
         List<List<String>> expResults = buildExpResults(lines, tuple -> true);
         for (int i = 0; i < keepCnt; i++)
@@ -453,9 +478,11 @@
         // With 4 tuples, throttleDelay=2sec, and ageSec=5
         // t0=add-f1, t1, t2=add-f2, t3, t4=add-f3, t5-rm-f1, t6=add-f4, t7=rm-f2, t8, t9=rm-f3, ...
         //
-        // So we want to check somewhere around t8 (after t7 and definitely before t9)
-        // so all 4 files were created and the first 2 have been aged out.
-        // with complete delay = #files-1*throttle + TMO_SEC, should be 6+2 == t8.
+        // The expected results happen somewhere around t8 (after t7 and definitely before t9),
+        // all 4 files were created and the first 2 have been aged out.
+        //
+        // If the "completion condition" doesn't manage to get run during that interval
+        // the test will fail even if the code is working fine. 
         
         int throttleSec = 2;
         TStream<String> s = PlumbingStreams.blockingThrottle(
@@ -489,7 +516,7 @@
         
         IFileWriterPolicy<String> policy = new FileWriterPolicy<String>(
                 FileWriterFlushConfig.newImplicitConfig(),
-                FileWriterCycleConfig.newCountBasedConfig(1000),
+                FileWriterCycleConfig.newCountBasedConfig(expResults.get(0).size()),
                 FileWriterRetentionConfig.newFileCountBasedConfig(10)
                 );
         FileStreams.textFileWriter(s, () -> basePath.toString(), () -> policy);
@@ -514,7 +541,7 @@
         
         IFileWriterPolicy<String> policy = new FileWriterPolicy<String>(
                 FileWriterFlushConfig.newCountBasedConfig(1),  // every tuple
-                FileWriterCycleConfig.newCountBasedConfig(1000),  // all in 1 file
+                FileWriterCycleConfig.newCountBasedConfig(expResults.get(0).size()),  // all in 1 file
                 FileWriterRetentionConfig.newFileCountBasedConfig(10)
                 );
         FileStreams.textFileWriter(s, () -> basePath.toString(), () -> policy);
@@ -543,7 +570,7 @@
         
         IFileWriterPolicy<String> policy = new FileWriterPolicy<String>(
                 FileWriterFlushConfig.newTimeBasedConfig(TimeUnit.MILLISECONDS.toMillis(250)),
-                FileWriterCycleConfig.newCountBasedConfig(1000),  // all in 1 file
+                FileWriterCycleConfig.newCountBasedConfig(expResults.get(0).size()),  // all in 1 file
                 FileWriterRetentionConfig.newFileCountBasedConfig(10)
                 );
         FileStreams.textFileWriter(s, () -> basePath.toString(), () -> policy);
@@ -570,7 +597,7 @@
         IFileWriterPolicy<String> policy = new FileWriterPolicy<String>(
                 FileWriterFlushConfig.newPredicateBasedConfig(
                         tuple -> tuple.startsWith("1-") || tuple.startsWith("3-")),
-                FileWriterCycleConfig.newCountBasedConfig(1000),  // all in 1 file
+                FileWriterCycleConfig.newCountBasedConfig(expResults.get(0).size()),  // all in 1 file
                 FileWriterRetentionConfig.newFileCountBasedConfig(10)
                 );
         FileStreams.textFileWriter(s, () -> basePath.toString(), () -> policy);
@@ -676,7 +703,9 @@
 
         // build expected results
         // a tuple based config / predicate.  in this case should end up with 3 files.
-        Predicate<String> cycleIt = tuple -> tuple.startsWith("1-") || tuple.startsWith("3-");
+        // flush on the last tuple too to ensure the test completes before TMO.
+        Predicate<String> cycleIt = tuple -> tuple.startsWith("1-") || tuple.startsWith("3-")
+                                        || tuple.equals(lines[lines.length-1]);
         List<List<String>> expResults = buildExpResults(lines, cycleIt);
         assertEquals(3, expResults.size());
 
@@ -818,7 +847,7 @@
         System.out.println("<<<<< Dumping "+f);
         try {
             Path path = f.toPath();
-            try (BufferedReader br = Files.newBufferedReader(path)) {
+            try (BufferedReader br = newBufferedReader(path)) {
                 br.lines().forEach(line -> System.out.println(line));
             }
         }
@@ -847,34 +876,59 @@
             Path basePath, List<List<T>> expResults) throws Exception {
         
         try {
-            // just let it run to the tmo before we check the file contents
+            // wait until the right number of files and content or we timeout.
+            // (don't use a wait-till-tmo scheme as that's "too slow" especially
+            // when complete() adds a TMO multiplier when edgent.build.ci=true)
             Condition<Object> tc = new Condition<Object>() {
-                public boolean valid() { return false; }
-                public Object getResult() { return null; }
+                public boolean valid() {
+                    try {
+                        return checkFiles(basePath, expResults, true);
+                    } catch (Exception e) {
+                        return false;
+                    }
+                }
+                public Object getResult() { return getActFiles(basePath).size(); }
             };
-            
-            complete(t, tc, tmoSec, TimeUnit.SECONDS);
+
+            // if we time out we probably need to know which files are present to diagnose
+            try {
+                complete(t, tc, tmoSec, TimeUnit.SECONDS);
+            } catch(Exception e) {
+                System.out.println("completed with exception: "+e);
+            }
 
             System.out.println("########## "+t.getName());
-
-            // right number of files?
-            List<Path> actFiles = getActFiles(basePath);
-            System.out.println("actFiles: "+actFiles);
-            assertEquals(actFiles.toString(), expResults.size(), actFiles.size());
             
-            // do the file(s) have the right contents?
-            System.out.println("expResults: "+expResults);
-            int i = 0;
-            for (List<T> expFile : expResults) {
-                Path path = actFiles.get(i++);
-                checkContents(path, expFile.toArray(new String[0]));
-            }
+            checkFiles(basePath, expResults, false);
         }
         finally {
             deleteAll(basePath);
         }
     }
     
+    // silent==true => return false on fail; silent==false => asserts/throws on fail
+    private <T> boolean checkFiles(Path basePath, List<List<T>> expResults, boolean silent) {
+
+        // right number of files?
+        List<Path> actFiles = getActFiles(basePath);
+        if (!silent) System.out.println("actFiles: "+actFiles);
+        if (!silent) 
+            assertEquals(actFiles.toString(), expResults.size(), actFiles.size());
+        else if (expResults.size() != actFiles.size())
+            return false;
+        
+        // do the file(s) have the right contents?
+        if (!silent) System.out.println("expResults: "+expResults);
+        int i = 0;
+        for (List<T> expFile : expResults) {
+            Path path = actFiles.get(i++);
+            if (!checkContents(path, expFile.toArray(new String[0]), silent))
+                return false;
+        }
+        
+        return true;
+    }
+    
     private void deleteAll(Path basePath) {
         Path parent = basePath.getParent();
         String baseLeaf = basePath.getFileName().toString();
@@ -898,28 +952,39 @@
         return paths;
     }
     
-    private void checkContents(Path path, String[] lines) {
+    // silent==true => return false on fail; silent==false => asserts/throws on fail
+    private boolean checkContents(Path path, String[] lines, boolean silent) {
         if (path.getFileName().toString().endsWith(".zip")) {
-          checkZipContents(path, lines);
-          return;
+          return checkZipContents(path, lines, silent);
         }
-        System.out.println("checking file "+path);
+        if (!silent) System.out.println("checking file "+path);
         int lineCnt = 0;
-        try (BufferedReader br = Files.newBufferedReader(path)) {
+        try (BufferedReader br = newBufferedReader(path)) {
             for (String line : lines) {
                 ++lineCnt;
                 String actLine = br.readLine();
-                assertEquals("path:"+path+" line "+lineCnt, line, actLine);
+                if (!silent)
+                    assertEquals("path:"+path+" line "+lineCnt, line, actLine);
+                else if (!line.equals(actLine))
+                    return false;
             }
-            assertNull("path:"+path+" line "+lineCnt+" expected EOF", br.readLine());
+            if (!silent)
+                assertNull("path:"+path+" line "+lineCnt+" expected EOF", br.readLine());
+            else if (null != br.readLine())
+                return false;
         }
         catch (IOException e) {
-            assertNull("path:"+path+" line "+lineCnt+" unexpected IOException "+e, e);
+            if (!silent)
+                assertNull("path:"+path+" line "+lineCnt+" unexpected IOException "+e, e);
+            else
+                return false;
         }
+        return true;
     }
     
-    private void checkZipContents(Path path, String[] lines) {
-        System.out.println("checking file "+path);
+    // silent==true => return false on fail; silent==false => asserts/throws on fail
+    private boolean checkZipContents(Path path, String[] lines, boolean silent) {
+        if (!silent) System.out.println("checking file "+path);
         String fileName = path.getFileName().toString();
         String entryName = fileName.substring(0, fileName.length() - ".zip".length());
         int lineCnt = 0;
@@ -930,18 +995,31 @@
         {
           ZipEntry entry = zin.getNextEntry();
           
-          assertEquals(entryName, entry.getName());
+          if (!silent)
+              assertEquals(entryName, entry.getName());
+          else if (!entryName.equals(entry.getName()))
+              return false;
 
           BufferedReader br = new BufferedReader(new InputStreamReader(zin, StandardCharsets.UTF_8));
           for (String line : lines) {
             ++lineCnt;
             String actLine = br.readLine();
-            assertEquals("path:"+path+" line "+lineCnt, line, actLine);
+            if (!silent)
+                assertEquals("path:"+path+" line "+lineCnt, line, actLine);
+            else if (!line.equals(actLine))
+                return false;
           }
-          assertNull("path:"+path+" line "+lineCnt+" expected EOF", br.readLine());
+          if (!silent)
+              assertNull("path:"+path+" line "+lineCnt+" expected EOF", br.readLine());
+          else if (null != br.readLine())
+              return false;
         }
         catch (IOException e) {
-          assertNull("path:"+path+" line "+lineCnt+" unexpected IOException "+e, e);
+            if (!silent)
+                assertNull("path:"+path+" line "+lineCnt+" unexpected IOException "+e, e);
+            else
+                return false;
         }
+        return true;
     }
 }
diff --git a/connectors/http/build.gradle b/connectors/http/build.gradle
deleted file mode 100644
index caf8636..0000000
--- a/connectors/http/build.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:topology'
-  addProjectExtDependency 'compile', 'org.apache.httpcomponents:httpclient:4.5.1'
-  addProjectExtDependency 'compile', 'org.apache.httpcomponents:httpcore:4.4.4'
-  addTargetDirCoreExtJarDependencies 'compile'
-  
-  addTargetDirProjectJarDependency 'testCompile', ':providers:direct'
-  // N.B. root project adds test common dependencies
-}
-
-addCompileTestDependencies ':api:topology', ':providers:direct'
diff --git a/connectors/http/pom.xml b/connectors/http/pom.xml
new file mode 100644
index 0000000..81345d7
--- /dev/null
+++ b/connectors/http/pom.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-connectors</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-connectors-http</artifactId>
+
+  <name>Apache Edgent (Java 8): Connectors: HTTP</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpclient</artifactId>
+      <version>4.5.1</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpcore</artifactId>
+      <version>4.4.4</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/connectors/http/src/test/java/org/apache/edgent/test/connectors/http/HttpTest.java b/connectors/http/src/test/java/org/apache/edgent/test/connectors/http/HttpTest.java
index 0511150..9f08879 100644
--- a/connectors/http/src/test/java/org/apache/edgent/test/connectors/http/HttpTest.java
+++ b/connectors/http/src/test/java/org/apache/edgent/test/connectors/http/HttpTest.java
@@ -184,7 +184,7 @@
     /**
      * Test basic authentication, first with valid user/password
      * and then with invalid (results in 401).
-     * @throws Exception
+     * @throws Exception on failure
      */
     @Test
     public void testBasicAuthentication() throws Exception {
diff --git a/connectors/iot/build.gradle b/connectors/iot/build.gradle
deleted file mode 100644
index 6a86d26..0000000
--- a/connectors/iot/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:topology'
-  addTargetDirCoreExtJarDependencies 'compile'
-  
-  addTargetDirProjectJarDependency 'testCompile', ':providers:direct'
-  // N.B. root project adds test common dependencies
-}
-
-addCompileTestDependencies ':api:topology', ':providers:direct'
diff --git a/connectors/iot/pom.xml b/connectors/iot/pom.xml
new file mode 100644
index 0000000..80a9c1b
--- /dev/null
+++ b/connectors/iot/pom.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-connectors</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-connectors-iot</artifactId>
+
+  <name>Apache Edgent (Java 8): Connectors: IoT</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-function</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/connectors/iotp/build.gradle b/connectors/iotp/build.gradle
deleted file mode 100644
index c5e2390..0000000
--- a/connectors/iotp/build.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:topology'
-  addTargetDirProjectJarDependency 'compile', ':connectors:iot'
-  addProjectExtDependency 'compile', 'com.ibm.messaging:watson-iot:0.2.2'
-  addTargetDirCoreExtJarDependencies 'compile'
-  
-  addTargetDirProjectJarDependency 'testCompile', ':providers:direct'
-  // N.B. root project adds test common dependencies
-}
-
-addCompileTestDependencies ':api:topology', ':providers:direct'
diff --git a/connectors/iotp/pom.xml b/connectors/iotp/pom.xml
new file mode 100644
index 0000000..da3bb7c
--- /dev/null
+++ b/connectors/iotp/pom.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-connectors</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-connectors-iotp</artifactId>
+
+  <name>Apache Edgent (Java 8): Connectors: IoTP</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-function</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-connectors-iot</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>com.ibm.messaging</groupId>
+      <artifactId>watson-iot</artifactId>
+      <version>0.2.2</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/connectors/iotp/src/main/java/org/apache/edgent/connectors/iotp/IotpGWDevice.java b/connectors/iotp/src/main/java/org/apache/edgent/connectors/iotp/IotpGWDevice.java
index 786fd59..bfebebc 100644
--- a/connectors/iotp/src/main/java/org/apache/edgent/connectors/iotp/IotpGWDevice.java
+++ b/connectors/iotp/src/main/java/org/apache/edgent/connectors/iotp/IotpGWDevice.java
@@ -91,7 +91,7 @@
   @Override
   public boolean equals(Object o2) {
     return o2 == this 
-        || equals(o2 instanceof IotpGWDevice && ((IotpGWDevice)o2).fqDeviceId.equals(fqDeviceId));
+        || (o2 instanceof IotpGWDevice && ((IotpGWDevice)o2).fqDeviceId.equals(fqDeviceId));
   }
 
   @Override
diff --git a/connectors/javax.websocket-client/build.gradle b/connectors/javax.websocket-client/build.gradle
deleted file mode 100644
index 0391c10..0000000
--- a/connectors/javax.websocket-client/build.gradle
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-archivesBaseName = project.name
-
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':connectors:edgent.javax.websocket'
-  addProjectExtDependency 'compile', 'org.eclipse.jetty.websocket:javax-websocket-client-impl:9.3.6.v20151106'
-  
-  // N.B. root project adds test common dependencies
-}
diff --git a/connectors/javax.websocket-client/src/main/resources/META-INF/services/org.apache.edgent.javax.websocket.EdgentSslContainerProvider b/connectors/javax.websocket-client/src/main/resources/META-INF/services/org.apache.edgent.javax.websocket.EdgentSslContainerProvider
deleted file mode 100644
index 7ebe858..0000000
--- a/connectors/javax.websocket-client/src/main/resources/META-INF/services/org.apache.edgent.javax.websocket.EdgentSslContainerProvider
+++ /dev/null
@@ -1 +0,0 @@
-org.apache.edgent.javax.websocket.impl.EdgentSslContainerProviderImpl
\ No newline at end of file
diff --git a/connectors/javax.websocket-server/build.gradle b/connectors/javax.websocket-server/build.gradle
deleted file mode 100644
index 16d7f5f..0000000
--- a/connectors/javax.websocket-server/build.gradle
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-archivesBaseName = project.name
-
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':connectors:javax.websocket-client'
-  addProjectExtDependency 'compile', 'org.eclipse.jetty.websocket:javax-websocket-server-impl:9.3.6.v20151106'
-  
-  // N.B. root project adds test common dependencies
-}
diff --git a/connectors/jdbc/.gitignore b/connectors/jdbc/.gitignore
new file mode 100644
index 0000000..14903f1
--- /dev/null
+++ b/connectors/jdbc/.gitignore
@@ -0,0 +1,3 @@
+# test created swill
+derby.log
+/JdbcStreamsTestDb
diff --git a/connectors/jdbc/build.gradle b/connectors/jdbc/build.gradle
deleted file mode 100644
index 0eef8c2..0000000
--- a/connectors/jdbc/build.gradle
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:topology'
-  addTargetDirCoreExtJarDependencies 'compile'
-  addTargetDirProjectJarDependency 'testCompile', ':providers:direct'
-  
-  testCompile files("${System.env.DERBY_HOME}/lib/derby.jar")
-  // N.B. root project adds test common dependencies
-}
-
-addCompileTestDependencies ':api:topology', ':providers:direct', ':connectors:common'
diff --git a/connectors/jdbc/pom.xml b/connectors/jdbc/pom.xml
new file mode 100644
index 0000000..d442266
--- /dev/null
+++ b/connectors/jdbc/pom.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-connectors</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-connectors-jdbc</artifactId>
+
+  <name>Apache Edgent (Java 8): Connectors: JDBC</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <!--
+                Set an environment variable to make sure any derby log is created
+                inside the target directory as well as pass in a location for
+                creating the database.
+          -->
+          <systemProperties>
+            <property>
+              <name>jdbcStreamsTest.tmpdir</name>
+              <value>${project.build.directory}/jdbc-test-tmp</value>
+            </property>
+            <property>
+              <name>derby.stream.error.file</name>
+              <value>${project.build.directory}/derby.log</value>
+            </property>
+          </systemProperties>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-function</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-connectors-common</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <!-- https://mvnrepository.com/artifact/org.apache.derby/derby -->
+    <dependency>
+      <groupId>org.apache.derby</groupId>
+      <artifactId>derby</artifactId>
+      <version>10.13.1.1</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/connectors/jdbc/src/test/java/org/apache/edgent/test/connectors/jdbc/JdbcStreamsTest.java b/connectors/jdbc/src/test/java/org/apache/edgent/test/connectors/jdbc/JdbcStreamsTest.java
index a15b9d9..a284340 100644
--- a/connectors/jdbc/src/test/java/org/apache/edgent/test/connectors/jdbc/JdbcStreamsTest.java
+++ b/connectors/jdbc/src/test/java/org/apache/edgent/test/connectors/jdbc/JdbcStreamsTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import java.lang.reflect.Method;
@@ -32,43 +33,46 @@
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.Collectors;
 
 import javax.sql.DataSource;
 
 import org.apache.edgent.connectors.jdbc.JdbcStreams;
+import org.apache.edgent.function.Predicate;
 import org.apache.edgent.test.connectors.common.ConnectorTestBase;
 import org.apache.edgent.topology.TSink;
 import org.apache.edgent.topology.TStream;
 import org.apache.edgent.topology.Topology;
 import org.apache.edgent.topology.plumbing.PlumbingStreams;
+import org.apache.edgent.topology.tester.Condition;
 import org.junit.Test;
 
 /**
  * JdbcStreams connector tests.
  * <p>
  * The tests use Apache Embedded Derby as the backing dbms.
- * The Oracle JDK includes Derby in $JAVA_HOME/db.
- * Manually install Derby for other JDKs if required.
- * Arrange for the classpath to be configured by one of:
- * <ul>
- * <li>manually add derby.jar to the classpath</li>
- * <li>set the DERBY_HOME environment variable.  connectors/jdbc/build.gradle adds 
- *     $DERBY_HOME/lib/derby.jar to the classpath when running the tests.
- *     e.g., try
- *     <ul>
- *       <li> export DERBY_HOME=$JAVA_HOME/db</li>
- *       <li> OSX: export DERBY_HOME=`/usr/libexec/java_home`/db
- *     </ul>
- *     </li>
- * </ul>
+ * The connectors/jdbc/pom.xml includes a "test" dependency on derby 
+ * so execution of the test via maven automatially retrieves and adds
+ * the derby jar to the classpath.  
+ * The pom also defines jdbcStreamsTest.tmpdir and redirects the derby.log location.
+ * <p>
+ * If running the test from Eclipse you may have to manually add derby.jar
+ * to the test's classpath.
+ * The Oracle JDK includes Derby in $JAVA_HOME/db/lib/derby.jar.
+ * <p>
  * The tests are "skipped" if the dbms's jdbc driver can't be found.
  */
 public class JdbcStreamsTest  extends ConnectorTestBase {
     
     private static final int SEC_TIMEOUT = 10;
-    private final static String DB_NAME = "JdbcStreamsTestDb";
-    private final static String USERNAME = System.getProperty("user.name");
+    private final static String TMPDIR_PROPERTY_NAME = "jdbcStreamsTest.tmpdir";
+    private final static String TMPDIR = System.getProperty(TMPDIR_PROPERTY_NAME, "");
+    static {
+        if (TMPDIR.equals("")) {
+            throw new RuntimeException("System property \""+TMPDIR_PROPERTY_NAME+"\" is not set.");
+        }
+    }
+    private final static String DB_NAME = TMPDIR + "/JdbcStreamsTestDb";
+    private final static String USERNAME = "test"; // can't contain "."
     private final static String PW = "none";
     private static final List<Person> personList = new ArrayList<>();
     static {
@@ -124,7 +128,7 @@
     {
         // Avoid a compile-time dependency to the jdbc driver.
         // At runtime, require that the classpath can find it.
-        // e.g., build.xml adds $DERBY_HOME/lib/derby.jar to the test classpath
+        // e.g., pom.xml adds derby.jar to the test classpath
 
         String DERBY_DATA_SOURCE = "org.apache.derby.jdbc.EmbeddedDataSource";
     
@@ -134,9 +138,6 @@
         }
         catch (ClassNotFoundException e) {
             String msg = "Fix the test classpath. ";
-            if (System.getenv("DERBY_HOME") == null) {
-                msg += "DERBY_HOME not set. ";
-            }
             msg += "Class not found: "+e.getLocalizedMessage();
             System.err.println(msg);
             assumeTrue(false);
@@ -236,15 +237,18 @@
         return rcvdPerson;
     }
     
-    private static java.util.function.Predicate<Person> newOddIdPredicate() {
+    private static Predicate<Person> newOddIdPredicate() {
         return (person) -> person.id % 2 != 0;
     }
     
-    private List<String> expectedPersons(java.util.function.Predicate<Person> predicate, List<Person> persons) {
-        return persons.stream()
-                .filter(predicate)
-                .map(person -> person.toString())
-                .collect(Collectors.toList());
+    private List<String> expectedPersons(Predicate<Person> predicate, List<Person> persons) {
+        List<String> expPersons = new ArrayList<>();
+        for (Person p : persons) {
+            if (predicate.test(p)) {
+                expPersons.add(p.toString());
+            }
+        }
+        return expPersons;
     }
 
     @Test
@@ -462,6 +466,7 @@
                         executionExcCnt.incrementAndGet();
                         return;
                     }
+                    // don't ever expect to get here in this case
                     resultSet.next();
                     int id = resultSet.getInt("id");
                     String firstName = resultSet.getString("firstname");
@@ -473,10 +478,21 @@
                 );
         TStream<String> rcvd = rcvdPerson.map(person -> person.toString());
         
+        // Await completion on having received the correct number of exception.
+        // Then also verify that no non-exceptional results were received.
+        Condition<Object> tc = new Condition<Object>() {
+            public boolean valid() {
+                return executionExcCnt.get() == expectedExcCnt;
+            }
+            public Object getResult() { return executionExcCnt.get(); }
+        };
+        Condition<List<String>> rcvdContents = t.getTester().streamContents(rcvd, expected.toArray(new String[0]));
+        
         rcvd.sink(tuple -> System.out.println(
                 String.format("%s rcvd: %s", t.getName(), tuple)));
-        completeAndValidate("", t, rcvd, SEC_TIMEOUT, expected.toArray(new String[0]));
+        complete(t, tc, SEC_TIMEOUT, TimeUnit.SECONDS);
         assertEquals("executionExcCnt", expectedExcCnt, executionExcCnt.get());
+        assertTrue("rcvd: "+rcvdContents.getResult(), rcvdContents.valid());
     }
     
     @Test
diff --git a/connectors/kafka/build.gradle b/connectors/kafka/build.gradle
deleted file mode 100644
index 047bed7..0000000
--- a/connectors/kafka/build.gradle
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:topology'
-  
-  // The pom for kafka includes dependencies that don't make sense for us.
-  // In at least one case kafka dependencies include a slf4j *implementation* jar
-  // and that conflicts with our samples' binding to a particular
-  // version of slf4j implementation.
-  // This all seems like fallout from, I believe, the kafka jars containing
-  // the code for their cli tools too, and possibly tests, which need things like:
-  //   slf4j-log4j12, snappy-java, jline, jopt-simple, junit-3.8.1
-  //
-  // So at least for now, avoid transitive and just match our ant based config
-  //
-  // addProjectExtDependency 'compile', 'org.apache.kafka:kafka_2.10:0.8.2.2'
-  // addProjectExtDependency 'compile', 'org.apache.kafka:kafka-clients:0.8.2.2'
-  addProjectExtDependency 'compile', 'org.apache.kafka:kafka_2.10:0.8.2.2@jar'
-  addProjectExtDependency 'compile', 'org.apache.kafka:kafka-clients:0.8.2.2@jar'
-  addProjectExtDependency 'compile', 'log4j:log4j:1.2.16@jar'
-  addProjectExtDependency 'compile', 'com.yammer.metrics:metrics-core:2.2.0@jar'
-  addProjectExtDependency 'compile', 'org.scala-lang:scala-library:2.10.4@jar'
-  addProjectExtDependency 'compile', 'com.101tec:zkclient:0.3@jar'
-  addProjectExtDependency 'compile', 'org.apache.zookeeper:zookeeper:3.4.6@jar'
-  
-  addTargetDirCoreExtJarDependencies 'compile'
-  
-  addTargetDirProjectJarDependency 'testCompile', ':providers:direct'
-  // N.B. root project adds test common dependencies
-}
-
-addCompileTestDependencies ':api:topology', ':providers:direct', ':connectors:common'
diff --git a/connectors/kafka/pom.xml b/connectors/kafka/pom.xml
new file mode 100644
index 0000000..e2d3b09
--- /dev/null
+++ b/connectors/kafka/pom.xml
@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-connectors</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-connectors-kafka</artifactId>
+
+  <name>Apache Edgent (Java 8): Connectors: Kafka</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <!--
+   The pom for kafka includes dependencies that don't make sense for us.
+   In at least one case kafka dependencies include a slf4j *implementation* jar
+   and that conflicts with our samples' binding to a particular
+   version of slf4j implementation.
+   This all seems like fallout from, I believe, the kafka jars containing
+   the code for their cli tools too, and possibly tests, which need things like:
+     slf4j-log4j12, snappy-java, jline, jopt-simple, junit-3.8.1
+
+   So at least for now, avoid transitive and just match our ant/gradle based
+   config.  Include only the exact dependencies that our kafka
+   dependency requires to function properly.
+   -->
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.kafka</groupId>
+      <artifactId>kafka_2.10</artifactId>
+      <version>0.8.2.2</version>
+      <exclusions>
+        <exclusion> <!-- not transitive -->
+          <groupId>*</groupId>
+          <artifactId>*</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.kafka</groupId>
+      <artifactId>kafka-clients</artifactId>
+      <version>0.8.2.2</version>
+      <exclusions>
+        <exclusion> <!-- not transitive -->
+          <groupId>*</groupId>
+          <artifactId>*</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+      <version>1.2.16</version>
+      <exclusions>
+        <exclusion> <!-- not transitive -->
+          <groupId>*</groupId>
+          <artifactId>*</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>com.yammer.metrics</groupId>
+      <artifactId>metrics-core</artifactId>
+      <version>2.2.0</version>
+      <exclusions>
+        <exclusion> <!-- not transitive -->
+          <groupId>*</groupId>
+          <artifactId>*</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.scala-lang</groupId>
+      <artifactId>scala-library</artifactId>
+      <version>2.10.4</version>
+      <exclusions>
+        <exclusion> <!-- not transitive -->
+          <groupId>*</groupId>
+          <artifactId>*</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>com.101tec</groupId>
+      <artifactId>zkclient</artifactId>
+      <version>0.3</version>
+      <exclusions>
+        <exclusion> <!-- not transitive -->
+          <groupId>*</groupId>
+          <artifactId>*</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.zookeeper</groupId>
+      <artifactId>zookeeper</artifactId>
+      <version>3.4.6</version>
+      <exclusions>
+        <exclusion> <!-- not transitive -->
+          <groupId>*</groupId>
+          <artifactId>*</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-connectors-common</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/connectors/kafka/src/main/java/org/apache/edgent/connectors/kafka/package-info.java b/connectors/kafka/src/main/java/org/apache/edgent/connectors/kafka/package-info.java
index 3e27717..b62af36 100644
--- a/connectors/kafka/src/main/java/org/apache/edgent/connectors/kafka/package-info.java
+++ b/connectors/kafka/src/main/java/org/apache/edgent/connectors/kafka/package-info.java
@@ -17,7 +17,7 @@
 under the License.
 */
 /**
- * Apache Kafka enterprise messing hub stream connector.
+ * Apache Kafka enterprise messaging hub stream connector.
  * <p>
  * The connector uses and includes components from the Kafka 0.8.2.2 release.
  * It has been successfully tested against kafka_2.11-0.10.1.0 and kafka_2.11-0.9.0.0 server as well.
diff --git a/connectors/kafka/src/test/java/org/apache/edgent/test/connectors/kafka/KafkaStreamsSkipMeTest.java b/connectors/kafka/src/test/java/org/apache/edgent/test/connectors/kafka/KafkaStreamsSkipMeTest.java
index 78bdc47..fbc9a8c 100644
--- a/connectors/kafka/src/test/java/org/apache/edgent/test/connectors/kafka/KafkaStreamsSkipMeTest.java
+++ b/connectors/kafka/src/test/java/org/apache/edgent/test/connectors/kafka/KafkaStreamsSkipMeTest.java
@@ -23,7 +23,7 @@
 import org.junit.Test;
 
 /*
- * Our current gradle driven test config (test filtering with
+ * Our current maven/gradle driven test config (test filtering with
  * includeTestsMatching '*Test') results in failing a project's
  * test task if the project lacks any "*Test" classes.
  * 
diff --git a/connectors/mqtt/build.gradle b/connectors/mqtt/build.gradle
deleted file mode 100644
index 2d1d32e..0000000
--- a/connectors/mqtt/build.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:topology'
-  addTargetDirProjectJarDependency 'compile', ':connectors:iot'
-  addTargetDirProjectJarDependency 'compile', ':connectors:common'
-  addProjectExtDependency 'compile', 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0'
-  
-  addTargetDirProjectJarDependency 'testCompile', ':providers:direct'
-  // N.B. root project adds test common dependencies
-}
-
-addCompileTestDependencies ':api:topology', ':providers:direct', ':connectors:common'
diff --git a/connectors/mqtt/pom.xml b/connectors/mqtt/pom.xml
new file mode 100644
index 0000000..e3192e2
--- /dev/null
+++ b/connectors/mqtt/pom.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-connectors</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-connectors-mqtt</artifactId>
+
+  <name>Apache Edgent (Java 8): Connectors: MQTT</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.rat</groupId>
+        <artifactId>apache-rat-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>license-check</id>
+            <phase>verify</phase>
+            <goals>
+              <goal>check</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <excludes combine.children="append">
+            <exclude>src/test/resources/keystores/*.crt</exclude>
+            <exclude>src/test/resources/keystores/*.key</exclude>
+            <exclude>src/test/resources/keystores/*.srl</exclude>
+            <exclude>src/test/resources/keystores/*.csr</exclude>
+            <exclude>src/test/resources/keystores/*.p12</exclude>
+            <exclude>src/test/resources/keystores/mosquitto-SSL.conf.template</exclude>
+            <exclude>src/test/resources/keystores/mosquitto.org.crt</exclude>
+          </excludes>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-connectors-iot</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-connectors-common</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.paho</groupId>
+      <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
+      <version>1.1.0</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-connectors-common</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+      <version>3.4</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/connectors/mqtt/src/test/java/org/apache/edgent/test/connectors/mqtt/MqttStreamsTestManual.java b/connectors/mqtt/src/test/java/org/apache/edgent/test/connectors/mqtt/MqttStreamsTestManual.java
index 2ab320d..4eae1f1 100644
--- a/connectors/mqtt/src/test/java/org/apache/edgent/test/connectors/mqtt/MqttStreamsTestManual.java
+++ b/connectors/mqtt/src/test/java/org/apache/edgent/test/connectors/mqtt/MqttStreamsTestManual.java
@@ -38,8 +38,8 @@
 import java.util.Properties;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
-import java.util.stream.Collectors;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.edgent.connectors.mqtt.MqttConfig;
 import org.apache.edgent.connectors.mqtt.MqttStreams;
 import org.apache.edgent.function.BiFunction;
@@ -112,7 +112,7 @@
     }
     
     protected String getKeystorePath(String storeLeaf) {
-        return TestRepoPath.getPath("connectors", "mqtt", "src", "test", "keystores", storeLeaf);
+        return TestRepoPath.getPath("keystores/" + storeLeaf);
     }
     
     private MqttConfig newConfig(String serverURL, String clientId) {
@@ -295,6 +295,14 @@
         completeAndValidate(clientId, top, rcvd, mgen, SEC_TIMEOUT, msgs.toArray(new String[0]));
     }
     
+    private List<String> msgsAsStr(String topic, List<String> msgs) {
+        List<String> msgsAsStr = new ArrayList<>();
+        for (String msgStr : msgs) {
+            msgsAsStr.add(new Msg(msgStr, topic).toString());
+        }
+        return msgsAsStr;
+    }
+    
     @Test
     public void testGenericPublish() throws Exception {
         Topology top = newTopology("testGenericPublish");
@@ -306,11 +314,7 @@
         // and use a different topic
         String topic = getMqttTopics()[0] + "-Generic";
         List<String> msgs = createMsgs(mgen, topic, getMsg1(), getMsg2());
-        List<String> expMsgsAsStr = 
-                msgs
-                .stream()
-                .map(t -> (new Msg(t, topic)).toString())
-                .collect(Collectors.toList());
+        List<String> expMsgsAsStr = msgsAsStr(topic, msgs);
 
         TStream<Msg> s = PlumbingStreams.blockingOneShotDelay(
                 top.collection(msgs), PUB_DELAY_MSEC, TimeUnit.MILLISECONDS)
@@ -491,17 +495,24 @@
                 top.collection(msgs), PUB_DELAY_MSEC, TimeUnit.MILLISECONDS);
         
         // Code coverage test: induce connection failure
-        //
-        // At this point the only thing we can check is an expected
-        // result of 0 msgs received.
         
         MqttConfig config = newConfig("tcp://localhost:31999", clientId);
         MqttStreams mqtt = new MqttStreams(top, () -> config);
 
         mqtt.publish(s, topic, qos, retain);
-        TStream<String> rcvd = mqtt.subscribe(topic, qos);
-
-        completeAndValidate(clientId, top, rcvd, mgen, SEC_TIMEOUT, new String[0]);
+        TStream<String> rcvd = mqtt.subscribe(topic, qos);  // rcv nothing
+        
+        // in this case there's no useful condition that we can check for
+        // to validate this is behaving properly other than the connector doesn't
+        // blow up and that nothing is rcvd, so just wait a short time
+        // before verifying nothing was rcvd.
+        // Don't use the complete() TMO for successful termination.
+        
+        Condition<List<String>> rcvdContent = top.getTester().streamContents(rcvd, new String[0]);
+        Condition<Object> tc = newWaitTimeCondition(3);
+        
+        complete(top, tc, SEC_TIMEOUT, TimeUnit.SECONDS);
+        assertTrue("rcvd: "+rcvdContent.getResult(), rcvdContent.valid());
     }
     
     private String retainTestSetup(boolean isRetained, MsgGenerator mgen) throws Exception {
@@ -550,11 +561,7 @@
         
         // verify the next connect/subscribe [doesn't] sees the retain and then new msgs
         List<String> msgs = createMsgs(mgen, topic, getMsg1(), getMsg2());
-        List<String> expMsgsAsStr = 
-                msgs
-                .stream()
-                .map(t -> (new Msg(t, topic)).toString())
-                .collect(Collectors.toList());
+        List<String> expMsgsAsStr = msgsAsStr(topic, msgs);
         if (isRetained)
             expMsgsAsStr.add(0, (new Msg(retainedMsg, topic)).toString());
 
@@ -592,11 +599,7 @@
         
         // verify the next connect/subscribe [doesn't] sees the retain and then new msgs
         List<String> msgs = createMsgs(mgen, topic, getMsg1(), getMsg2());
-        List<String> expMsgsAsStr = 
-                msgs
-                .stream()
-                .map(t -> (new Msg(t, topic)).toString())
-                .collect(Collectors.toList());
+        List<String> expMsgsAsStr = msgsAsStr(topic, msgs);
         if (isRetained)
             expMsgsAsStr.add(0, (new Msg(retainedMsg, topic)).toString());
 
@@ -796,7 +799,7 @@
 //        propTester.add("mqtt.persistence", "some.persistence.classname",
 //                () -> configRef.get().getPersistence());
         propTester.add("mqtt.serverURLs", "tcp://somehost:1234,ssl://somehost:5678",
-                () -> String.join(",", configRef.get().getServerURLs()));
+                () -> StringUtils.join(configRef.get().getServerURLs(), ","));
         propTester.add("mqtt.subscriberIdleReconnectIntervalSec", "14", 
                 () -> ((Integer)configRef.get().getSubscriberIdleReconnectInterval()).toString());
         propTester.add("mqtt.trustStore", "some/path/truststore",
diff --git a/connectors/mqtt/src/test/keystores/README b/connectors/mqtt/src/test/resources/keystores/README
similarity index 100%
rename from connectors/mqtt/src/test/keystores/README
rename to connectors/mqtt/src/test/resources/keystores/README
diff --git a/connectors/mqtt/src/test/keystores/ca.crt b/connectors/mqtt/src/test/resources/keystores/ca.crt
similarity index 100%
rename from connectors/mqtt/src/test/keystores/ca.crt
rename to connectors/mqtt/src/test/resources/keystores/ca.crt
diff --git a/connectors/mqtt/src/test/keystores/ca.key b/connectors/mqtt/src/test/resources/keystores/ca.key
similarity index 100%
rename from connectors/mqtt/src/test/keystores/ca.key
rename to connectors/mqtt/src/test/resources/keystores/ca.key
diff --git a/connectors/mqtt/src/test/keystores/ca.srl b/connectors/mqtt/src/test/resources/keystores/ca.srl
similarity index 100%
rename from connectors/mqtt/src/test/keystores/ca.srl
rename to connectors/mqtt/src/test/resources/keystores/ca.srl
diff --git a/connectors/mqtt/src/test/keystores/client.crt b/connectors/mqtt/src/test/resources/keystores/client.crt
similarity index 100%
rename from connectors/mqtt/src/test/keystores/client.crt
rename to connectors/mqtt/src/test/resources/keystores/client.crt
diff --git a/connectors/mqtt/src/test/keystores/client.csr b/connectors/mqtt/src/test/resources/keystores/client.csr
similarity index 100%
rename from connectors/mqtt/src/test/keystores/client.csr
rename to connectors/mqtt/src/test/resources/keystores/client.csr
diff --git a/connectors/mqtt/src/test/keystores/client.key b/connectors/mqtt/src/test/resources/keystores/client.key
similarity index 100%
rename from connectors/mqtt/src/test/keystores/client.key
rename to connectors/mqtt/src/test/resources/keystores/client.key
diff --git a/connectors/mqtt/src/test/keystores/client.p12 b/connectors/mqtt/src/test/resources/keystores/client.p12
similarity index 100%
rename from connectors/mqtt/src/test/keystores/client.p12
rename to connectors/mqtt/src/test/resources/keystores/client.p12
Binary files differ
diff --git a/connectors/mqtt/src/test/keystores/clientKeyStore.jks b/connectors/mqtt/src/test/resources/keystores/clientKeyStore.jks
similarity index 100%
rename from connectors/mqtt/src/test/keystores/clientKeyStore.jks
rename to connectors/mqtt/src/test/resources/keystores/clientKeyStore.jks
Binary files differ
diff --git a/connectors/mqtt/src/test/keystores/clientTrustStore.jks b/connectors/mqtt/src/test/resources/keystores/clientTrustStore.jks
similarity index 100%
rename from connectors/mqtt/src/test/keystores/clientTrustStore.jks
rename to connectors/mqtt/src/test/resources/keystores/clientTrustStore.jks
Binary files differ
diff --git a/connectors/mqtt/src/test/keystores/create-all.sh b/connectors/mqtt/src/test/resources/keystores/create-all.sh
similarity index 100%
rename from connectors/mqtt/src/test/keystores/create-all.sh
rename to connectors/mqtt/src/test/resources/keystores/create-all.sh
diff --git a/connectors/mqtt/src/test/keystores/mosquitto-SSL.conf.template b/connectors/mqtt/src/test/resources/keystores/mosquitto-SSL.conf.template
similarity index 100%
rename from connectors/mqtt/src/test/keystores/mosquitto-SSL.conf.template
rename to connectors/mqtt/src/test/resources/keystores/mosquitto-SSL.conf.template
diff --git a/connectors/mqtt/src/test/keystores/mosquitto.org.crt b/connectors/mqtt/src/test/resources/keystores/mosquitto.org.crt
similarity index 100%
rename from connectors/mqtt/src/test/keystores/mosquitto.org.crt
rename to connectors/mqtt/src/test/resources/keystores/mosquitto.org.crt
diff --git a/connectors/mqtt/src/test/keystores/server.crt b/connectors/mqtt/src/test/resources/keystores/server.crt
similarity index 100%
rename from connectors/mqtt/src/test/keystores/server.crt
rename to connectors/mqtt/src/test/resources/keystores/server.crt
diff --git a/connectors/mqtt/src/test/keystores/server.csr b/connectors/mqtt/src/test/resources/keystores/server.csr
similarity index 100%
rename from connectors/mqtt/src/test/keystores/server.csr
rename to connectors/mqtt/src/test/resources/keystores/server.csr
diff --git a/connectors/mqtt/src/test/keystores/server.key b/connectors/mqtt/src/test/resources/keystores/server.key
similarity index 100%
rename from connectors/mqtt/src/test/keystores/server.key
rename to connectors/mqtt/src/test/resources/keystores/server.key
diff --git a/connectors/pom.xml b/connectors/pom.xml
new file mode 100644
index 0000000..47b0f55
--- /dev/null
+++ b/connectors/pom.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-parent</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-connectors</artifactId>
+  <packaging>pom</packaging>
+
+  <name>Apache Edgent (Java 8): Connectors</name>
+
+  <modules>
+    <module>command</module>
+    <module>common</module>
+    <module>csv</module>
+    <module>file</module>
+    <module>http</module>
+    <module>iot</module>
+    <module>iotp</module>
+    <module>jdbc</module>
+    <module>kafka</module>
+    <module>mqtt</module>
+    <module>pubsub</module>
+    <module>serial</module>
+    <module>websocket</module>
+    <module>websocket-base</module>
+    <module>websocket-jetty</module>
+    <module>websocket-misc</module>
+    <module>websocket-server</module>
+  </modules>
+
+</project>
diff --git a/connectors/pubsub/build.gradle b/connectors/pubsub/build.gradle
deleted file mode 100644
index 6a86d26..0000000
--- a/connectors/pubsub/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:topology'
-  addTargetDirCoreExtJarDependencies 'compile'
-  
-  addTargetDirProjectJarDependency 'testCompile', ':providers:direct'
-  // N.B. root project adds test common dependencies
-}
-
-addCompileTestDependencies ':api:topology', ':providers:direct'
diff --git a/connectors/pubsub/pom.xml b/connectors/pubsub/pom.xml
new file mode 100644
index 0000000..3d3cdc1
--- /dev/null
+++ b/connectors/pubsub/pom.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-connectors</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-connectors-pubsub</artifactId>
+
+  <name>Apache Edgent (Java 8): Connectors: PubSub</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/connectors/pubsub/src/test/java/org/apache/edgent/test/connectors/pubsub/PubSubTest.java b/connectors/pubsub/src/test/java/org/apache/edgent/test/connectors/pubsub/PubSubTest.java
index 8c133e4..d60f0c3 100644
--- a/connectors/pubsub/src/test/java/org/apache/edgent/test/connectors/pubsub/PubSubTest.java
+++ b/connectors/pubsub/src/test/java/org/apache/edgent/test/connectors/pubsub/PubSubTest.java
@@ -46,7 +46,7 @@
     /**
      * Test without a pub-sub service so no
      * cross job connections will be made.
-     * @throws Exception
+     * @throws Exception on failure
      */
     @Test
     public void testNoService() throws Exception {
diff --git a/connectors/serial/build.gradle b/connectors/serial/build.gradle
deleted file mode 100644
index 6a86d26..0000000
--- a/connectors/serial/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:topology'
-  addTargetDirCoreExtJarDependencies 'compile'
-  
-  addTargetDirProjectJarDependency 'testCompile', ':providers:direct'
-  // N.B. root project adds test common dependencies
-}
-
-addCompileTestDependencies ':api:topology', ':providers:direct'
diff --git a/connectors/serial/pom.xml b/connectors/serial/pom.xml
new file mode 100644
index 0000000..28f001e
--- /dev/null
+++ b/connectors/serial/pom.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-connectors</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-connectors-serial</artifactId>
+
+  <name>Apache Edgent (Java 8): Connectors: Serial</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/connectors/websocket-base/pom.xml b/connectors/websocket-base/pom.xml
new file mode 100644
index 0000000..4da4564
--- /dev/null
+++ b/connectors/websocket-base/pom.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-connectors</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-connectors-websocket-base</artifactId>
+
+  <name>Apache Edgent (Java 8): Connectors: Websocket: Base</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/connectors/wsclient/src/main/java/org/apache/edgent/connectors/wsclient/WebSocketClient.java b/connectors/websocket-base/src/main/java/org/apache/edgent/connectors/wsclient/WebSocketClient.java
similarity index 100%
rename from connectors/wsclient/src/main/java/org/apache/edgent/connectors/wsclient/WebSocketClient.java
rename to connectors/websocket-base/src/main/java/org/apache/edgent/connectors/wsclient/WebSocketClient.java
diff --git a/connectors/wsclient/src/main/java/org/apache/edgent/connectors/wsclient/package-info.java b/connectors/websocket-base/src/main/java/org/apache/edgent/connectors/wsclient/package-info.java
similarity index 88%
rename from connectors/wsclient/src/main/java/org/apache/edgent/connectors/wsclient/package-info.java
rename to connectors/websocket-base/src/main/java/org/apache/edgent/connectors/wsclient/package-info.java
index c2cc0cb..c508759 100644
--- a/connectors/wsclient/src/main/java/org/apache/edgent/connectors/wsclient/package-info.java
+++ b/connectors/websocket-base/src/main/java/org/apache/edgent/connectors/wsclient/package-info.java
@@ -17,6 +17,6 @@
 under the License.
 */
 /**
- * WebSocket Client Connector API for sending and receiving messages to a WebSocket Server.
+ * A generic WebSocket Client Connector API for sending and receiving messages to a WebSocket Server.
  */
 package org.apache.edgent.connectors.wsclient;
diff --git a/connectors/javax.websocket-client/README b/connectors/websocket-jetty/README
similarity index 100%
rename from connectors/javax.websocket-client/README
rename to connectors/websocket-jetty/README
diff --git a/connectors/websocket-jetty/pom.xml b/connectors/websocket-jetty/pom.xml
new file mode 100644
index 0000000..1f27493
--- /dev/null
+++ b/connectors/websocket-jetty/pom.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-connectors</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-connectors-websocket-jetty</artifactId>
+
+  <name>Apache Edgent (Java 8): Connectors: Websocket: Jetty</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-connectors-websocket-misc</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>javax-websocket-client-impl</artifactId>
+      <version>9.3.6.v20151106</version>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/connectors/javax.websocket-client/src/main/java/org/apache/edgent/javax/websocket/impl/EdgentSslContainerProviderImpl.java b/connectors/websocket-jetty/src/main/java/org/apache/edgent/javax/websocket/impl/EdgentSslContainerProviderImpl.java
similarity index 100%
rename from connectors/javax.websocket-client/src/main/java/org/apache/edgent/javax/websocket/impl/EdgentSslContainerProviderImpl.java
rename to connectors/websocket-jetty/src/main/java/org/apache/edgent/javax/websocket/impl/EdgentSslContainerProviderImpl.java
diff --git a/connectors/javax.websocket-client/src/main/java/org/apache/edgent/javax/websocket/impl/package-info.java b/connectors/websocket-jetty/src/main/java/org/apache/edgent/javax/websocket/impl/package-info.java
similarity index 100%
rename from connectors/javax.websocket-client/src/main/java/org/apache/edgent/javax/websocket/impl/package-info.java
rename to connectors/websocket-jetty/src/main/java/org/apache/edgent/javax/websocket/impl/package-info.java
diff --git a/connectors/websocket-jetty/src/main/resources/META-INF/services/org.apache.edgent.javax.websocket.EdgentSslContainerProvider b/connectors/websocket-jetty/src/main/resources/META-INF/services/org.apache.edgent.javax.websocket.EdgentSslContainerProvider
new file mode 100644
index 0000000..8c3cec2
--- /dev/null
+++ b/connectors/websocket-jetty/src/main/resources/META-INF/services/org.apache.edgent.javax.websocket.EdgentSslContainerProvider
@@ -0,0 +1,17 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+org.apache.edgent.javax.websocket.impl.EdgentSslContainerProviderImpl
\ No newline at end of file
diff --git a/connectors/websocket-misc/pom.xml b/connectors/websocket-misc/pom.xml
new file mode 100644
index 0000000..47a2073
--- /dev/null
+++ b/connectors/websocket-misc/pom.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-connectors</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-connectors-websocket-misc</artifactId>
+
+  <name>Apache Edgent (Java 8): Connectors: Websocket: Misc</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>javax.websocket</groupId>
+      <artifactId>javax.websocket-api</artifactId>
+      <version>1.0</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/connectors/edgent.javax.websocket/src/main/java/org/apache/edgent/javax/websocket/EdgentSslContainerProvider.java b/connectors/websocket-misc/src/main/java/org/apache/edgent/javax/websocket/EdgentSslContainerProvider.java
similarity index 100%
rename from connectors/edgent.javax.websocket/src/main/java/org/apache/edgent/javax/websocket/EdgentSslContainerProvider.java
rename to connectors/websocket-misc/src/main/java/org/apache/edgent/javax/websocket/EdgentSslContainerProvider.java
diff --git a/connectors/edgent.javax.websocket/src/main/java/org/apache/edgent/javax/websocket/package-info.java b/connectors/websocket-misc/src/main/java/org/apache/edgent/javax/websocket/package-info.java
similarity index 100%
rename from connectors/edgent.javax.websocket/src/main/java/org/apache/edgent/javax/websocket/package-info.java
rename to connectors/websocket-misc/src/main/java/org/apache/edgent/javax/websocket/package-info.java
diff --git a/connectors/javax.websocket-server/README b/connectors/websocket-server/README
similarity index 100%
rename from connectors/javax.websocket-server/README
rename to connectors/websocket-server/README
diff --git a/connectors/websocket-server/pom.xml b/connectors/websocket-server/pom.xml
new file mode 100644
index 0000000..20a7c83
--- /dev/null
+++ b/connectors/websocket-server/pom.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-connectors</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-connectors-websocket-server</artifactId>
+
+  <name>Apache Edgent (Java 8): Connectors: Websocket: Server</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-connectors-websocket-jetty</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>javax-websocket-server-impl</artifactId>
+      <version>9.3.6.v20151106</version>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/connectors/websocket/pom.xml b/connectors/websocket/pom.xml
new file mode 100644
index 0000000..f49c8dc
--- /dev/null
+++ b/connectors/websocket/pom.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-connectors</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-connectors-websocket</artifactId>
+
+  <name>Apache Edgent (Java 8): Connectors: Websocket</name>
+
+  <properties>
+    <remote-resources-maven-plugin.remote-resources.dir>../../src/main/ibm-remote-resources</remote-resources-maven-plugin.remote-resources.dir>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-connectors-common</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-connectors-websocket-misc</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-connectors-websocket-base</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>javax.websocket</groupId>
+      <artifactId>javax.websocket-api</artifactId>
+      <version>1.0</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-http</artifactId>
+      <version>${jetty.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-server</artifactId>
+      <version>${jetty.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-servlet</artifactId>
+      <version>${jetty.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>javax-websocket-server-impl</artifactId>
+      <version>${jetty.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-connectors-common</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-providers-direct</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-api-topology</artifactId>
+      <version>1.2.0</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-connectors-websocket-jetty</artifactId>
+      <version>1.2.0</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/connectors/wsclient-javax.websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/Jsr356WebSocketClient.java b/connectors/websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/Jsr356WebSocketClient.java
similarity index 100%
rename from connectors/wsclient-javax.websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/Jsr356WebSocketClient.java
rename to connectors/websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/Jsr356WebSocketClient.java
diff --git a/connectors/wsclient-javax.websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/package-info.java b/connectors/websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/package-info.java
similarity index 88%
rename from connectors/wsclient-javax.websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/package-info.java
rename to connectors/websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/package-info.java
index 277ce5c..d590d14 100644
--- a/connectors/wsclient-javax.websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/package-info.java
+++ b/connectors/websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/package-info.java
@@ -17,6 +17,6 @@
 under the License.
 */
 /**
- * WebSocket Client Connector for sending and receiving messages to a WebSocket Server.
+ * A JSR356 based WebSocket Client Connector for sending and receiving messages to a WebSocket Server.
  */
 package org.apache.edgent.connectors.wsclient.javax.websocket;
diff --git a/connectors/wsclient-javax.websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/runtime/WebSocketClientBinaryReceiver.java b/connectors/websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/runtime/WebSocketClientBinaryReceiver.java
similarity index 100%
rename from connectors/wsclient-javax.websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/runtime/WebSocketClientBinaryReceiver.java
rename to connectors/websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/runtime/WebSocketClientBinaryReceiver.java
diff --git a/connectors/wsclient-javax.websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/runtime/WebSocketClientBinarySender.java b/connectors/websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/runtime/WebSocketClientBinarySender.java
similarity index 100%
rename from connectors/wsclient-javax.websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/runtime/WebSocketClientBinarySender.java
rename to connectors/websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/runtime/WebSocketClientBinarySender.java
diff --git a/connectors/wsclient-javax.websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/runtime/WebSocketClientConnector.java b/connectors/websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/runtime/WebSocketClientConnector.java
similarity index 100%
rename from connectors/wsclient-javax.websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/runtime/WebSocketClientConnector.java
rename to connectors/websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/runtime/WebSocketClientConnector.java
diff --git a/connectors/wsclient-javax.websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/runtime/WebSocketClientReceiver.java b/connectors/websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/runtime/WebSocketClientReceiver.java
similarity index 100%
rename from connectors/wsclient-javax.websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/runtime/WebSocketClientReceiver.java
rename to connectors/websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/runtime/WebSocketClientReceiver.java
diff --git a/connectors/wsclient-javax.websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/runtime/WebSocketClientSender.java b/connectors/websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/runtime/WebSocketClientSender.java
similarity index 100%
rename from connectors/wsclient-javax.websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/runtime/WebSocketClientSender.java
rename to connectors/websocket/src/main/java/org/apache/edgent/connectors/wsclient/javax/websocket/runtime/WebSocketClientSender.java
diff --git a/connectors/websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketClientConnectTestHelper.java b/connectors/websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketClientConnectTestHelper.java
new file mode 100644
index 0000000..41878f7
--- /dev/null
+++ b/connectors/websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketClientConnectTestHelper.java
@@ -0,0 +1,64 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+package org.apache.edgent.test.connectors.wsclient.javax.websocket;
+
+import java.net.URI;
+import java.util.Properties;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ContainerProvider;
+import javax.websocket.OnError;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.util.component.LifeCycle;
+
+@ClientEndpoint 
+public class WebSocketClientConnectTestHelper {
+  
+  @OnError
+  public void onError(Session client, Throwable t) {
+    System.err.println("Unable to connect to WebSocket server: "+t.getMessage());
+  }
+
+  public static void connectToServer(Properties config) throws Exception {
+    // Verify we can create a real websocket connection to the server.
+    //
+    // We do the following instead of a simple socket connect
+    // because in at least one location, the websocket connect/upgrade
+    // fails with: expecting 101 got 403 (Forbidden).
+    // There's something about that location that's not
+    // allowing a websocket to be created to the (public) server.
+    // Everything works fine from other locations.
+    //
+    String wsUri = config.getProperty("ws.uri");
+    URI uri = new URI(wsUri);
+    WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+    try {
+      Session session = container.connectToServer(WebSocketClientConnectTestHelper.class,  uri);
+      session.close();
+    }
+    finally {
+      if (container instanceof LifeCycle) {
+        ((LifeCycle)container).stop();
+      }
+    }
+  }
+
+}
diff --git a/connectors/wsclient-javax.websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketClientGlobalTest.java b/connectors/websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketClientGlobalTest.java
similarity index 95%
rename from connectors/wsclient-javax.websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketClientGlobalTest.java
rename to connectors/websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketClientGlobalTest.java
index 5bbed78..9651b19 100644
--- a/connectors/wsclient-javax.websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketClientGlobalTest.java
+++ b/connectors/websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketClientGlobalTest.java
@@ -21,6 +21,7 @@
 /**
  * WebSocketClient connector globalization tests.
  */
+//@Ignore("There seem to be issues with SSL related tests")
 public class WebSocketClientGlobalTest extends WebSocketClientTest {
     private final static String globalStr1 = "一";
     private final static String globalStr2 = "二";
diff --git a/connectors/wsclient-javax.websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketClientTest.java b/connectors/websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketClientTest.java
similarity index 89%
rename from connectors/wsclient-javax.websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketClientTest.java
rename to connectors/websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketClientTest.java
index 0a85cfb..b256bfe 100644
--- a/connectors/wsclient-javax.websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketClientTest.java
+++ b/connectors/websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketClientTest.java
@@ -20,13 +20,13 @@
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
-import java.net.InetSocketAddress;
-import java.net.Socket;
 import java.net.URI;
 import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.concurrent.CountDownLatch;
@@ -42,11 +42,13 @@
 import org.apache.edgent.topology.Topology;
 import org.apache.edgent.topology.json.JsonFunctions;
 import org.apache.edgent.topology.plumbing.PlumbingStreams;
+import org.apache.edgent.topology.tester.Condition;
 import org.junit.After;
 import org.junit.Test;
 
 import com.google.gson.JsonObject;
 
+//@Ignore("There seem to be issues with SSL related tests")
 public class WebSocketClientTest extends ConnectorTestBase {
     private final static int SEC_TMO = 5;
     WebSocketServerEcho wsServer;
@@ -142,7 +144,7 @@
     }
     
     private String getStorePath(String storeLeaf) {
-        return TestRepoPath.getPath("connectors", "wsclient-javax.websocket", "src", "test", "keystores", storeLeaf);
+        return TestRepoPath.getPath("keystores/" + storeLeaf);
     }
 
     @Test
@@ -595,9 +597,19 @@
         s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
         wsClient.sendString(s);
         
-        TStream<String> rcvd = wsClient.receiveString();
+        TStream<String> rcvd = wsClient.receiveString();  // rcv nothing
         
-        completeAndValidate("", t, rcvd, SEC_TMO, new String[0]);  //rcv nothing
+        // in this case there's no useful condition that we can check for
+        // to validate this is behaving properly other than the connector doesn't
+        // blow up and that nothing is rcvd, so just wait a short time
+        // before verifying nothing was rcvd.
+        // Don't use the complete() TMO for successful termination.
+        
+        Condition<List<String>> rcvdContent = t.getTester().streamContents(rcvd, new String[0]);
+        Condition<Object> tc = newWaitTimeCondition(3);
+        
+        complete(t, tc, SEC_TMO, TimeUnit.SECONDS);
+        assertTrue("rcvd: "+rcvdContent.getResult(), rcvdContent.valid());
     }
     
     @Test
@@ -674,9 +686,19 @@
         s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
         wsClient.sendString(s);
         
-        TStream<String> rcvd = wsClient.receiveString();
+        TStream<String> rcvd = wsClient.receiveString();  // rcv nothing
         
-        completeAndValidate("", t, rcvd, SEC_TMO, new String[0]); // rcv nothing
+        // in this case there's no useful condition that we can check for
+        // to validate this is behaving properly other than the connector doesn't
+        // blow up and that nothing is rcvd, so just wait a short time
+        // before verifying nothing was rcvd.
+        // Don't use the complete() TMO for successful termination.
+        
+        Condition<List<String>> rcvdContent = t.getTester().streamContents(rcvd, new String[0]);
+        Condition<Object> tc = newWaitTimeCondition(3);
+        
+        complete(t, tc, SEC_TMO, TimeUnit.SECONDS);
+        assertTrue("rcvd: "+rcvdContent.getResult(), rcvdContent.valid());
     }
     
     @Test
@@ -732,27 +754,31 @@
         s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
         wsClient.sendString(s);
         
-        TStream<String> rcvd = wsClient.receiveString();
+        TStream<String> rcvd = wsClient.receiveString();  // rcv nothing
         
-        completeAndValidate("", t, rcvd, SEC_TMO, new String[0]);  //rcv nothing
+        // in this case there's no useful condition that we can check for
+        // to validate this is behaving properly other than the connector doesn't
+        // blow up and that nothing is rcvd, so just wait a short time
+        // before verifying nothing was rcvd.
+        // Don't use the complete() TMO for successful termination.
+        
+        Condition<List<String>> rcvdContent = t.getTester().streamContents(rcvd, new String[0]);
+        Condition<Object> tc = newWaitTimeCondition(3);
+        
+        complete(t, tc, SEC_TMO, TimeUnit.SECONDS);
+        assertTrue("rcvd: "+rcvdContent.getResult(), rcvdContent.valid());
     }
     
     private void skipTestIfCantConnect(Properties config) throws Exception {
-        String wsUri = config.getProperty("ws.uri");
-        // Skip tests if the WebSocket server can't be contacted.
-        try {
-            URI uri = new URI(wsUri);
-            int port = uri.getPort();
-            if (port == -1)
-                port = uri.getScheme().equals("ws") ? 80 : 443;
-            Socket s = new Socket();
-            s.connect(new InetSocketAddress(uri.getHost(), port), 5*1000/*cn-timeout-msec*/);
-            s.close();
-        } catch (Exception e) {
-            System.err.println("Unable to connect to WebSocket server "+wsUri+" : "+e.getMessage());
-            e.printStackTrace();
-            assumeTrue(false);
-        }
+      String wsUri = config.getProperty("ws.uri");
+      try {
+          WebSocketClientConnectTestHelper.connectToServer(config);
+      } catch (Exception e) {
+          System.err.println("Unable to connect to WebSocket server "+wsUri+" : "+e.getMessage());
+          e.printStackTrace();
+          System.err.println("skipTestIfCantConnect(): SKIPPING TEST");
+          assumeTrue(false);
+      }
     }
     
     @Test
@@ -846,9 +872,19 @@
             s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
             wsClient.sendString(s);
             
-            TStream<String> rcvd = wsClient.receiveString();
+            TStream<String> rcvd = wsClient.receiveString();  // rcv nothing
             
-            completeAndValidate("", t, rcvd, SEC_TMO, new String[0]);  //rcv nothing
+            // in this case there's no useful condition that we can check for
+            // to validate this is behaving properly other than the connector doesn't
+            // blow up and that nothing is rcvd, so just wait a short time
+            // before verifying nothing was rcvd.
+            // Don't use the complete() TMO for successful termination.
+            
+            Condition<List<String>> rcvdContent = t.getTester().streamContents(rcvd, new String[0]);
+            Condition<Object> tc = newWaitTimeCondition(3);
+            
+            complete(t, tc, SEC_TMO, TimeUnit.SECONDS);
+            assertTrue("rcvd: "+rcvdContent.getResult(), rcvdContent.valid());
         }
         finally {
             sslProps.restore();
diff --git a/connectors/wsclient-javax.websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketServerEcho.java b/connectors/websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketServerEcho.java
similarity index 98%
rename from connectors/wsclient-javax.websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketServerEcho.java
rename to connectors/websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketServerEcho.java
index d6c9bde..9e0651e 100644
--- a/connectors/wsclient-javax.websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketServerEcho.java
+++ b/connectors/websocket/src/test/java/org/apache/edgent/test/connectors/wsclient/javax/websocket/WebSocketServerEcho.java
@@ -144,7 +144,7 @@
     }
     
     private String getStorePath(String storeLeaf) {
-        return TestRepoPath.getPath("connectors", "wsclient-javax.websocket", "src", "test", "keystores", storeLeaf);
+        return TestRepoPath.getPath("keystores/" + storeLeaf);
     }
     
     public int getPort() {
diff --git a/connectors/wsclient-javax.websocket/src/test/keystores/README b/connectors/websocket/src/test/resources/keystores/README
similarity index 79%
rename from connectors/wsclient-javax.websocket/src/test/keystores/README
rename to connectors/websocket/src/test/resources/keystores/README
index 4419c23..466d40f 100644
--- a/connectors/wsclient-javax.websocket/src/test/keystores/README
+++ b/connectors/websocket/src/test/resources/keystores/README
@@ -1,5 +1,22 @@
 #!/bin/sh
 
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+
 # The test's key and trust stores are created as follows.
 # They have the password "passw0rd".
 
diff --git a/connectors/wsclient-javax.websocket/src/test/keystores/clientKeyStore.jks b/connectors/websocket/src/test/resources/keystores/clientKeyStore.jks
similarity index 100%
rename from connectors/wsclient-javax.websocket/src/test/keystores/clientKeyStore.jks
rename to connectors/websocket/src/test/resources/keystores/clientKeyStore.jks
Binary files differ
diff --git a/connectors/wsclient-javax.websocket/src/test/keystores/clientTrustStore.jks b/connectors/websocket/src/test/resources/keystores/clientTrustStore.jks
similarity index 100%
rename from connectors/wsclient-javax.websocket/src/test/keystores/clientTrustStore.jks
rename to connectors/websocket/src/test/resources/keystores/clientTrustStore.jks
Binary files differ
diff --git a/connectors/wsclient-javax.websocket/src/test/keystores/serverKeyStore.jks b/connectors/websocket/src/test/resources/keystores/serverKeyStore.jks
similarity index 100%
rename from connectors/wsclient-javax.websocket/src/test/keystores/serverKeyStore.jks
rename to connectors/websocket/src/test/resources/keystores/serverKeyStore.jks
Binary files differ
diff --git a/connectors/wsclient-javax.websocket/src/test/keystores/serverTrustStore.jks b/connectors/websocket/src/test/resources/keystores/serverTrustStore.jks
similarity index 100%
rename from connectors/wsclient-javax.websocket/src/test/keystores/serverTrustStore.jks
rename to connectors/websocket/src/test/resources/keystores/serverTrustStore.jks
Binary files differ
diff --git a/connectors/wsclient-javax.websocket/build.gradle b/connectors/wsclient-javax.websocket/build.gradle
deleted file mode 100644
index e0c84dc..0000000
--- a/connectors/wsclient-javax.websocket/build.gradle
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:topology'
-  addTargetDirProjectJarDependency 'compile', ':connectors:common'
-  addTargetDirProjectJarDependency 'compile', ':connectors:wsclient'
-  addTargetDirProjectJarDependency 'compile', ':connectors:edgent.javax.websocket'
-  
-  addTargetDirProjectJarDependency 'testCompile', ':providers:direct'
-  addTargetDirProjectJarDependency 'testCompile', ':connectors:javax.websocket-client'
-  addTargetDirProjectJarDependency 'testCompile', ':connectors:javax.websocket-server'
-  
-  // N.B. root project adds test common dependencies
-}
-
-addCompileTestDependencies ':api:topology', ':providers:direct', ':connectors:common'
diff --git a/connectors/wsclient/build.gradle b/connectors/wsclient/build.gradle
deleted file mode 100644
index 6a86d26..0000000
--- a/connectors/wsclient/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addTargetDirProjectJarDependency 'compile', ':api:topology'
-  addTargetDirCoreExtJarDependencies 'compile'
-  
-  addTargetDirProjectJarDependency 'testCompile', ':providers:direct'
-  // N.B. root project adds test common dependencies
-}
-
-addCompileTestDependencies ':api:topology', ':providers:direct'
diff --git a/console/.classpath b/console/.classpath
deleted file mode 100644
index a2a5a9c..0000000
--- a/console/.classpath
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" path="server/src/main/java"/>
-	<classpathentry kind="src" path="server/src/test/java"/>
-	<classpathentry kind="src" path="servlets/src/main/java"/>
-	<classpathentry kind="lib" path="../externalJars/java8/console/server/ext/jetty-http-9.3.6.v20151106.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/console/server/ext/jetty-io-9.3.6.v20151106.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/console/server/ext/jetty-security-9.3.6.v20151106.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/console/server/ext/jetty-server-9.3.6.v20151106.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/console/server/ext/jetty-servlet-9.3.6.v20151106.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/console/server/ext/jetty-util-9.3.6.v20151106.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/console/server/ext/jetty-webapp-9.3.6.v20151106.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/console/server/ext/jetty-xml-9.3.6.v20151106.jar"/>
-	<classpathentry kind="lib" path="../externalJars/java8/console/server/ext/javax.servlet-api-3.1.0.jar"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/ext"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/utils"/>
-	<classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/console/.gitignore b/console/.gitignore
deleted file mode 100644
index 948fe44..0000000
--- a/console/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-/classes/
-**/test.classes/
-**/classes/
-**/unittests/
-/bin/
diff --git a/console/.project b/console/.project
deleted file mode 100644
index d1df45d..0000000
--- a/console/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>console</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-	</natures>
-</projectDescription>
diff --git a/console/pom.xml b/console/pom.xml
new file mode 100644
index 0000000..4256fbc
--- /dev/null
+++ b/console/pom.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-parent</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-console</artifactId>
+  <packaging>pom</packaging>
+
+  <name>Apache Edgent (Java 8): Console</name>
+
+  <modules>
+    <module>server</module>
+    <module>servlets</module>
+  </modules>
+
+</project>
diff --git a/console/server/build.gradle b/console/server/build.gradle
deleted file mode 100644
index 1febc14..0000000
--- a/console/server/build.gradle
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-dependencies {
-  addProjectExtDependency 'compile', 'org.eclipse.jetty:jetty-http:9.3.6.v20151106'
-  addProjectExtDependency 'compile', 'org.eclipse.jetty:jetty-io:9.3.6.v20151106'
-  addProjectExtDependency 'compile', 'org.eclipse.jetty:jetty-security:9.3.6.v20151106'
-  addProjectExtDependency 'compile', 'org.eclipse.jetty:jetty-server:9.3.6.v20151106'
-  addProjectExtDependency 'compile', 'org.eclipse.jetty:jetty-servlet:9.3.6.v20151106'
-  addProjectExtDependency 'compile', 'org.eclipse.jetty:jetty-util:9.3.6.v20151106'
-  addProjectExtDependency 'compile', 'org.eclipse.jetty:jetty-webapp:9.3.6.v20151106'
-  addProjectExtDependency 'compile', 'org.eclipse.jetty:jetty-xml:9.3.6.v20151106'
-  addTargetDirCoreExtJarDependencies 'compile'
-  
-  // TODO runtime dependsOn ":console:servlets"  ???  
-
-  // N.B. root project adds test common dependencies
-}
diff --git a/console/server/pom.xml b/console/server/pom.xml
new file mode 100644
index 0000000..8355265
--- /dev/null
+++ b/console/server/pom.xml
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-console</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-console-server</artifactId>
+
+  <name>Apache Edgent (Java 8): Console: Server</name>
+
+  <build>
+    <resources>
+      <resource>
+        <directory>${project.basedir}/src/main/resources</directory>
+        <targetPath>${project.build.outputDirectory}/</targetPath>
+      </resource>
+      <resource>
+        <!--  bundle the licenses of the artifacts we are bundling -->
+        <directory>${project.basedir}/../../src/main/appended-resources/licenses</directory>
+        <targetPath>${project.build.outputDirectory}/META-INF/licenses</targetPath>
+      </resource>
+    </resources>
+    <plugins>
+      <!--
+        Copy the servlets.war into the build output so it is embedded as
+        resource into the jar.
+      -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>copy</id>
+            <phase>generate-resources</phase>
+            <goals>
+              <goal>copy</goal>
+            </goals>
+            <configuration>
+               <!--  Bundle these dependencies in the jar.
+                    
+                  You must maintain the corresponding info if you
+                  add/remove artifacts or change their versions:
+                    - entry in src/main/resources/META-INF/LICENSE
+                    - entry in src/main/resources/META-INF/NOTICE when appropriate                    
+ 
+                  See console/servlets and its pom,LICENSE,NOTICE.
+                  
+                  You must maintain the info in this project's
+                  corresponding platforms/java7 files.
+               -->
+              <artifactItems>
+                <artifactItem>
+                  <groupId>org.apache.edgent</groupId>
+                  <artifactId>edgent-console-servlets</artifactId>
+                  <version>${project.version}</version>
+                  <type>war</type>
+                  <outputDirectory>${project.build.outputDirectory}/resources</outputDirectory>
+                  <destFileName>servlets.war</destFileName>
+                </artifactItem>
+              </artifactItems>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+   </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-http</artifactId>
+      <version>${jetty.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-io</artifactId>
+      <version>${jetty.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-security</artifactId>
+      <version>${jetty.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-server</artifactId>
+      <version>${jetty.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-servlet</artifactId>
+      <version>${jetty.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-util</artifactId>
+      <version>${jetty.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-webapp</artifactId>
+      <version>${jetty.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-xml</artifactId>
+      <version>${jetty.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+      <version>1.7.12</version>
+    </dependency>
+
+    <!--
+        This artifact is needed by the maven-dependency-plugin
+        by marking this dependency optional, it is not included
+        in the resulting artifact, but Maven ensures it is built
+        prior to this module.
+    -->
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-console-servlets</artifactId>
+      <version>1.2.0</version>
+      <type>war</type>
+      <optional>true</optional>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/console/server/src/main/java/org/apache/edgent/console/server/HttpServer.java b/console/server/src/main/java/org/apache/edgent/console/server/HttpServer.java
index 5844aeb..302200d 100644
--- a/console/server/src/main/java/org/apache/edgent/console/server/HttpServer.java
+++ b/console/server/src/main/java/org/apache/edgent/console/server/HttpServer.java
@@ -19,8 +19,6 @@
 
 package org.apache.edgent.console.server;
 
-import java.security.ProtectionDomain;
-
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
@@ -29,6 +27,8 @@
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.webapp.WebAppContext;
 
+import java.io.File;
+
 public class HttpServer {
 
 	/**
@@ -46,8 +46,6 @@
         private static final WebAppContext WEBAPP = new WebAppContext();
         private static final HttpServer INSTANCE = new HttpServer();
         private static boolean INITIALIZED = false;
-        private static final String consoleWarNotFoundMessage =  
-    			"console.war not found.  Run 'ant' from the top level edgent directory, or 'ant' from 'console/servlets' to create console.war under the webapps directory.";
     }
 
     /**
@@ -71,24 +69,14 @@
             ServletContextHandler contextMetrics = new ServletContextHandler(ServletContextHandler.SESSIONS);
             contextMetrics.setContextPath("/metrics");
             ServerUtil sUtil = new ServerUtil();
-            String commandWarFilePath = sUtil.getAbsoluteWarFilePath("console.war");
-            if (commandWarFilePath.equals("")){
-            	// check if we are on Eclipse, if Eclipse can't find it, it probably does not exist
-            	// running on Eclipse, look for the eclipse war file path
-            	ProtectionDomain protectionDomain = HttpServer.class.getProtectionDomain();
-            	String eclipseWarFilePath = sUtil.getEclipseWarFilePath(protectionDomain, "console.war");
-            	if (!eclipseWarFilePath.equals("")) {            	
-            		HttpServerHolder.WEBAPP.setWar(eclipseWarFilePath);
-            	} else {
-            		throw new Exception(HttpServerHolder.consoleWarNotFoundMessage);
-            	}
+            File servletsJarFile = sUtil.getWarFilePath();
+            if (servletsJarFile.exists()){
+            	HttpServerHolder.WEBAPP.setWar(servletsJarFile.getAbsolutePath());
             } else {
-            	HttpServerHolder.WEBAPP.setWar(commandWarFilePath);
+                throw new RuntimeException("Unable to find WAR archive in " + servletsJarFile.getAbsolutePath());
             }
 
-
-            
-            HttpServerHolder.WEBAPP.addAliasCheck(new AllowSymLinkAliasChecker()); 
+            HttpServerHolder.WEBAPP.addAliasCheck(new AllowSymLinkAliasChecker());
             ContextHandlerCollection contexts = new ContextHandlerCollection();
             contexts.setHandlers(new Handler[] { contextJobs, contextMetrics, HttpServerHolder.WEBAPP });
             HttpServerHolder.JETTYSERVER.setHandler(contexts);
diff --git a/console/server/src/main/java/org/apache/edgent/console/server/ServerUtil.java b/console/server/src/main/java/org/apache/edgent/console/server/ServerUtil.java
index 5002d5d..f8c6fde 100644
--- a/console/server/src/main/java/org/apache/edgent/console/server/ServerUtil.java
+++ b/console/server/src/main/java/org/apache/edgent/console/server/ServerUtil.java
@@ -19,17 +19,7 @@
 
 package org.apache.edgent.console.server;
 
-import java.io.IOException;
-import java.io.File;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Path;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.security.ProtectionDomain;
-import java.util.ArrayList;
-import java.util.List;
+import java.io.*;
 
 public class ServerUtil {
 
@@ -37,90 +27,58 @@
 	 *  The public constructor of this utility class for use by the HttpServer class.
 	 */
     public ServerUtil() {
-
-    }
-    /**
-     * Returns the path to the jar file for this package
-     * @return a String representing the path to the jar file of this package
-     */
-    private String getPath() {
-        return getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
-    }
-
-    /**
-     * Returns a file object representing the parent's parent directory of the jar file.
-     * @return a File object
-     */
-    private File getTopDirFilePath() {
-        String topDirProp = System.getProperty("edgent.test.top.dir.file.path");
-        if (topDirProp != null) {
-          return new File(topDirProp);
-        }
-        File jarFile = new File(getPath());
-        return jarFile.getParentFile().getParentFile().getParentFile();
     }
 
     /**
      * Returns the File object representing the "webapps" directory
      * @return a File object or null if the "webapps" directory is not found
      */
-    private File getWarFilePath() {
-        List<File> foundFiles = new ArrayList<>();
-        try {
-            Files.walkFileTree(getTopDirFilePath().toPath(), new SimpleFileVisitor<Path>() {
-                @Override
-                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
-                    if (dir.endsWith("webapps")) {
-                      foundFiles.add(dir.toFile());
-                    }
-                    return FileVisitResult.CONTINUE;
+    public File getWarFilePath() {
+        File war = new File("target/war-resources/servlets.war");
+        if(!war.exists()) {
+            // Eventually create the directory for serving the war.
+            if(!war.getParentFile().exists()) {
+                if(!war.getParentFile().mkdirs()) {
+                    throw new RuntimeException("Could not create output directory at " + war.getAbsolutePath());
                 }
-            });
-        } catch (IOException e) {
-          // end of file searching
+            }
+
+            // Dump the servlet.war into the output directory.
+            InputStream stream = null;
+            FileOutputStream fileOutputStream = null;
+            try {
+                stream = ServerUtil.class.getResourceAsStream("/resources/servlets.war");
+                if(stream == null) {
+                    throw new RuntimeException("Could not get resource '/resources/servlets.war' from classpath.");
+                }
+
+                int readBytes;
+                byte[] buffer = new byte[4096];
+                fileOutputStream = new FileOutputStream(war);
+                while ((readBytes = stream.read(buffer)) > 0) {
+                    fileOutputStream.write(buffer, 0, readBytes);
+                }
+            } catch (IOException e) {
+                throw new RuntimeException("Could not dump resource 'resources/servlets.war' from classpath.", e);
+            } finally {
+                if(stream != null) {
+                    try {
+                        stream.close();
+                    } catch (IOException e) {
+                        // Ignore.
+                    }
+                }
+                if(fileOutputStream != null) {
+                    try {
+                        fileOutputStream.close();
+                    } catch (IOException e) {
+                        // Ignore.
+                    }
+                }
+            }
         }
-        if (foundFiles.size() != 0) {
-            return foundFiles.get(0);
-        }
-        return null;
-    }
-    
-    /**
-     * Looks for the absolute file path of the name of the warFileName argument
-     * @param warFileName the name of the war file to find the absolute path to
-     * @return the absolute path to the warFileName argument as a String
-     */
-    public String getAbsoluteWarFilePath(String warFileName) {
-        File warFilePath = getWarFilePath();
-        if (warFilePath != null) {
-        	File warFile = new File(warFilePath.getAbsolutePath() + "/" + warFileName);
-        	if (warFile.exists()) {        	
-        		return warFile.getAbsolutePath();
-        	} else {
-        		return "";
-        	}
-        }
-        else {
-            return "";
-        }
-    }
-    
-    /**
-     * Looks for the absolute file path of the name of the warFileName argument when running from Eclipse
-     * @param pDomain the ProtectionDomain to use to get the source's location
-     * @param warFileName the name of the war file to find the absolute path to
-     * @return the absolute path to the warFileName argument as a String
-     */
-    public String getEclipseWarFilePath(ProtectionDomain pDomain, String warFileName) {
-        URL location = pDomain.getCodeSource().getLocation();
-        File topEdgent = new File(location.getPath()).getParentFile().getParentFile();
-        File warFile = new File(topEdgent, "./target/java8/console/webapps/" +warFileName);
-        if (warFile.exists()) {
-        	return warFile.getAbsolutePath();
-        } else {
-        	return "";
-        }
-	
+
+        return war;
     }
 
 }
diff --git a/console/server/src/main/remote-resources/META-INF/LICENSE b/console/server/src/main/remote-resources/META-INF/LICENSE
new file mode 100644
index 0000000..8d0e1a0
--- /dev/null
+++ b/console/server/src/main/remote-resources/META-INF/LICENSE
@@ -0,0 +1,42 @@
+
+===============================================================================
+APACHE EDGENT SUBCOMPONENTS:
+
+This binary includes a number of subcomponents with separate
+copyright notices and license terms.  Your use of this binary
+is subject to the terms and conditions of the following licenses.
+
+===============================================================================
+License: Apache License Version 2.0
+For details, see META-INF/licenses/apache-license-version-2.0.txt
+
+gson (com.google.code.gson:gson:2.2.4)
+metrics-core (io.dropwizard.metrics:metrics-core:3.1.2)
+
+===============================================================================
+License: MIT
+
+jquery (org.webjars:jquery:1.11.2)
+    For details, see META-INF/licenses/jquery-1_11_2-MIT.txt.
+
+jquery-ui (org.webjars:jquery-ui:1.11.4)
+    For details, see META-INF/licenses/jquery-ui-1_11_4-MIT.txt.
+
+d3.legend.js (included inside the resources/servlets.war!/js/ext/d3.legend/d3.legend.js)
+    https://gist.githubusercontent.com/ZJONSSON/3918369/raw/bf9bce6b68a3b70f87450f155436ca4a84af1ba4/d3.legend.js
+    With Edgent specific modifications.
+    For details, see META-INF/licenses/d3.legend-MIT.txt.
+
+===============================================================================
+License: BSD 3-Clause
+
+d3 (org.webjars.bower:d3:3.3.9)
+    For details, see META-INF/licenses/d3-3_3_9-BSD.txt.
+
+===============================================================================
+License: BSD 2-Clause
+
+d3-plugins-sankey (org.webjars.bower:d3-plugins-sankey:1.1.0)
+    For details, see META-INF/licenses/d3-plugins-sankey-1_1_0-BSD.txt.
+
+===============================================================================
diff --git a/console/server/src/main/remote-resources/META-INF/NOTICE b/console/server/src/main/remote-resources/META-INF/NOTICE
new file mode 100644
index 0000000..9c9e7e9
--- /dev/null
+++ b/console/server/src/main/remote-resources/META-INF/NOTICE
@@ -0,0 +1,28 @@
+===============================================================================
+
+Portions of this software were developed by IBM Corp.
+Copyright IBM Corp. 2015, 2016
+
+===============================================================================
+
+APACHE EDGENT SUBCOMPONENTS:
+
+This product includes a number of subcomponents with separate
+copyright notices and license terms.  The following notices apply.
+
+-------------------------------------------------------------------------------
+metrics-core (io.dropwizard.metrics:metrics-core:3.1.2)
+
+Metrics
+Copyright 2010-2013 Coda Hale and Yammer, Inc.
+
+This product includes software developed by Coda Hale and Yammer, Inc.
+
+This product includes code derived from the JSR-166 project (ThreadLocalRandom,
+Striped64, LongAdder) with the following comments:
+
+          Written by Doug Lea with assistance from members of JCP JSR-166
+          Expert Group and released to the public domain, as explained at
+          http://creativecommons.org/publicdomain/zero/1.0/
+
+===============================================================================
diff --git a/console/server/src/test/java/org/apache/edgent/test/console/server/HttpServerTest.java b/console/server/src/test/java/org/apache/edgent/test/console/server/HttpServerTest.java
index 3ecc510..46bd511 100644
--- a/console/server/src/test/java/org/apache/edgent/test/console/server/HttpServerTest.java
+++ b/console/server/src/test/java/org/apache/edgent/test/console/server/HttpServerTest.java
@@ -46,7 +46,7 @@
         		assertEquals("", "");
         	}
         } finally {
-        	if (warNotFoundExceptionThrown == false) {
+        	if (!warNotFoundExceptionThrown) {
         		assertNotNull("HttpServer getInstance is null", myHttpServer);
         	} else {
         		assertNull("HttpServer getInstance is null because console.war could not be found", myHttpServer);
@@ -67,14 +67,13 @@
         		assertEquals("", "");
         	}
         } finally {
-        	if (warNotFoundExceptionThrown == false) {
+        	if ((!warNotFoundExceptionThrown) && (myHttpServer != null)){
                 myHttpServer.startServer();
                 assertTrue(myHttpServer.isServerStarted());
         	} else {
         		assertNull("HttpServer getInstance is null because console.war could not be found", myHttpServer);
         	}
         }
-
     }
 
     @Test
@@ -90,7 +89,7 @@
         		assertEquals("", "");
         	}
         } finally {
-        	if (warNotFoundExceptionThrown == false) {
+			if ((!warNotFoundExceptionThrown) && (myHttpServer != null)){
                 myHttpServer.startServer();
                 assertFalse(myHttpServer.isServerStopped());
         	} else {
@@ -112,7 +111,7 @@
         		assertEquals("", "");
         	}
         } finally {
-        	if (warNotFoundExceptionThrown == false) {
+			if ((!warNotFoundExceptionThrown) && (myHttpServer != null)){
         		assertEquals("/console", myHttpServer.getConsoleContextPath());
         	} else {
         		assertNull("HttpServer getInstance is null because console.war could not be found", myHttpServer);
@@ -135,7 +134,7 @@
         		assertEquals("", "");
         	}
         } finally {
-        	if (warNotFoundExceptionThrown == false) {
+			if ((!warNotFoundExceptionThrown) && (myHttpServer != null)){
                 myHttpServer.startServer();
                 int portNum = myHttpServer.getConsolePortNumber();
                 String context = myHttpServer.getConsoleContextPath();
@@ -159,7 +158,7 @@
         		assertEquals("", "");
         	}
         } finally {
-        	if (warNotFoundExceptionThrown == false) {
+			if ((!warNotFoundExceptionThrown) && (myHttpServer != null)){
                 myHttpServer.startServer();
                 int portNum = myHttpServer.getConsolePortNumber();
                 assertTrue("the port number is not in integer range: " + Integer.toString(portNum),
diff --git a/console/server/src/test/java/org/apache/edgent/test/console/server/ServerUtilTest.java b/console/server/src/test/java/org/apache/edgent/test/console/server/ServerUtilTest.java
index 7dfc855..cea0ac4 100644
--- a/console/server/src/test/java/org/apache/edgent/test/console/server/ServerUtilTest.java
+++ b/console/server/src/test/java/org/apache/edgent/test/console/server/ServerUtilTest.java
@@ -37,7 +37,7 @@
     @Test
     public void testGetPath() throws IOException {
         ServerUtil serverUtil = new ServerUtil();
-        assertNotNull("Get AbsoluteWarFilePath is null", serverUtil.getAbsoluteWarFilePath("console.war"));
+        assertNotNull("Get AbsoluteWarFilePath is null", serverUtil.getWarFilePath());
     }
 
 }
diff --git a/console/servlets/README b/console/servlets/README
index 144b0cd..dbcbbf2 100644
--- a/console/servlets/README
+++ b/console/servlets/README
@@ -6,16 +6,3 @@
 
 servlets/webapp_content/js/ext/d3.legend.js: 
 Obtained from: https://github.com/jieter/d3-legend/blob/master/d3.legend.js
-
-servlets/webapp_content/js/ext/d3.min.js: 
-Obtained from: https://github.com/d3/d3/releases/tag/v3.5.9
-
-servlets/webapp_content/js/ext/sankey_edgent.js: 
-Originally obtained sankey.js from: https://github.com/d3/d3-sankey
-Modified it and renamed it to sankey_edgent.js
-
-servlets/webapp_content/js/ext/jquery-ui-1.11.4.custom:
-Obtained from: http://jqueryui.com (select 'Custom Download')
-
-servlets/webapp_content/js/ext/jquery-ui-1.11.4.custom/external/jquery/jquery.js:
-Obtained from: https://code.jquery.com/jquery/
diff --git a/console/servlets/build.gradle b/console/servlets/build.gradle
deleted file mode 100644
index 272a672..0000000
--- a/console/servlets/build.gradle
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-distsDirName = 'webapps'
-
-plugins.apply 'war'
-
-dependencies {
-  addTargetDirProjectJarDependency 'providedCompile', ':utils:streamscope'
-  addProjectExtDependency 'providedCompile', 'javax.servlet:javax.servlet-api:3.1.0'
-  addTargetDirCoreExtJarDependencies 'providedCompile'
-
-  // N.B. root project adds test common dependencies
-}
-
-war {
-  destinationDir = file("$target_java8_dir/$project.simpleGroupName/webapps")
-  archiveName 'console.war'
-  from file('webapp_content/html')
-  into('resources') {
-    from file('webapp_content/resources')
-  }
-  into('js') {
-    from file('webapp_content/js')
-  }
-  webXml = file('webapp_content/WEB-INF/console.xml')
-  
-  doFirst {
-    configure jarOptions
-  }
-}
-
-testClasses.dependsOn war
diff --git a/console/servlets/pom.xml b/console/servlets/pom.xml
new file mode 100644
index 0000000..88d01e1
--- /dev/null
+++ b/console/servlets/pom.xml
@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.edgent</groupId>
+    <artifactId>edgent-console</artifactId>
+    <version>1.2.0</version>
+  </parent>
+
+  <artifactId>edgent-console-servlets</artifactId>
+  <packaging>war</packaging>
+
+  <name>Apache Edgent (Java 8): Console: Servlets</name>
+
+  <build>
+    <!-- Add the directory where all the external resources were unpacked to the build -->
+    <resources>
+      <resource>
+        <directory>${project.build.directory}/generated-resources/META-INF/resources/webjars</directory>
+        <targetPath>${project.build.directory}/${project.artifactId}-${project.version}/js/ext</targetPath>
+      </resource>
+      <!--  bundle the licenses of the artifacts we are bundling -->
+      <resource>
+        <directory>${project.basedir}/../../src/main/appended-resources/licenses</directory>
+        <targetPath>${project.build.directory}/${project.artifactId}-${project.version}/META-INF/licenses</targetPath>
+      </resource>
+    </resources>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.rat</groupId>
+        <artifactId>apache-rat-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>license-check</id>
+            <phase>verify</phase>
+            <goals>
+              <goal>check</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <excludes combine.children="append">
+            <!-- TODO: Still got one unlicensed file in there ... -->
+            <exclude>src/main/webapp/js/ext/**</exclude>
+          </excludes>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-remote-resources-plugin</artifactId>
+        <configuration>
+          <outputDirectory>${project.build.directory}/${project.artifactId}-${project.version}</outputDirectory>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>unpack-js-deps</id>
+            <phase>generate-resources</phase>
+            <goals>
+              <goal>unpack</goal>
+            </goals>
+            <configuration>
+              <!--  Bundle these dependencies in the war.
+                    
+                  You must maintain the corresponding info if you
+                  add/remove artifacts or change their versions:
+                    - entry in src/main/webapp/META-INF/LICENSE
+                    - entry in src/main/webapp/META-INF/NOTICE when appropriate
+                    
+                  You must maintain the info in the corresponding
+                  console/server files because it bundles this war. 
+                  
+                  You must maintain the info in this project's
+                  corresponding platforms/java7 files.
+               -->
+              <artifactItems>
+                <artifactItem>
+                  <groupId>org.webjars</groupId>
+                  <artifactId>jquery</artifactId>
+                  <version>1.11.2</version>
+                  <outputDirectory>${project.build.directory}/generated-resources</outputDirectory>
+                  <includes>META-INF/resources/webjars/**/jquery.min.js</includes>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.webjars</groupId>
+                  <artifactId>jquery-ui</artifactId>
+                  <version>1.11.4</version>
+                  <outputDirectory>${project.build.directory}/generated-resources</outputDirectory>
+                  <includes>META-INF/resources/webjars/**/images/*.png,META-INF/resources/webjars/**/*.min.*</includes>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.webjars.bower</groupId>
+                  <artifactId>d3</artifactId>
+                  <version>3.3.9</version>
+                  <outputDirectory>${project.build.directory}/generated-resources</outputDirectory>
+                  <includes>META-INF/resources/webjars/**/**.min.*</includes>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.webjars.bower</groupId>
+                  <artifactId>d3-plugins-sankey</artifactId>
+                  <version>1.1.0</version>
+                  <outputDirectory>${project.build.directory}/generated-resources</outputDirectory>
+                  <includes>META-INF/resources/webjars/**/*.css,META-INF/resources/webjars/**/*.js</includes>
+                </artifactItem>
+              </artifactItems>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <!-- Download JavaScript form GitHub -->
+      <!--plugin>
+        <groupId>com.googlecode.maven-download-plugin</groupId>
+        <artifactId>download-maven-plugin</artifactId>
+        <version>1.2.1</version>
+        <executions>
+          <execution>
+            <id>get-d3-legend-js</id>
+            <phase>generate-resources</phase>
+            <goals>
+              <goal>wget</goal>
+            </goals>
+            <configuration>
+              <url>https://gist.githubusercontent.com/ZJONSSON/3918369/raw/bf9bce6b68a3b70f87450f155436ca4a84af1ba4/d3.legend.js</url>
+              <outputDirectory>${project.build.directory}/generated-resources/META-INF/resources/webjars/d3.legend/</outputDirectory>
+              <outputFileName>d3.legend.js</outputFileName>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin-->
+      <plugin>
+        <!--  Included to enable "mvn tomcat:run" and have a running
+              console server without using the server jar.
+        -->
+        <groupId>org.apache.tomcat.maven</groupId>
+        <artifactId>tomcat7-maven-plugin</artifactId>
+        <version>2.2</version>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.edgent</groupId>
+      <artifactId>edgent-utils-streamscope</artifactId>
+      <version>1.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+      <version>3.1.0</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/console/servlets/src/main/java/org/apache/edgent/console/servlets/ConsoleJobServlet.java b/console/servlets/src/main/java/org/apache/edgent/console/servlets/ConsoleJobServlet.java
index d1b1fb9..94bc913 100644
--- a/console/servlets/src/main/java/org/apache/edgent/console/servlets/ConsoleJobServlet.java
+++ b/console/servlets/src/main/java/org/apache/edgent/console/servlets/ConsoleJobServlet.java
@@ -50,32 +50,31 @@
         boolean jobsInfo = false;
         boolean jobGraph = false;
         for(Map.Entry<String,String[]> entry : parameterMap.entrySet()) {
-                if (entry.getKey().equals("jobsInfo")) {
+                if ("jobsInfo".equals(entry.getKey())) {
                         String[] vals = entry.getValue();
-                        if (vals[0].equals("true")) {
+                        if ("true".equals(vals[0])) {
                                 jobsInfo = true;
                         }
-                } else if (entry.getKey().equals("jobgraph")) {
+                } else if ("jobgraph".equals(entry.getKey())) {
                         String[] vals = entry.getValue();
-                        if (vals[0].equals("true")) {
+                        if ("true".equals(vals[0])) {
                                 jobGraph = true;
                         }
-                } else if (entry.getKey().equals("jobId")) {
+                } else if ("jobId".equals(entry.getKey())) {
                         String[] ids = entry.getValue();
                         if (ids.length == 1) {
                                 jobId = ids[0];
                         }
                 }
         }
-        
 
-        StringBuffer sbuf = new StringBuffer();
+        StringBuilder sbuf = new StringBuilder();
         sbuf.append("*:interface=");
         sbuf.append(ObjectName.quote("org.apache.edgent.execution.mbeans.JobMXBean"));
         sbuf.append(",type=");
         sbuf.append(ObjectName.quote("job"));
         
-        if (!jobId.equals("")) {
+        if (!jobId.isEmpty()) {
         	sbuf.append(",id=");
         	sbuf.append(ObjectName.quote(jobId));
         } 
@@ -90,13 +89,16 @@
         String jsonString = "";
         if (jobsInfo) {
         	jsonString = JobUtil.getJobsInfo(jobObjName);
-        } else if (jobGraph && !(jobId.equals("")) && !(jobId.equals("undefined"))) {
+        } else if (jobGraph && !(jobId.isEmpty()) && !("undefined".equals(jobId))) {
             jsonString = JobUtil.getJobGraph(jobObjName);
         }
 
         response.setContentType("application/json");
         response.setCharacterEncoding("UTF-8");
-        response.getWriter().write(jsonString);
-        
+        try {
+            response.getWriter().write(jsonString);
+        } catch (IOException e) {
+            throw new ServletException(e);
+        }
 	}		
 }
diff --git a/console/servlets/src/main/java/org/apache/edgent/console/servlets/JobUtil.java b/console/servlets/src/main/java/org/apache/edgent/console/servlets/JobUtil.java
index fbd449d..8cee003 100644
--- a/console/servlets/src/main/java/org/apache/edgent/console/servlets/JobUtil.java
+++ b/console/servlets/src/main/java/org/apache/edgent/console/servlets/JobUtil.java
@@ -40,14 +40,14 @@
 
 final class JobUtil {
 	
-	static MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
+	private static MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
 	private static final Logger logger = LoggerFactory.getLogger(JobUtil.class);
 
 	static String getJobsInfo(ObjectName jobObjName) {
         Set<ObjectInstance> jobInstances = mBeanServer.queryMBeans(jobObjName, null);
         
         Iterator<ObjectInstance> jobIterator = jobInstances.iterator();
-        StringBuffer json = new StringBuffer("[");
+        StringBuilder json = new StringBuilder("[");
         int counter = 0;
         while (jobIterator.hasNext()) {
         	if (counter > 0) {
@@ -55,47 +55,43 @@
         	}
         	ObjectInstance jobInstance = jobIterator.next();
             ObjectName jobObjectName = jobInstance.getObjectName();
-            MBeanInfo mBeanInfo = null;
+            MBeanInfo mBeanInfo;
 			try {
 				mBeanInfo = mBeanServer.getMBeanInfo(jobObjectName);
 			} catch (IntrospectionException | InstanceNotFoundException | ReflectionException e) {
 			    logger.error("Exception caught while getting MBeanInfo", e);
+				throw new RuntimeException("Exception caught while getting MBeanInfo", e);
 			}
             
             /*
              * Get the names of all the attributes
              */
-			            
-			Set<String> names = new HashSet<String> ();
+			Set<String> names = new HashSet<> ();
 	    	for (MBeanAttributeInfo attributeInfo : mBeanInfo.getAttributes()) {
 	    			names.add(attributeInfo.getName());
 	    	}
 	    	// now construct the job json and add it to the string buffer
-	    	StringBuffer s = new StringBuffer();
+	    	StringBuilder s = new StringBuilder();
 	    	s.append("{\"");
-	    	Iterator<String> it = names.iterator();
-	    	while(it.hasNext()) {
-	    		String attr = it.next();
-	    		s.append(attr);
-	    		s.append("\":\"");
-	    		try {
-					s.append((String)mBeanServer.getAttribute(jobObjectName, attr));
-				} catch (AttributeNotFoundException | InstanceNotFoundException 
+			for (String attr : names) {
+				s.append(attr);
+				s.append("\":\"");
+				try {
+					s.append((String) mBeanServer.getAttribute(jobObjectName, attr));
+				} catch (AttributeNotFoundException | InstanceNotFoundException
 						| MBeanException | ReflectionException e) {
-				    logger.error("Exception caught while accessing MBean", e);
+					logger.error("Exception caught while accessing MBean", e);
 				}
-	    		s.append("\",\"");
-	    	}
+				s.append("\",\"");
+			}
 	    	// remove the trailing ,\
 	    	s.deleteCharAt(s.length()-1);
 	    	s.deleteCharAt(s.length()-1);
-	    	json.append(s.toString() + "}");
+	    	json.append(s.toString()).append("}");
 	    	counter++;
         }
         json.append("]");
-        String jsonString = json.toString();
-		
-		return jsonString;
+        return json.toString();
 	}
 	
 	static String getJobGraph(ObjectName jobObjName) {
@@ -109,28 +105,28 @@
         String gSnapshot = "";
         if (jobInstance != null) {
             ObjectName jobObjectName = jobInstance.getObjectName();
-            MBeanInfo mBeanInfo = null;
+            MBeanInfo mBeanInfo;
 			try {
 				mBeanInfo = mBeanServer.getMBeanInfo(jobObjectName);
 			} catch (IntrospectionException | InstanceNotFoundException | ReflectionException e) {
 			    logger.error("Exception caught while getting MBeanInfo", e);
+			    throw new RuntimeException("Exception caught while getting MBeanInfo", e);
 			}
+
 	    	/*
 	    	 * Now get the graph for the job
 	    	 */
-	    	Set<String> operations = new HashSet<String> ();
 	    	for (MBeanOperationInfo operationInfo: mBeanInfo.getOperations()) {
-	    		operations.add(operationInfo.getName());
 	    		if (operationInfo.getName().equals("graphSnapshot")) {
 	    			try {
 						gSnapshot = (String) mBeanServer.invoke(jobObjectName, "graphSnapshot",null, null);
-						//System.out.println(gSnapshot);
 					} catch (InstanceNotFoundException | ReflectionException | MBeanException e) {
 					    logger.error("Exception caught while invoking operation on MBean", e);
-					}
+                        throw new RuntimeException("Exception caught while invoking operation on MBean", e);
+                    }
 	    		}
 	    	}
-	}
+	    }
 		return gSnapshot;
 	}
 }
diff --git a/console/servlets/src/main/java/org/apache/edgent/console/servlets/MetricsGson.java b/console/servlets/src/main/java/org/apache/edgent/console/servlets/MetricsGson.java
index 7ba60b7..7f29de1 100644
--- a/console/servlets/src/main/java/org/apache/edgent/console/servlets/MetricsGson.java
+++ b/console/servlets/src/main/java/org/apache/edgent/console/servlets/MetricsGson.java
@@ -24,12 +24,17 @@
 
 public class MetricsGson {
 
-	public String jobId = null;
-	public ArrayList<Operator> ops = new ArrayList<Operator>();
+    private String jobId = null;
+    private ArrayList<Operator> ops = new ArrayList<>();
 	
 	class Operator {
-		String opId = null;
-		ArrayList<OpMetric> metrics = null;
+		final String opId;
+		final ArrayList<OpMetric> metrics;
+		Operator(String name) {
+		    opId = name;
+		    metrics = new ArrayList<>();
+		    MetricsGson.this.addOp(this);
+		}
 	}
 	
 	class OpMetric {
@@ -58,7 +63,7 @@
 		
 		Iterator<Operator> opsIterator = this.ops.iterator();
 		while (opsIterator.hasNext()) {
-			Operator op = (Operator) opsIterator.next();
+			Operator op = opsIterator.next();
 			if (getOpId(op).equals(opId)) {
 				return op;
 			}
@@ -77,7 +82,7 @@
 		
 		Iterator<Operator> opsIterator = this.ops.iterator();
 		while (opsIterator.hasNext()) {
-			Operator op = (Operator) opsIterator.next();
+			Operator op = opsIterator.next();
 			if (getOpId(op).equals(opId)) {
 				return true;
 			}
@@ -97,8 +102,4 @@
 		return theOp.metrics;
 	}
 	
-	public void setOpMetrics(Operator theOp, ArrayList<OpMetric> theMetrics) {
-		theOp.metrics = theMetrics;
-	}
-	
 }
diff --git a/console/servlets/src/main/java/org/apache/edgent/console/servlets/MetricsUtil.java b/console/servlets/src/main/java/org/apache/edgent/console/servlets/MetricsUtil.java
index bca6cc6..3d74338 100644
--- a/console/servlets/src/main/java/org/apache/edgent/console/servlets/MetricsUtil.java
+++ b/console/servlets/src/main/java/org/apache/edgent/console/servlets/MetricsUtil.java
@@ -19,7 +19,6 @@
 package org.apache.edgent.console.servlets;
 
 import java.lang.management.ManagementFactory;
-import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.Set;
 
@@ -42,13 +41,13 @@
 
 final class MetricsUtil {
 	
-	static MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
+	private static MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
 	private static final Logger logger = LoggerFactory.getLogger(MetricsUtil.class);
 
 	static Iterator<ObjectInstance> getCounterObjectIterator(String jobId) {
 		ObjectName counterObjName = null;
-		StringBuffer sbuf = new StringBuffer();
-		sbuf.append("*:jobId=" + jobId);
+		StringBuilder sbuf = new StringBuilder();
+		sbuf.append("*:jobId=").append(jobId);
 		sbuf.append(",type=metric.counters,*");
 
         // i.e, edgent.providers.development:jobId=JOB-0,opId=OP_4,name=TupleRateMeter.edgent.oplet.JOB_0.OP_4,type=metric.meters
@@ -64,8 +63,8 @@
 	static Iterator<ObjectInstance> getMeterObjectIterator(String jobId) {
 		ObjectName meterObjName = null;
 			
-			StringBuffer sbuf1 = new StringBuffer();
-			sbuf1.append("*:jobId=" + jobId);
+			StringBuilder sbuf1 = new StringBuilder();
+			sbuf1.append("*:jobId=").append(jobId);
 			sbuf1.append(",type=metric.meters,*");
 
 			try {
@@ -82,21 +81,19 @@
 	
 	static MetricsGson getAvailableMetricsForJob(String jobId, Iterator<ObjectInstance> meterIterator, Iterator<ObjectInstance> counterIterator) {
 		MetricsGson gsonJob = new MetricsGson();
-		ArrayList<Operator> counterOps = new ArrayList<Operator>();
 		gsonJob.setJobId(jobId);
 		while (meterIterator.hasNext()) {
-			ArrayList<OpMetric> metrics = null;
-			ObjectInstance meterInstance = (ObjectInstance)meterIterator.next();
+			ObjectInstance meterInstance = meterIterator.next();
 			ObjectName mObjName = meterInstance.getObjectName(); 
 			String opName = mObjName.getKeyProperty("opId");
 
-			Operator anOp = null;
 				if (!opName.equals("")) {
-					MBeanInfo mBeanInfo = null;
+					MBeanInfo mBeanInfo;
 					try {
 						mBeanInfo = mBeanServer.getMBeanInfo(mObjName);
 					} catch (IntrospectionException | InstanceNotFoundException | ReflectionException e) {
 					    logger.error("Exception caught while getting MBeanInfo", e);
+					    throw new RuntimeException("Exception caught while getting MBeanInfo", e);
 					}
 
 			    	for (MBeanAttributeInfo attributeInfo : mBeanInfo.getAttributes()) {
@@ -112,32 +109,27 @@
 	    				}
 		    			
 		    			// if the op associated with this metric is not in the job add it
-		    			if (!gsonJob.isOpInJob(opName)) {
-						    anOp = gsonJob.new Operator();
-						    gsonJob.addOp(anOp);
-						    anOp.opId = opName;
-						    counterOps.add(anOp);
-						    metrics = new ArrayList<OpMetric>();
-		    			} 
-		    			metrics.add(aMetric);
+		    			Operator theOp = gsonJob.getOp(opName);
+		    			if (theOp == null) {
+						    theOp = gsonJob.new Operator(opName);
+		    			}
+		    			theOp.metrics.add(aMetric);
 			    	}
-			    	gsonJob.setOpMetrics(anOp, metrics);
 				}
 		}
 
 		while (counterIterator.hasNext()) {
-			ArrayList<OpMetric> metrics = null;
-			ObjectInstance counterInstance = (ObjectInstance)counterIterator.next();
+			ObjectInstance counterInstance = counterIterator.next();
 			ObjectName cObjName = counterInstance.getObjectName();
 			String opName1 = cObjName.getKeyProperty("opId");
 
-			Operator anOp = null;
 			if (!opName1.equals("")) {
-			MBeanInfo mBeanInfo = null;
+			MBeanInfo mBeanInfo;
 			try {
 				mBeanInfo = mBeanServer.getMBeanInfo(cObjName);
 			} catch (IntrospectionException | InstanceNotFoundException | ReflectionException e) {
 			    logger.error("Exception caught while getting MBeanInfo", e);
+			    throw new RuntimeException("Exception caught while getting MBeanInfo", e);
 			}
 
 	    	for (MBeanAttributeInfo attributeInfo : mBeanInfo.getAttributes()) {
@@ -152,16 +144,9 @@
 				}
     			Operator theOp = gsonJob.getOp(opName1);
 				if (theOp == null) {
-					anOp = gsonJob.new Operator();
-					gsonJob.addOp(anOp);
-					anOp.opId = opName1;
-					metrics = new ArrayList<OpMetric>();
-					gsonJob.setOpMetrics(anOp, metrics);
-				} else {
-					// get the op
-					metrics = theOp.metrics;
+                    theOp = gsonJob.new Operator(opName1);
 				}
-    			metrics.add(aMetric);
+    			theOp.metrics.add(aMetric);
 	    	}
 			}
 		}
@@ -172,7 +157,7 @@
 	
 	/**
 	 * Get all the rate metrics, i.e, one minute rate, fifteen minute rate, mean rate, etc  for a job
-	 * @param job id (e.g, "JOB_0")
+	 * @param jobId id (e.g, "JOB_0")
 	 * 
 	 * @return  all metrics for this job if there are any
 	 */
@@ -182,21 +167,20 @@
 		
 		Iterator<ObjectInstance> meterIterator = MetricsUtil.getMeterObjectIterator(jobId);
 		while (meterIterator.hasNext()) {
-			ArrayList<OpMetric> metrics = null;
-			ObjectInstance meterInstance = (ObjectInstance)meterIterator.next();
+			ObjectInstance meterInstance = meterIterator.next();
 			ObjectName mObjName = meterInstance.getObjectName();
 			//i.e, edgent.providers.development:jobId=JOB-0,opId=OP_4,name=TupleRateMeter.edgent.oplet.JOB_0.OP_4,type=metric.meters
 			String jobName = mObjName.getKeyProperty("jobId");
 			String opName = mObjName.getKeyProperty("opId");
-			Operator anOp = null;
 
 			if (jobId.equals(jobName)) {
-				MBeanInfo mBeanInfo = null;
+				MBeanInfo mBeanInfo;
 			
 				try {
 					mBeanInfo = mBeanServer.getMBeanInfo(mObjName);
 				} catch (IntrospectionException | InstanceNotFoundException | ReflectionException e) {
 				    logger.error("Exception caught while getting MBeanInfo", e);
+				    throw new RuntimeException("Exception caught while getting MBeanInfo", e);
 				}
 				
 		    	for (MBeanAttributeInfo attributeInfo : mBeanInfo.getAttributes()) {
@@ -210,35 +194,28 @@
 							| ReflectionException e) {
 					    logger.error("Exception caught while accessing MBean", e);
     				}
-					 if (!gsonJob.isOpInJob(opName)) {
-					    anOp = gsonJob.new Operator();
-					    gsonJob.addOp(anOp);
-					    anOp.opId = opName;
-					    metrics = new ArrayList<OpMetric>();
-					    gsonJob.setOpMetrics(anOp, metrics);
-					 } else {
-						anOp = gsonJob.getOp(opName);
-						metrics = anOp.metrics;
+					 Operator theOp = gsonJob.getOp(opName);
+					 if (theOp == null) {
+					    theOp = gsonJob.new Operator(opName);
 					 }
-					 metrics.add(aMetric);
+					 theOp.metrics.add(aMetric);
 		    	}
 			}
 		}
 		
 		Iterator<ObjectInstance> counterIterator = MetricsUtil.getCounterObjectIterator(jobId);
 		while (counterIterator.hasNext()) {
-			ArrayList<OpMetric> metrics = null;
-			ObjectInstance counterInstance = (ObjectInstance)counterIterator.next();
+			ObjectInstance counterInstance = counterIterator.next();
 			ObjectName cObjName = counterInstance.getObjectName();
 			String opName1 = cObjName.getKeyProperty("opId");
 
-			Operator anOp = null;
 			if (!opName1.equals("")) {
-			MBeanInfo mBeanInfo = null;
+			MBeanInfo mBeanInfo;
 			try {
 				mBeanInfo = mBeanServer.getMBeanInfo(cObjName);
 			} catch (IntrospectionException | InstanceNotFoundException | ReflectionException e) {
 			    logger.error("Exception caught while getting MBeanInfo", e);
+			    throw new RuntimeException("Exception caught while getting MBeanInfo", e);
 			}
 
 	    	for (MBeanAttributeInfo attributeInfo : mBeanInfo.getAttributes()) {
@@ -253,16 +230,9 @@
 				}
     			Operator theOp = gsonJob.getOp(opName1);
 				if (theOp == null) {
-					anOp = gsonJob.new Operator();
-					gsonJob.addOp(anOp);
-					anOp.opId = opName1;
-					metrics = new ArrayList<OpMetric>();
-					gsonJob.setOpMetrics(anOp, metrics);
-				} else {
-					// get the op
-					metrics = theOp.metrics;
+					theOp = gsonJob.new Operator(opName1);
 				}
-    			metrics.add(aMetric);
+    			theOp.metrics.add(aMetric);
 	    	}
 			}
 		}
@@ -274,29 +244,27 @@
 		MetricsGson gsonJob = new MetricsGson();
 		gsonJob.setJobId(jobId);
 		String[] desiredParts = metricName.split(",");
-		String[] nameA = new String[2];
 		String desName = "";
 		if (!desiredParts[0].equals("")) {
-			nameA = desiredParts[0].split(":");
+			String[] nameA = desiredParts[0].split(":");
 			desName = nameA[1];
 		}
 		
 		while (metricIterator.hasNext()) {
-			ArrayList<OpMetric> metrics = null;
-			ObjectInstance meterInstance = (ObjectInstance)metricIterator.next();
+			ObjectInstance meterInstance = metricIterator.next();
 			ObjectName mObjName = meterInstance.getObjectName();
 			//i.e, edgent.providers.development:jobId=JOB-0,opId=OP_4,name=TupleRateMeter.edgent.oplet.JOB_0.OP_4,type=metric.meters
 			String jobName = mObjName.getKeyProperty("jobId");
 			String opName = mObjName.getKeyProperty("opId");
-			Operator anOp = null;
 
 			if (jobId.equals(jobName)) {
-				MBeanInfo mBeanInfo = null;
+				MBeanInfo mBeanInfo;
 			
 				try {
 					mBeanInfo = mBeanServer.getMBeanInfo(mObjName);
 				} catch (IntrospectionException | InstanceNotFoundException | ReflectionException e) {
 				    logger.error("Exception caught while getting MBeanInfo", e);
+				    throw new RuntimeException("Exception caught while getting MBeanInfo", e);
 				}
 				
 		    	for (MBeanAttributeInfo attributeInfo : mBeanInfo.getAttributes()) {
@@ -311,38 +279,31 @@
 								| ReflectionException e) {
 						    logger.error("Exception caught while accessing MBean", e);
 						}
-    					if (!gsonJob.isOpInJob(opName)) {
-    					    anOp = gsonJob.new Operator();
-    					    gsonJob.addOp(anOp);
-    					    anOp.opId = opName;
-    					    metrics = new ArrayList<OpMetric>();
-    					    gsonJob.setOpMetrics(anOp, metrics);
-    					} else {
-    						anOp = gsonJob.getOp(opName);
-    						metrics = anOp.metrics;
+    					Operator theOp = gsonJob.getOp(opName);
+    					if (theOp == null) {
+    					    theOp = gsonJob.new Operator(opName);
     					}
-	    				 metrics.add(aMetric);
+	    				 theOp.metrics.add(aMetric);
 	    			 }
 		    	}
 			}
 		}
 		
 		while (counterIterator.hasNext()) {
-			ArrayList<OpMetric> metrics = null;
-			ObjectInstance counterInstance = (ObjectInstance)counterIterator.next();
+			ObjectInstance counterInstance = counterIterator.next();
 			ObjectName cObjName = counterInstance.getObjectName();
 			String jobName1 = cObjName.getKeyProperty("jobId");
 			String opName1 = cObjName.getKeyProperty("opId");
 			
 
-			Operator anOp = null;
 			if (jobId.equals(jobName1)) {
-				MBeanInfo mBeanInfo = null;
+				MBeanInfo mBeanInfo;
 			
 				try {
 					mBeanInfo = mBeanServer.getMBeanInfo(cObjName);
 				} catch (IntrospectionException | InstanceNotFoundException | ReflectionException e) {
 				    logger.error("Exception caught while getting MBeanInfo", e);
+				    throw new RuntimeException("Exception caught while getting MBeanInfo", e);
 				}
 				
 		    	for (MBeanAttributeInfo attributeInfo : mBeanInfo.getAttributes()) {
@@ -358,17 +319,11 @@
 								| ReflectionException e) {
 						    logger.error("Exception caught while accessing MBean", e);
 						}
-    					if (!gsonJob.isOpInJob(opName1)) {
-    					    anOp = gsonJob.new Operator();
-    					    gsonJob.addOp(anOp);
-    					    anOp.opId = opName1;
-    					    metrics = new ArrayList<OpMetric>();
-    					    gsonJob.setOpMetrics(anOp, metrics);
-    					} else {
-    						anOp = gsonJob.getOp(opName1);
-    						metrics = anOp.metrics;
+    					Operator theOp = gsonJob.getOp(opName1);
+    					if (theOp == null) {
+    					    theOp = gsonJob.new Operator(opName1);
     					}
-	    				 metrics.add(aMetric);
+	    				 theOp.metrics.add(aMetric);
 	    			 }
 		    	}
 			}
diff --git a/console/servlets/src/main/remote-resources/META-INF/LICENSE b/console/servlets/src/main/remote-resources/META-INF/LICENSE
new file mode 100644
index 0000000..29d3c2e
--- /dev/null
+++ b/console/servlets/src/main/remote-resources/META-INF/LICENSE
@@ -0,0 +1,42 @@
+
+===============================================================================
+APACHE EDGENT SUBCOMPONENTS:
+
+This binary includes a number of subcomponents with separate
+copyright notices and license terms.  Your use of this binary
+is subject to the terms and conditions of the following licenses.
+
+===============================================================================
+License: Apache License Version 2.0
+For details, see META-INF/licenses/apache-license-version-2.0.txt
+
+gson (com.google.code.gson:gson:2.2.4)
+metrics-core (io.dropwizard.metrics:metrics-core:3.1.2)
+
+===============================================================================
+License: MIT
+
+jquery (org.webjars:jquery:1.11.2)
+    For details, see META-INF/licenses/jquery-1_11_2-MIT.txt.
+
+jquery-ui (org.webjars:jquery-ui:1.11.4)
+    For details, see META-INF/licenses/jquery-ui-1_11_4-MIT.txt.
+
+d3.legend.js (src/main/webapp/js/ext/d3.legend/d3.legend.js)
+    https://gist.githubusercontent.com/ZJONSSON/3918369/raw/bf9bce6b68a3b70f87450f155436ca4a84af1ba4/d3.legend.js
+    With Edgent specific modifications.
+    For details, see META-INF/licenses/d3.legend-MIT.txt.
+
+===============================================================================
+License: BSD 3-Clause
+
+d3 (org.webjars.bower:d3:3.3.9)
+    For details, see META-INF/licenses/d3-3_3_9-BSD.txt.
+
+===============================================================================
+License: BSD 2-Clause
+
+d3-plugins-sankey (org.webjars.bower:d3-plugins-sankey:1.1.0)
+    For details, see META-INF/licenses/d3-plugins-sankey-1_1_0-BSD.txt.
+
+===============================================================================
diff --git a/console/servlets/src/main/remote-resources/META-INF/NOTICE b/console/servlets/src/main/remote-resources/META-INF/NOTICE
new file mode 100644
index 0000000..9c9e7e9
--- /dev/null
+++ b/console/servlets/src/main/remote-resources/META-INF/NOTICE
@@ -0,0 +1,28 @@
+===============================================================================
+
+Portions of this software were developed by IBM Corp.
+Copyright IBM Corp. 2015, 2016
+
+===============================================================================
+
+APACHE EDGENT SUBCOMPONENTS:
+
+This product includes a number of subcomponents with separate
+copyright notices and license terms.  The following notices apply.
+
+-------------------------------------------------------------------------------
+metrics-core (io.dropwizard.metrics:metrics-core:3.1.2)
+
+Metrics
+Copyright 2010-2013 Coda Hale and Yammer, Inc.
+
+This product includes software developed by Coda Hale and Yammer, Inc.
+
+This product includes code derived from the JSR-166 project (ThreadLocalRandom,
+Striped64, LongAdder) with the following comments:
+
+          Written by Doug Lea with assistance from members of JCP JSR-166
+          Expert Group and released to the public domain, as explained at
+          http://creativecommons.org/publicdomain/zero/1.0/
+
+===============================================================================
diff --git a/console/servlets/src/main/webapp/WEB-INF/web.xml b/console/servlets/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..f4aa1d1
--- /dev/null
+++ b/console/servlets/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,62 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
+         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
+         version="3.1">
+
+  <servlet>
+    <display-name>Console Application</display-name>
+    <servlet-name>ConsoleServlet</servlet-name>
+    <servlet-class>org.apache.edgent.console.servlets.ConsoleServlet</servlet-class>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>ConsoleServlet</servlet-name>
+    <url-pattern>/console</url-pattern>
+  </servlet-mapping>
+
+  <servlet>
+    <display-name>Job Listing Service</display-name>
+    <servlet-name>ConsoleJobServlet</servlet-name>
+    <servlet-class>org.apache.edgent.console.servlets.ConsoleJobServlet</servlet-class>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>ConsoleJobServlet</servlet-name>
+    <url-pattern>/jobs</url-pattern>
+  </servlet-mapping>
+
+  <servlet>
+    <display-name>Metrics Listing Service</display-name>
+    <servlet-name>ConsoleMetricsServlet</servlet-name>
+    <servlet-class>org.apache.edgent.console.servlets.ConsoleMetricsServlet</servlet-class>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>ConsoleMetricsServlet</servlet-name>
+    <url-pattern>/metrics</url-pattern>
+  </servlet-mapping>
+
+  <welcome-file-list>
+    <welcome-file>index.html</welcome-file>
+  </welcome-file-list>
+
+</web-app>
\ No newline at end of file
diff --git a/console/servlets/src/main/webapp/index.html b/console/servlets/src/main/webapp/index.html
new file mode 100644
index 0000000..ba48dfc
--- /dev/null
+++ b/console/servlets/src/main/webapp/index.html
@@ -0,0 +1,134 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xml:lang="en-us">
+<head>
+    <title>Apache Edgent Console</title>
+
+    <link rel="stylesheet" type="text/css" href="js/ext/jquery-ui/1.11.4/jquery-ui.min.css">
+    <link rel="stylesheet" type="text/css" href="js/ext/jquery-ui/1.11.4/jquery-ui.theme.min.css">
+    <link rel="stylesheet" type="text/css" href="js/ext/jquery-ui/1.11.4/jquery-ui.structure.min.css">
+    <link rel="stylesheet" type="text/css" href="resources/css/main.css">
+    <link rel="stylesheet" type="text/css" href="resources/css/metrics.css">
+    <link rel="shortcut icon" href="resources/images/favicon.png"/>
+</head>
+
+<body>
+<div role="main">
+    <div style='margin-left:15px; float:left;'>
+        <img height='81' width='178' tabindex=0 alt="Apache Edgent logo" title="Apache Edgent logo"
+             src="resources/images/apache_edgent.png"/>
+    </div>
+    <div style='float:left;margin-top: 30px; font-size: 1.2em; margin-left: 320px;'>
+        <label tabindex=0>Topology Graph</label>
+    </div>
+    <div class="topControls" style='margin-left: 30px;clear:both;'>
+        <label for="jobs" class="jobsLabel">Job:</label>
+        <select id="jobs"></select>
+    </div>
+    <div id='stateImg' tabindex=0
+         style="float:left; display:none; margin-top: 10px; margin-left: 5px; margin-right: 10px;">
+        <div class="hidden">Job state, press the Enter key to display the job state information in a table</div>
+        <img width="28" height="28" alt="job state" src="resources/images/state.png"/>
+    </div>
+    <div class="topControls">
+        <label for="layers" class="layersLabel">View by:</label>
+        <select title="view by" id="layers">
+            <option value="static">Static flow</option>
+            <option value="flow">Tuple count</option>
+            <option value="opletColor">Oplet kind</option>
+        </select>
+
+        <div id="tagsDiv" style="visibility:hidden;margin-top: 5px;">
+            <input type='checkbox' id='showTags' checked/>
+            <label for='showTags'>Show tags:</label>
+            <div>
+
+                <input type='checkbox' id='showAllTags' checked/>
+                <label for='showAllTags'>Show all tags:</label>
+                <button id="tagDialogBtn" type='button' disabled>Select individual tags ...</button>
+            </div>
+            <div id="dialog" title="Select one or more tags">
+                <label for="tags" class="tagsLabel">Tag:</label>
+                <select id="tags" multiple></select>
+            </div>
+        </div>
+    </div>
+    <div class="topControls">
+        <label class="refreshLabel" tabindex=0>Refresh interval:</label>
+        <input aria-label='Refresh interval' name='refreshInterval' id="refreshInterval" style='width: 40px;'
+               type="number" min="3" max="20" step="1" value="5"/>
+        <label title="seconds" tabindex=0 style='margin-left: 5px; margin-right: 10px;'>seconds</label>
+        <button id="toggleTimer" title='Pause graph' type="button">Pause graph</button>
+    </div>
+
+    <div style='width: 370px; margin-left: 30px; clear:both;'>
+        <div id="showAll" tabindex=0>
+            <div>View all oplet properties</div>
+            <div style='font-size: 0.95em'>(opens a new window)</div>
+        </div>
+        <div id="graphLegend" style='height: 600px; width: 340px;'></div>
+    </div>
+    <div id="chart" style="float:right; margin-right: 100px; margin-top: -600px;">
+        <div id="loading">Loading graph ...</div>
+    </div>
+
+
+    <div id="metricsDiv" style="clear:both;display:none;margin-left: 30px;">
+        <div>
+            <label class="metricsTitle">Metrics</label>
+        </div>
+        <div class="metricsControls">
+            <label for='metrics' class="metricsLabel">Metrics:</label>
+            <select id="metrics"></select>
+            <span id="rateUnit"></span>
+        </div>
+
+        <div class="metricsControls">
+            <label for='mChartType' class="chartTypeLabel">Chart type:</label>
+            <select id="mChartType">
+                <option value='barChart' selected>Bar chart</option>
+                <option value='lineChart' selected>Line chart</option>
+            </select>
+        </div>
+
+        <div class="metricsControls">
+            <span id="noLineChartWarning" style="visibility:hidden;" class="warningLabel">*Line charts are not available when you select a metric containing multiple oplets</span>
+        </div>
+        <div id="showMetricsTable" tabindex=0 style='clear:left; padding-top: 10px; width: 150px;'>
+            <div class='hidden'>Show metrics in table. Press the Enter key to display available metrics in a table. To
+                close the dialog once it is open, press the Escape key.
+            </div>
+            <img width="150" height="38" alt='show table metrics' src="resources/images/show_metrics_in_table.png"/>
+        </div>
+
+        <div id="metricsChart" style="clear:left;"></div>
+    </div>
+</div>
+<script src="js/ext/d3/3.3.9/d3.min.js" charset="utf-8" type="application/javascript"></script>
+<script src="js/ext/jquery/1.11.2/jquery.min.js" type="application/javascript"></script>
+<script src="js/ext/jquery-ui/1.11.4/jquery-ui.min.js" type="application/javascript"></script>
+<script src="js/ext/d3-plugins-sankey/1.1.0/sankey.js" type="application/javascript"></script>
+<script src="js/ext/d3.legend/d3.legend.js" type="application/javascript"></script>
+<script src="js/streamtags.js" type="application/javascript"></script>
+<script src="js/graph.js" type="application/javascript"></script>
+<script src="js/metrics.js" type="application/javascript"></script>
+
+<script src="js/index.js" type="application/javascript"></script>
+</body>
+</html>
\ No newline at end of file
diff --git a/console/servlets/src/main/webapp/js/ext/d3.legend/d3.legend.js b/console/servlets/src/main/webapp/js/ext/d3.legend/d3.legend.js
new file mode 100644
index 0000000..79c3c43
--- /dev/null
+++ b/console/servlets/src/main/webapp/js/ext/d3.legend/d3.legend.js
@@ -0,0 +1,193 @@
+// d3.legend.js
+// (C) 2012 ziggy.jonsson.nyc@gmail.com
+// MIT licence
+
+// Edgent modified from https://gist.githubusercontent.com/ZJONSSON/3918369/raw/bf9bce6b68a3b70f87450f155436ca4a84af1ba4/d3.legend.js
+
+(function() {
+    d3.legend = function(g, chartSvg, pItems, legendTitle) {
+        g.each(function() {
+            var g= d3.select(this);
+            var items = {};
+            var svg = !chartSvg ? d3.select(g.property("nearestViewportElement")) : chartSvg;
+            var isTupleFlowLegend = false;
+            var isRect = function(d) {
+                var k = d.key.toUpperCase();
+                return k.startsWith("COUNTEROP")
+                    || k.startsWith("STREAMSCOPE");
+            };
+
+            var	legendPadding = g.attr("data-style-padding") || 5,
+                lTitleItems = g.selectAll(".legend-title-items").data([true]),
+                lb = g.selectAll(".legend-box").data([true]),
+                li = g.selectAll(".legend-items").data([true])
+
+            lTitleItems.enter().append("g").classed("legend-title-items", true)
+            lb.enter().append("rect").classed("legend-box",true)
+            liG = li.enter().append("g").classed("legend-items",true)
+
+            if (pItems) {
+                pItems.forEach(function(p){
+                    if (p.idx !== undefined) {
+                        items[p.name] = {color: p.fill, idx: p.idx};
+                        isTupleFlowLegend = true;
+                    } else {
+                        items[p.name] = {color: p.fill};
+                        isTupleFlowLegend = false;
+                    }
+                });
+            } else {
+                svg.selectAll("[data-legend]").each(function() {
+                    var self = d3.select(this);
+                    items[self.attr("data-legend")] = {
+                        pos : self.attr("data-legend-pos") || this.getBBox().y,
+                        color : self.attr("data-legend-color") != undefined ? self.attr("data-legend-color") : self.style("fill") != 'none' ? self.style("fill") : self.style("stroke")
+                    }
+                });
+            }
+
+            if (isTupleFlowLegend)
+                items = d3.entries(items).sort(
+                    function(a,b) {
+                        if (a.value.idx < b.value.idx) {
+                            return -1;
+                        } else if (a.value.idx > b.value.idx) {
+                            return 1;
+                        } else {
+                            return 0;
+                        }
+                    });
+            else  {
+                items = d3.entries(items).sort(
+                    function(a,b) {
+                        // rect before circle - graphic positioning code below
+                        var ra = isRect(a);
+                        var rb = isRect(b);
+                        if (ra && !rb) {
+                            return -1;
+                        } else if (!ra && rb) {
+                            return 1;
+                        }
+                        if (a.key < b.key) {
+                            return -1;
+                        } else if (a.key > b.key) {
+                            return 1;
+                        } else {
+                            return 0;
+                        }
+                    });
+            }
+
+            li.selectAll("text")
+                .data(items,function(d) {
+                    return d.key}
+                )
+                .call(function(d) { d.enter().append("text")})
+                .call(function(d) { d.exit().remove()})
+                .attr("y",function(d,i) { return i+"em"})
+                .attr("x","1.5em")
+                .text(function(d) {
+                    return d.key;
+                })
+
+            var legendOpacity = 0.7;
+            if (legendTitle && legendTitle === "Stream tags") {
+                legendOpacity = 1.0;
+                li.selectAll("rect")
+                    .data(items,function(d) {
+                        return d.key}
+                    )
+                    .call(function(d) { d.enter().append("rect")})
+                    .call(function(d) { d.exit().remove()})
+                    .attr("y", function(d,i) {
+                        return i-0.75+ "em"})
+                    .attr("width", 10)
+                    .attr("height", 8)
+                    .style("fill",function(d) {
+                        return d.value.color === "#c7c7c7" ? "#008080" : d.value.color;
+                    })
+                    .style("stroke", "none")
+                    .style("fill-opacity", legendOpacity);
+            } else if (legendTitle && legendTitle === "Oplet kind" || legendTitle === "Tuple count"){
+                liG.selectAll("g")
+                    .data(items, function(d) {
+                        return d.key;
+                    })
+                    .enter()
+                    .append(function(d) {
+                        if (isRect(d)) {
+                            return document.createElementNS(d3.ns.prefix.svg, 'rect');
+                        } else {
+                            return document.createElementNS(d3.ns.prefix.svg, 'circle');
+                        }
+                    });
+
+                // rects before circles
+                var count = 0;
+                li.selectAll("rect")
+                    .attr("x", -3)
+                    .attr("y", function(d,i) {
+                        count++;
+                        return i-0.75+ "em"})
+                    .attr("width", legendTitle === "Oplet kind" ? 8 : 10)
+                    .attr("height", 8)
+                    .style("fill",function(d) {
+                        return d.value.color
+                    })
+                    .style("stroke", "none")
+                    .style("fill-opacity", legendOpacity);
+
+                li.selectAll("circle")
+                    .attr("cy",function(d,i) {
+                        return (i+count)-0.25+"em"})
+                    .attr("cx",0)
+                    .attr("r","0.4em")
+                    .style("fill",function(d) {
+                        return d.value.color
+                    })
+                    .style("fill-opacity", legendOpacity);
+
+            } else {
+                li.selectAll("circle")
+                    .data(items,function(d) {
+                        return d.key}
+                    )
+                    .call(function(d) { d.enter().append("circle")})
+                    .call(function(d) { d.exit().remove()})
+                    .attr("cy",function(d,i) { return i-0.25+"em"})
+                    .attr("cx",0)
+                    .attr("r","0.4em")
+                    .style("fill",function(d) {
+                        return d.value.color
+                    })
+                    .style("fill-opacity", legendOpacity);
+            }
+            // Reposition and resize the box
+            var lbbox = li[0][0].getBBox();
+            lb.attr("x",(lbbox.x-legendPadding))
+                .attr("y",(lbbox.y-legendPadding))
+                .attr("height",(lbbox.height+2*legendPadding))
+                .attr("width",((lbbox.width+12) + 2*legendPadding));
+
+            lTitleItems.attr("x", 0)
+                .attr("y", (lbbox.y - legendPadding - 15))
+                .attr("height",15)
+                .attr("width",(lbbox.width+2*legendPadding));
+            if (legendTitle) {
+                lTitleItems.selectAll("text")
+                    .data([""],function(d) {
+                        return legendTitle;
+                    })
+                    .call(function(d) { d.enter().append("text")})
+                    .call(function(d) { d.exit().remove()})
+                    .attr("y",function(d,i) { return "-2em"})
+                    .attr("x",(lbbox.x-legendPadding))
+                    .text(function(d) {
+                        return legendTitle;
+                    });
+            }
+
+        })
+        return g
+    }
+})()
diff --git a/console/servlets/webapp_content/js/graph.js b/console/servlets/src/main/webapp/js/graph.js
similarity index 100%
rename from console/servlets/webapp_content/js/graph.js
rename to console/servlets/src/main/webapp/js/graph.js
diff --git a/console/servlets/webapp_content/js/index.js b/console/servlets/src/main/webapp/js/index.js
similarity index 97%
rename from console/servlets/webapp_content/js/index.js
rename to console/servlets/src/main/webapp/js/index.js
index 43a3e12..949d202 100644
--- a/console/servlets/webapp_content/js/index.js
+++ b/console/servlets/src/main/webapp/js/index.js
@@ -240,10 +240,10 @@
 
 var path = d3.svg.diagonal()
 .source(function(d) { 
-	return {"x":d.sourceIdx.y + d.sourceIdx.dy/2, "y":d.sourceIdx.x + sankey.nodeWidth()/2}; 
+	return {"x":d.source.y + d.source.dy/2, "y":d.source.x + sankey.nodeWidth()/2};
  })            
 .target(function(d) { 
-	return {"x":d.targetIdx.y + d.targetIdx.dy/2, "y":d.targetIdx.x + sankey.nodeWidth()/2}; 
+	return {"x":d.target.y + d.target.dy/2, "y":d.target.x + sankey.nodeWidth()/2};
  })
 .projection(function(d) { 
 	return [d.y, d.x]; 
@@ -703,8 +703,8 @@
 		var sourceStreamAliasesMap = new Map();
 		var sourceStreamTagsMap = new Map();
 		n.targetLinks.forEach(function(trg) {
-			var source = trg.sourceIdx.idx.toString();
-			var sourceLinks = trg.sourceIdx.sourceLinks;
+			var source = trg.source.idx.toString();
+			var sourceLinks = trg.source.sourceLinks;
 			for (var i = 0; i < sourceLinks.length; i++) {
 				if (trg.sourceId == sourceLinks[i].sourceId && trg.targetId == sourceLinks[i].targetId) {
 					if (layer == "static") {
@@ -731,8 +731,8 @@
 		var targetStreamAliasesMap = new Map();
 		var targetStreamTagsMap = new Map();
 		n.sourceLinks.forEach(function(src) {
-			var target = src.targetIdx.idx.toString();
-			var targetLinks = src.targetIdx.targetLinks;
+			var target = src.target.idx.toString();
+			var targetLinks = src.target.targetLinks;
 			for (var i = 0; i < targetLinks.length; i++) {
 				if (src.sourceId == targetLinks[i].sourceId && src.targetId == targetLinks[i].targetId) {
 					if (layer == "static") {
@@ -1075,8 +1075,8 @@
 				value = edge.value;
 			}
 			edge.value = value;
-			edge.sourceIdx = vertexMap[edge.sourceId].idx;
-			edge.targetIdx = vertexMap[edge.targetId].idx;
+			edge.source = vertexMap[edge.sourceId].idx;
+			edge.target = vertexMap[edge.targetId].idx;
 			i++;
 			if (edge.tags && edge.tags.length > 0) {
 				setAvailableTags(edge.tags);
@@ -1165,10 +1165,10 @@
     	  } else if (d.isZero) {
     		  value = "0";
     	  }
-    	  var sKind = parseOpletKind(d.sourceIdx.invocation.kind);
-    	  var tKind = parseOpletKind(d.targetIdx.invocation.kind);
-    	  var retString = "Oplet name: " + d.sourceIdx.idx + "\nOplet kind: " + sKind + " --> \n"
-    	  + "Oplet name: " + d.targetIdx.idx + "\nOplet kind: " + tKind + "\n" + value;
+    	  var sKind = parseOpletKind(d.source.invocation.kind);
+    	  var tKind = parseOpletKind(d.target.invocation.kind);
+    	  var retString = "Oplet name: " + d.source.idx + "\nOplet kind: " + sKind + " --> \n"
+    	  + "Oplet name: " + d.target.idx + "\nOplet kind: " + tKind + "\n" + value;
 
     	  if (d.alias) {
     		  retString += "\nStream alias: " + d.alias;
@@ -1308,8 +1308,8 @@
 		var sourceStreamAliasesMap = new Map();
 		var sourceStreamTagsMap = new Map();
 		d.targetLinks.forEach(function(trg) {
-			var source = trg.sourceIdx.idx.toString();
-			var sourceLinks = trg.sourceIdx.sourceLinks;
+			var source = trg.source.idx.toString();
+			var sourceLinks = trg.source.sourceLinks;
 			for (var i = 0; i < sourceLinks.length; i++) {
 				if (trg.sourceId == sourceLinks[i].sourceId && trg.targetId == sourceLinks[i].targetId) {
 					if (layer == "static") {
@@ -1337,8 +1337,8 @@
 		var targetStreamAliasesMap = new Map();
 		var targetStreamTagsMap = new Map();
 		d.sourceLinks.forEach(function(src) {
-			var target = src.targetIdx.idx.toString();
-			var targetLinks = src.targetIdx.targetLinks;
+			var target = src.target.idx.toString();
+			var targetLinks = src.target.targetLinks;
 			for (var i = 0; i < targetLinks.length; i++) {
 				if (src.sourceId == targetLinks[i].sourceId && src.targetId == targetLinks[i].targetId) {
 					if (layer == "static") {
diff --git a/console/servlets/webapp_content/js/metrics.js b/console/servlets/src/main/webapp/js/metrics.js
similarity index 99%
rename from console/servlets/webapp_content/js/metrics.js
rename to console/servlets/src/main/webapp/js/metrics.js
index 5c3bd8c..9d3c2e8 100644
--- a/console/servlets/webapp_content/js/metrics.js
+++ b/console/servlets/src/main/webapp/js/metrics.js
@@ -54,7 +54,6 @@
 					  var metrics = op.metrics;
 					  metrics.forEach(function(met) {
 						 obj.type = met.type;
-						 obj.opId = obj.opId;
 						 obj.name = met.name;
 						 obj.value = met.value;
 					  });
diff --git a/console/servlets/webapp_content/js/streamtags.js b/console/servlets/src/main/webapp/js/streamtags.js
similarity index 100%
rename from console/servlets/webapp_content/js/streamtags.js
rename to console/servlets/src/main/webapp/js/streamtags.js
diff --git a/console/servlets/webapp_content/resources/css/main.css b/console/servlets/src/main/webapp/resources/css/main.css
similarity index 98%
rename from console/servlets/webapp_content/resources/css/main.css
rename to console/servlets/src/main/webapp/resources/css/main.css
index 7498ab0..3d281a5 100644
--- a/console/servlets/webapp_content/resources/css/main.css
+++ b/console/servlets/src/main/webapp/resources/css/main.css
@@ -151,8 +151,7 @@
 
 .ui-widget-overlay {
 	opacity: 0.2;
-	background-color: steelblue;
-	background-image: none;
+	background: steelblue none;
 }
 
 #metrics {
diff --git a/console/servlets/webapp_content/resources/css/metrics.css b/console/servlets/src/main/webapp/resources/css/metrics.css
similarity index 100%
rename from console/servlets/webapp_content/resources/css/metrics.css
rename to console/servlets/src/main/webapp/resources/css/metrics.css
diff --git a/console/servlets/webapp_content/resources/images/apache_edgent.png b/console/servlets/src/main/webapp/resources/images/apache_edgent.png
similarity index 100%
rename from console/servlets/webapp_content/resources/images/apache_edgent.png
rename to console/servlets/src/main/webapp/resources/images/apache_edgent.png
Binary files differ
diff --git a/console/servlets/webapp_content/resources/images/favicon.png b/console/servlets/src/main/webapp/resources/images/favicon.png
similarity index 100%
rename from console/servlets/webapp_content/resources/images/favicon.png
rename to console/servlets/src/main/webapp/resources/images/favicon.png
Binary files differ
diff --git a/console/servlets/webapp_content/resources/images/show_metrics_in_table.png b/console/servlets/src/main/webapp/resources/images/show_metrics_in_table.png
similarity index 100%
rename from console/servlets/webapp_content/resources/images/show_metrics_in_table.png
rename to console/servlets/src/main/webapp/resources/images/show_metrics_in_table.png
Binary files differ
diff --git a/console/servlets/webapp_content/resources/images/state.png b/console/servlets/src/main/webapp/resources/images/state.png
similarity index 100%
rename from console/servlets/webapp_content/resources/images/state.png
rename to console/servlets/src/main/webapp/resources/images/state.png
Binary files differ
diff --git a/console/servlets/webapp_content/resources/json/samplegraph.json b/console/servlets/src/main/webapp/resources/json/samplegraph.json
similarity index 100%
rename from console/servlets/webapp_content/resources/json/samplegraph.json
rename to console/servlets/src/main/webapp/resources/json/samplegraph.json
diff --git a/console/servlets/webapp_content/resources/json/samplegraph_health.json b/console/servlets/src/main/webapp/resources/json/samplegraph_health.json
similarity index 100%
rename from console/servlets/webapp_content/resources/json/samplegraph_health.json
rename to console/servlets/src/main/webapp/resources/json/samplegraph_health.json
diff --git a/console/servlets/webapp_content/WEB-INF/console.xml b/console/servlets/webapp_content/WEB-INF/console.xml
deleted file mode 100644
index f152998..0000000
--- a/console/servlets/webapp_content/WEB-INF/console.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<!--
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
- -->
-<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
-         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
-    version="3.1">
-    <servlet>
-        <display-name>Console Application</display-name>
-        <servlet-name>ConsoleServlet</servlet-name>
-        <servlet-class>org.apache.edgent.console.servlets.ConsoleServlet</servlet-class>
-        <load-on-startup>1</load-on-startup>
-    </servlet>
-    <servlet-mapping>
-        <servlet-name>ConsoleServlet</servlet-name>
-        <url-pattern>/console</url-pattern>
-    </servlet-mapping>
-            <servlet>
-   <display-name>Job Listing Service</display-name>
-        <servlet-name>ConsoleJobServlet</servlet-name>
-        <servlet-class>org.apache.edgent.console.servlets.ConsoleJobServlet</servlet-class>
-        <load-on-startup>1</load-on-startup>
-    </servlet>
-    <servlet-mapping>
-        <servlet-name>ConsoleJobServlet</servlet-name>
-        <url-pattern>/jobs</url-pattern>
-    </servlet-mapping>
-    <servlet>
-      	<display-name>Metrics Listing Service</display-name>
-     	<servlet-name>ConsoleMetricsServlet</servlet-name>
-     	<servlet-class>org.apache.edgent.console.servlets.ConsoleMetricsServlet</servlet-class>
-     	<load-on-startup>1</load-on-startup>
-      </servlet>
-     <servlet-mapping>
-      	<servlet-name>ConsoleMetricsServlet</servlet-name>
-      	<url-pattern>/metrics</url-pattern>
-      </servlet-mapping>
-    <welcome-file-list>
-        <welcome-file>index.html</welcome-file>
-        <welcome-file>index.htm</welcome-file>
-    </welcome-file-list>
-</web-app>
\ No newline at end of file
diff --git a/console/servlets/webapp_content/html/index.html b/console/servlets/webapp_content/html/index.html
deleted file mode 100644
index ecef02a..0000000
--- a/console/servlets/webapp_content/html/index.html
+++ /dev/null
@@ -1,128 +0,0 @@
-<!--
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
- -->
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" 
-   "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-us">
-<head>
-<title>Apache Edgent Console</title>
-
-<link rel="stylesheet" type="text/css" href="js/ext/jquery-ui-1.11.4.custom/jquery-ui.min.css">
-<link rel="stylesheet" type="text/css" href="js/ext/jquery-ui-1.11.4.custom/jquery-ui.theme.min.css">
-<link rel="stylesheet" type="text/css" href="js/ext/jquery-ui-1.11.4.custom/jquery-ui.structure.min.css">
-<link rel="stylesheet" type="text/css" href="resources/css/main.css">
-<link rel="stylesheet" type="text/css" href="resources/css/metrics.css">
-<link rel="shortcut icon" href="resources/images/favicon.png" />
-</head>
-
-<body>
-<div role="main">
-<div style='margin-left:15px; float:left;'>
-<img height='81' width='178' tabindex=0 alt="Apache Edgent logo" title="Apache Edgent logo" src="resources/images/apache_edgent.png"/>
-</div>
-<div style='float:left;margin-top: 30px; font-size: 1.2em; margin-left: 320px;'>
-<label tabindex=0>Topology Graph</label>
-</div>
-<div class="topControls" style='margin-left: 30px;clear:both;'>
-<label for="jobs" class="jobsLabel">Job:</label>
-<select id="jobs"></select>
-</div>
-<div id='stateImg' tabindex=0 style="float:left; display:none; margin-top: 10px; margin-left: 5px; margin-right: 10px;">
-<div class="hidden">Job state, press the Enter key to display the job state information in a table</div>
-<img width="28" height="28" alt="job state" src="resources/images/state.png"/>
-</div>
-<div class="topControls">
-<label for="layers" class="layersLabel">View by:</label>
-<select title="view by" id="layers">
-<option value="static">Static flow</option>
-<option value="flow">Tuple count</option>
-<option value="opletColor">Oplet kind</option>
-</select>
-
-<div id="tagsDiv" style="visibility:hidden;margin-top: 5px;">
-	<input type='checkbox' id='showTags' checked/>
-	<label for='showTags'>Show tags:</label>
-	<div>
-		
-		<input type='checkbox' id='showAllTags' checked/>
-		<label for='showAllTags'>Show all tags:</label>
-		<button id="tagDialogBtn" type='button' disabled>Select individual tags ...</button>
-	</div>
-		<div id="dialog" title="Select one or more tags">
-			<label for="tags" class="tagsLabel">Tag:</label>
-			<select id="tags" multiple></select>
-		</div>
-</div>
-</div>
-<div class="topControls">
-<label class="refreshLabel" tabindex=0>Refresh interval:</span>
-<input aria-label='Refresh interval' name='refreshInterval' id="refreshInterval" style='width: 40px;' type="number" min="3" max="20" step="1" value="5"/>
-<label title="seconds" tabindex=0 style='margin-left: 5px; margin-right: 10px;'>seconds</label>
-<button id="toggleTimer" title='Pause graph' type="button">Pause graph</button>
-</div>
-
-<div style='width: 370px; margin-left: 30px; clear:both;'>
-	<div id="showAll" tabindex=0 >
-		<div>View all oplet properties</div>
-		<div style='font-size: 0.95em'>(opens a new window)</div> 
-	</div>
-	<div id="graphLegend" style='height: 600px; width: 340px;'></div>
-</div>
-	<div id="chart" style="float:right; margin-right: 100px; margin-top: -600px;"><div id="loading">Loading graph ...</div></div>
-
-
-<div id="metricsDiv" style="clear:both;display:none;margin-left: 30px;">
-<div>
-<label class="metricsTitle">Metrics</label>
-</div>
-<div class="metricsControls">
-<label for='metrics' class="metricsLabel">Metrics:</label>
-<select id="metrics"></select>
-<span id="rateUnit"></span>
-</div>
-
-<div class="metricsControls">
-<label for='mChartType' class="chartTypeLabel">Chart type:</label>
-<select id="mChartType">
-<option value='barChart' selected>Bar chart</option>
-<option value='lineChart' selected>Line chart</option>
-</select>
-</div>
-
-<div class="metricsControls">
-<span id="noLineChartWarning" style="visibility:hidden;" class="warningLabel">*Line charts are not available when you select a metric containing multiple oplets</span>
-</div>
-<div id="showMetricsTable" tabindex=0 style='clear:left; padding-top: 10px; width: 150px;'>
-<div class='hidden'>Show metrics in table. Press the Enter key to display available metrics in a table. To close the dialog once it is open, press the Escape key.</div>
-<img width="150" height="38" alt='show table metrics' src="resources/images/show_metrics_in_table.png"/>
-</div>
-
-<div id="metricsChart" style="clear:left;"></div>
-</div>
-</div>
-<script src="js/ext/d3.min.js" charset="utf-8"></script>
-<script src="js/ext/jquery-ui-1.11.4.custom/external/jquery/jquery.js"></script>
-<script src="js/ext/jquery-ui-1.11.4.custom/jquery-ui.min.js"></script>
-<script src="js/ext/sankey_edgent.js"></script>
-<script src="js/streamtags.js"></script>
-<script src="js/ext/d3.legend.js"></script>
-<script src="js/graph.js"></script>
-<script src="js/metrics.js"></script>
-
-<script src="js/index.js"></script>
-
-</body>
-</html>
\ No newline at end of file
diff --git a/console/servlets/webapp_content/js/ext/d3.legend.js b/console/servlets/webapp_content/js/ext/d3.legend.js
deleted file mode 100755
index 114546c..0000000
--- a/console/servlets/webapp_content/js/ext/d3.legend.js
+++ /dev/null
@@ -1,191 +0,0 @@
-// d3.legend.js 
-// (C) 2012 ziggy.jonsson.nyc@gmail.com
-// MIT licence
-
-(function() {
-d3.legend = function(g, chartSvg, pItems, legendTitle) {
-  g.each(function() {
-    var g= d3.select(this);
-    var items = {};
-    var svg = !chartSvg ? d3.select(g.property("nearestViewportElement")) : chartSvg;
-    var isTupleFlowLegend = false;
-    var isRect = function(d) {
-        var k = d.key.toUpperCase();
-        return k.startsWith("COUNTEROP")
-             || k.startsWith("STREAMSCOPE");
-        };
-        
-    var	legendPadding = g.attr("data-style-padding") || 5,
-        lTitleItems = g.selectAll(".legend-title-items").data([true]),
-        lb = g.selectAll(".legend-box").data([true]),
-        li = g.selectAll(".legend-items").data([true])
-
-    lTitleItems.enter().append("g").classed("legend-title-items", true)
-    lb.enter().append("rect").classed("legend-box",true)
-    liG = li.enter().append("g").classed("legend-items",true)
-
-    if (pItems) {
-    	pItems.forEach(function(p){
-    		if (p.idx !== undefined) {
-    			items[p.name] = {color: p.fill, idx: p.idx};
-    			isTupleFlowLegend = true;
-    		} else {
-    			items[p.name] = {color: p.fill};
-    			isTupleFlowLegend = false;
-    		}
-    	});
-    } else {
-	    svg.selectAll("[data-legend]").each(function() {
-	        var self = d3.select(this);
-	        items[self.attr("data-legend")] = {
-	          pos : self.attr("data-legend-pos") || this.getBBox().y,
-	          color : self.attr("data-legend-color") != undefined ? self.attr("data-legend-color") : self.style("fill") != 'none' ? self.style("fill") : self.style("stroke") 
-	        }
-	      });
-    }
-
-    if (isTupleFlowLegend)
-	   items = d3.entries(items).sort(
-			   function(a,b) {
-				   if (a.value.idx < b.value.idx) {
-					   return -1;
-				   } else if (a.value.idx > b.value.idx) {
-					   return 1;
-				   } else {
-					   return 0;
-				   }
-			   });
-    else  {
-	    items = d3.entries(items).sort(
-	    		function(a,b) {
-	    		    // rect before circle - graphic positioning code below
-	    		    var ra = isRect(a);
-	    		    var rb = isRect(b);
-	    		    if (ra && !rb) {
-	    		      return -1;
-	    		    } else if (!ra && rb) {
-	    		      return 1;
-	    		    }
-	    			if (a.key < b.key) {
-	    				return -1;
-	    			} else if (a.key > b.key) {
-	    				return 1;
-	    			} else {
-	    				return 0;
-	    			}
-	    });
-    }
-    
-    li.selectAll("text")
-        .data(items,function(d) { 
-        	return d.key}
-        )
-        .call(function(d) { d.enter().append("text")})
-        .call(function(d) { d.exit().remove()})
-        .attr("y",function(d,i) { return i+"em"})
-        .attr("x","1.5em")
-        .text(function(d) {
-        	return d.key;
-        	})
-    
-    var legendOpacity = 0.7;
-    if (legendTitle && legendTitle === "Stream tags") {
-    	legendOpacity = 1.0;
-	    li.selectAll("rect")
-        .data(items,function(d) { 
-        	return d.key}
-        )
-        .call(function(d) { d.enter().append("rect")})
-        .call(function(d) { d.exit().remove()})
-        .attr("y", function(d,i) { 
-        	return i-0.75+ "em"}) 
-        .attr("width", 10)                          
-        .attr("height", 8)
-        .style("fill",function(d) {
-        	return d.value.color === "#c7c7c7" ? "#008080" : d.value.color;
-        	})
-        .style("stroke", "none")
-        .style("fill-opacity", legendOpacity);
-    } else if (legendTitle && legendTitle === "Oplet kind" || legendTitle === "Tuple count"){
-    	liG.selectAll("g")
-    		.data(items, function(d) { 
-    			return d.key;
-    		})
-    		.enter()
-    		.append(function(d) {
-    		    if (isRect(d)) {
-    	  			return document.createElementNS(d3.ns.prefix.svg, 'rect');
-    	  		} else {
-    	  			return document.createElementNS(d3.ns.prefix.svg, 'circle');
-    	  		}
-    		});
-
-        // rects before circles
-    	var count = 0;
-    	li.selectAll("rect")
-    	.attr("x", -3)
-        .attr("y", function(d,i) {
-        	count++;
-        	return i-0.75+ "em"}) 
-        .attr("width", legendTitle === "Oplet kind" ? 8 : 10)                          
-        .attr("height", 8)
-        .style("fill",function(d) {
-        	return d.value.color
-        	})
-        .style("stroke", "none")
-        .style("fill-opacity", legendOpacity);
-    	
-	    li.selectAll("circle")
-        .attr("cy",function(d,i) {
-        	return (i+count)-0.25+"em"})
-        .attr("cx",0)
-        .attr("r","0.4em")
-        .style("fill",function(d) {
-        	return d.value.color
-        	})
-         .style("fill-opacity", legendOpacity);
-    	
-    } else {
-	    li.selectAll("circle")
-	        .data(items,function(d) { 
-	        	return d.key}
-	        )
-	        .call(function(d) { d.enter().append("circle")})
-	        .call(function(d) { d.exit().remove()})
-	        .attr("cy",function(d,i) { return i-0.25+"em"})
-	        .attr("cx",0)
-	        .attr("r","0.4em")
-	        .style("fill",function(d) {
-	        	return d.value.color
-	        	})
-	         .style("fill-opacity", legendOpacity);
-    }
-    // Reposition and resize the box
-    var lbbox = li[0][0].getBBox();
-    lb.attr("x",(lbbox.x-legendPadding))
-        .attr("y",(lbbox.y-legendPadding))
-        .attr("height",(lbbox.height+2*legendPadding))
-        .attr("width",((lbbox.width+12) + 2*legendPadding));
-    
-    lTitleItems.attr("x", 0)
-    	.attr("y", (lbbox.y - legendPadding - 15))
-    	.attr("height",15)
-        .attr("width",(lbbox.width+2*legendPadding));
-        if (legendTitle) {
-	         lTitleItems.selectAll("text")
-	         .data([""],function(d) { 
-	         	return legendTitle;
-	          })
-	         .call(function(d) { d.enter().append("text")})
-	         .call(function(d) { d.exit().remove()})
-	         .attr("y",function(d,i) { return "-2em"})
-	         .attr("x",(lbbox.x-legendPadding))
-	         .text(function(d) { 
-	         	return legendTitle;
-	         	});
-        }
-    	
-  })
-  return g
-}
-})()
diff --git a/console/servlets/webapp_content/js/ext/d3.min.js b/console/servlets/webapp_content/js/ext/d3.min.js
deleted file mode 100755
index e3aa5c6..0000000
--- a/console/servlets/webapp_content/js/ext/d3.min.js
+++ /dev/null
@@ -1,5 +0,0 @@
-!function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:NaN}function r(n){return null===n?NaN:+n}function u(n){return!isNaN(n)}function i(n){return{left:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)<0?r=i+1:u=i}return r},right:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)>0?u=i:r=i+1}return r}}}function a(n){return n.length}function o(n){for(var t=1;n*t%1;)t*=10;return t}function l(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function c(){this._=Object.create(null)}function s(n){return(n+="")===xa||n[0]===ba?ba+n:n}function f(n){return(n+="")[0]===ba?n.slice(1):n}function h(n){return s(n)in this._}function g(n){return(n=s(n))in this._&&delete this._[n]}function p(){var n=[];for(var t in this._)n.push(f(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function m(){this._=Object.create(null)}function y(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=_a.length;r>e;++e){var u=_a[e]+t;if(u in n)return u}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,u=-1,i=r.length;++u<i;)(t=r[u].on)&&t.apply(this,arguments);return n}var e=[],r=new c;return t.on=function(t,u){var i,a=r.get(t);return arguments.length<2?a&&a.on:(a&&(a.on=null,e=e.slice(0,i=e.indexOf(a)).concat(e.slice(i+1)),r.remove(t)),u&&e.push(r.set(t,{on:u})),n)},t}function S(){oa.event.preventDefault()}function k(){for(var n,t=oa.event;n=t.sourceEvent;)t=n;return t}function N(n){for(var t=new _,e=0,r=arguments.length;++e<r;)t[arguments[e]]=w(t);return t.of=function(e,r){return function(u){try{var i=u.sourceEvent=oa.event;u.target=n,oa.event=u,t[u.type].apply(e,r)}finally{oa.event=i}}},t}function E(n){return Sa(n,Aa),n}function A(n){return"function"==typeof n?n:function(){return ka(n,this)}}function C(n){return"function"==typeof n?n:function(){return Na(n,this)}}function z(n,t){function e(){this.removeAttribute(n)}function r(){this.removeAttributeNS(n.space,n.local)}function u(){this.setAttribute(n,t)}function i(){this.setAttributeNS(n.space,n.local,t)}function a(){var e=t.apply(this,arguments);null==e?this.removeAttribute(n):this.setAttribute(n,e)}function o(){var e=t.apply(this,arguments);null==e?this.removeAttributeNS(n.space,n.local):this.setAttributeNS(n.space,n.local,e)}return n=oa.ns.qualify(n),null==t?n.local?r:e:"function"==typeof t?n.local?o:a:n.local?i:u}function L(n){return n.trim().replace(/\s+/g," ")}function q(n){return new RegExp("(?:^|\\s+)"+oa.requote(n)+"(?:\\s+|$)","g")}function T(n){return(n+"").trim().split(/^|\s+/)}function R(n,t){function e(){for(var e=-1;++e<u;)n[e](this,t)}function r(){for(var e=-1,r=t.apply(this,arguments);++e<u;)n[e](this,r)}n=T(n).map(D);var u=n.length;return"function"==typeof t?r:e}function D(n){var t=q(n);return function(e,r){if(u=e.classList)return r?u.add(n):u.remove(n);var u=e.getAttribute("class")||"";r?(t.lastIndex=0,t.test(u)||e.setAttribute("class",L(u+" "+n))):e.setAttribute("class",L(u.replace(t," ")))}}function P(n,t,e){function r(){this.style.removeProperty(n)}function u(){this.style.setProperty(n,t,e)}function i(){var r=t.apply(this,arguments);null==r?this.style.removeProperty(n):this.style.setProperty(n,r,e)}return null==t?r:"function"==typeof t?i:u}function j(n,t){function e(){delete this[n]}function r(){this[n]=t}function u(){var e=t.apply(this,arguments);null==e?delete this[n]:this[n]=e}return null==t?e:"function"==typeof t?u:r}function U(n){function t(){var t=this.ownerDocument,e=this.namespaceURI;return e?t.createElementNS(e,n):t.createElement(n)}function e(){return this.ownerDocument.createElementNS(n.space,n.local)}return"function"==typeof n?n:(n=oa.ns.qualify(n)).local?e:t}function F(){var n=this.parentNode;n&&n.removeChild(this)}function H(n){return{__data__:n}}function O(n){return function(){return Ea(this,n)}}function I(n){return arguments.length||(n=e),function(t,e){return t&&e?n(t.__data__,e.__data__):!t-!e}}function Y(n,t){for(var e=0,r=n.length;r>e;e++)for(var u,i=n[e],a=0,o=i.length;o>a;a++)(u=i[a])&&t(u,a,e);return n}function Z(n){return Sa(n,za),n}function V(n){var t,e;return function(r,u,i){var a,o=n[i].update,l=o.length;for(i!=e&&(e=i,t=0),u>=t&&(t=u+1);!(a=o[t])&&++t<l;);return a}}function X(n,t,e){function r(){var t=this[a];t&&(this.removeEventListener(n,t,t.$),delete this[a])}function u(){var u=l(t,ca(arguments));r.call(this),this.addEventListener(n,this[a]=u,u.$=e),u._=t}function i(){var t,e=new RegExp("^__on([^.]+)"+oa.requote(n)+"$");for(var r in this)if(t=r.match(e)){var u=this[r];this.removeEventListener(t[1],u,u.$),delete this[r]}}var a="__on"+n,o=n.indexOf("."),l=$;o>0&&(n=n.slice(0,o));var c=La.get(n);return c&&(n=c,l=B),o?t?u:r:t?b:i}function $(n,t){return function(e){var r=oa.event;oa.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{oa.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Ta,u="click"+r,i=oa.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==qa&&(qa="onselectstart"in e?!1:x(e.style,"userSelect")),qa){var a=n(e).style,o=a[qa];a[qa]="none"}return function(n){if(i.on(r,null),qa&&(a[qa]=o),n){var t=function(){i.on(u,null)};i.on(u,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var u=r.createSVGPoint();if(0>Ra){var i=t(n);if(i.scrollX||i.scrollY){r=oa.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var a=r[0][0].getScreenCTM();Ra=!(a.f||a.e),r.remove()}}return Ra?(u.x=e.pageX,u.y=e.pageY):(u.x=e.clientX,u.y=e.clientY),u=u.matrixTransform(n.getScreenCTM().inverse()),[u.x,u.y]}var o=n.getBoundingClientRect();return[e.clientX-o.left-n.clientLeft,e.clientY-o.top-n.clientTop]}function G(){return oa.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nn(n){return n>1?0:-1>n?ja:Math.acos(n)}function tn(n){return n>1?Ha:-1>n?-Ha:Math.asin(n)}function en(n){return((n=Math.exp(n))-1/n)/2}function rn(n){return((n=Math.exp(n))+1/n)/2}function un(n){return((n=Math.exp(2*n))-1)/(n+1)}function an(n){return(n=Math.sin(n/2))*n}function on(){}function ln(n,t,e){return this instanceof ln?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ln?new ln(n.h,n.s,n.l):_n(""+n,wn,ln):new ln(n,t,e)}function cn(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?i+(a-i)*n/60:180>n?a:240>n?i+(a-i)*(240-n)/60:i}function u(n){return Math.round(255*r(n))}var i,a;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,a=.5>=e?e*(1+t):e+t-e*t,i=2*e-a,new yn(u(n+120),u(n),u(n-120))}function sn(n,t,e){return this instanceof sn?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof sn?new sn(n.h,n.c,n.l):n instanceof hn?pn(n.l,n.a,n.b):pn((n=Sn((n=oa.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new sn(n,t,e)}function fn(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new hn(e,Math.cos(n*=Oa)*t,Math.sin(n)*t)}function hn(n,t,e){return this instanceof hn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof hn?new hn(n.l,n.a,n.b):n instanceof sn?fn(n.h,n.c,n.l):Sn((n=yn(n)).r,n.g,n.b):new hn(n,t,e)}function gn(n,t,e){var r=(n+16)/116,u=r+t/500,i=r-e/200;return u=vn(u)*Ka,r=vn(r)*Qa,i=vn(i)*no,new yn(mn(3.2404542*u-1.5371385*r-.4985314*i),mn(-.969266*u+1.8760108*r+.041556*i),mn(.0556434*u-.2040259*r+1.0572252*i))}function pn(n,t,e){return n>0?new sn(Math.atan2(e,t)*Ia,Math.sqrt(t*t+e*e),n):new sn(NaN,NaN,n)}function vn(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function dn(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function mn(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function yn(n,t,e){return this instanceof yn?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof yn?new yn(n.r,n.g,n.b):_n(""+n,yn,cn):new yn(n,t,e)}function Mn(n){return new yn(n>>16,n>>8&255,255&n)}function xn(n){return Mn(n)+""}function bn(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function _n(n,t,e){var r,u,i,a=0,o=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(u=r[2].split(","),r[1]){case"hsl":return e(parseFloat(u[0]),parseFloat(u[1])/100,parseFloat(u[2])/100);case"rgb":return t(Nn(u[0]),Nn(u[1]),Nn(u[2]))}return(i=ro.get(n))?t(i.r,i.g,i.b):(null==n||"#"!==n.charAt(0)||isNaN(i=parseInt(n.slice(1),16))||(4===n.length?(a=(3840&i)>>4,a=a>>4|a,o=240&i,o=o>>4|o,l=15&i,l=l<<4|l):7===n.length&&(a=(16711680&i)>>16,o=(65280&i)>>8,l=255&i)),t(a,o,l))}function wn(n,t,e){var r,u,i=Math.min(n/=255,t/=255,e/=255),a=Math.max(n,t,e),o=a-i,l=(a+i)/2;return o?(u=.5>l?o/(a+i):o/(2-a-i),r=n==a?(t-e)/o+(e>t?6:0):t==a?(e-n)/o+2:(n-t)/o+4,r*=60):(r=NaN,u=l>0&&1>l?0:r),new ln(r,u,l)}function Sn(n,t,e){n=kn(n),t=kn(t),e=kn(e);var r=dn((.4124564*n+.3575761*t+.1804375*e)/Ka),u=dn((.2126729*n+.7151522*t+.072175*e)/Qa),i=dn((.0193339*n+.119192*t+.9503041*e)/no);return hn(116*u-16,500*(r-u),200*(u-i))}function kn(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function Nn(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function En(n){return"function"==typeof n?n:function(){return n}}function An(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Cn(t,e,n,r)}}function Cn(n,t,e,r){function u(){var n,t=l.status;if(!t&&Ln(l)||t>=200&&300>t||304===t){try{n=e.call(i,l)}catch(r){return void a.error.call(i,r)}a.load.call(i,n)}else a.error.call(i,l)}var i={},a=oa.dispatch("beforesend","progress","load","error"),o={},l=new XMLHttpRequest,c=null;return!this.XDomainRequest||"withCredentials"in l||!/^(http(s)?:)?\/\//.test(n)||(l=new XDomainRequest),"onload"in l?l.onload=l.onerror=u:l.onreadystatechange=function(){l.readyState>3&&u()},l.onprogress=function(n){var t=oa.event;oa.event=n;try{a.progress.call(i,l)}finally{oa.event=t}},i.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?o[n]:(null==t?delete o[n]:o[n]=t+"",i)},i.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",i):t},i.responseType=function(n){return arguments.length?(c=n,i):c},i.response=function(n){return e=n,i},["get","post"].forEach(function(n){i[n]=function(){return i.send.apply(i,[n].concat(ca(arguments)))}}),i.send=function(e,r,u){if(2===arguments.length&&"function"==typeof r&&(u=r,r=null),l.open(e,n,!0),null==t||"accept"in o||(o.accept=t+",*/*"),l.setRequestHeader)for(var s in o)l.setRequestHeader(s,o[s]);return null!=t&&l.overrideMimeType&&l.overrideMimeType(t),null!=c&&(l.responseType=c),null!=u&&i.on("error",u).on("load",function(n){u(null,n)}),a.beforesend.call(i,l),l.send(null==r?null:r),i},i.abort=function(){return l.abort(),i},oa.rebind(i,a,"on"),null==r?i:i.get(zn(r))}function zn(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Ln(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qn(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var u=e+t,i={c:n,t:u,n:null};return io?io.n=i:uo=i,io=i,ao||(oo=clearTimeout(oo),ao=1,lo(Tn)),i}function Tn(){var n=Rn(),t=Dn()-n;t>24?(isFinite(t)&&(clearTimeout(oo),oo=setTimeout(Tn,t)),ao=0):(ao=1,lo(Tn))}function Rn(){for(var n=Date.now(),t=uo;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Dn(){for(var n,t=uo,e=1/0;t;)t.c?(t.t<e&&(e=t.t),t=(n=t).n):t=n?n.n=t.n:uo=t.n;return io=n,e}function Pn(n,t){return t-(n?Math.ceil(Math.log(n)/Math.LN10):1)}function jn(n,t){var e=Math.pow(10,3*Ma(8-t));return{scale:t>8?function(n){return n/e}:function(n){return n*e},symbol:n}}function Un(n){var t=n.decimal,e=n.thousands,r=n.grouping,u=n.currency,i=r&&e?function(n,t){for(var u=n.length,i=[],a=0,o=r[0],l=0;u>0&&o>0&&(l+o+1>t&&(o=Math.max(1,t-l)),i.push(n.substring(u-=o,u+o)),!((l+=o+1)>t));)o=r[a=(a+1)%r.length];return i.reverse().join(e)}:y;return function(n){var e=so.exec(n),r=e[1]||" ",a=e[2]||">",o=e[3]||"-",l=e[4]||"",c=e[5],s=+e[6],f=e[7],h=e[8],g=e[9],p=1,v="",d="",m=!1,y=!0;switch(h&&(h=+h.substring(1)),(c||"0"===r&&"="===a)&&(c=r="0",a="="),g){case"n":f=!0,g="g";break;case"%":p=100,d="%",g="f";break;case"p":p=100,d="%",g="r";break;case"b":case"o":case"x":case"X":"#"===l&&(v="0"+g.toLowerCase());case"c":y=!1;case"d":m=!0,h=0;break;case"s":p=-1,g="r"}"$"===l&&(v=u[0],d=u[1]),"r"!=g||h||(g="g"),null!=h&&("g"==g?h=Math.max(1,Math.min(21,h)):("e"==g||"f"==g)&&(h=Math.max(0,Math.min(20,h)))),g=fo.get(g)||Fn;var M=c&&f;return function(n){var e=d;if(m&&n%1)return"";var u=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===o?"":o;if(0>p){var l=oa.formatPrefix(n,h);n=l.scale(n),e=l.symbol+d}else n*=p;n=g(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=y?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!c&&f&&(x=i(x,1/0));var S=v.length+x.length+b.length+(M?0:u.length),k=s>S?new Array(S=s-S+1).join(r):"";return M&&(x=i(k+x,k.length?s-b.length:1/0)),u+=v,n=x+b,("<"===a?u+n+k:">"===a?k+u+n:"^"===a?k.substring(0,S>>=1)+u+n+k.substring(S):u+(M?n:k+n))+e}}}function Fn(n){return n+""}function Hn(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function On(n,t,e){function r(t){var e=n(t),r=i(e,1);return r-t>t-e?e:r}function u(e){return t(e=n(new go(e-1)),1),e}function i(n,e){return t(n=new go(+n),e),n}function a(n,r,i){var a=u(n),o=[];if(i>1)for(;r>a;)e(a)%i||o.push(new Date(+a)),t(a,1);else for(;r>a;)o.push(new Date(+a)),t(a,1);return o}function o(n,t,e){try{go=Hn;var r=new Hn;return r._=n,a(r,t,e)}finally{go=Date}}n.floor=n,n.round=r,n.ceil=u,n.offset=i,n.range=a;var l=n.utc=In(n);return l.floor=l,l.round=In(r),l.ceil=In(u),l.offset=In(i),l.range=o,n}function In(n){return function(t,e){try{go=Hn;var r=new Hn;return r._=t,n(r,e)._}finally{go=Date}}}function Yn(n){function t(n){function t(t){for(var e,u,i,a=[],o=-1,l=0;++o<r;)37===n.charCodeAt(o)&&(a.push(n.slice(l,o)),null!=(u=vo[e=n.charAt(++o)])&&(e=n.charAt(++o)),(i=A[e])&&(e=i(t,null==u?"e"===e?" ":"0":u)),a.push(e),l=o+1);return a.push(n.slice(l,o)),a.join("")}var r=n.length;return t.parse=function(t){var r={y:1900,m:0,d:1,H:0,M:0,S:0,L:0,Z:null},u=e(r,n,t,0);if(u!=t.length)return null;"p"in r&&(r.H=r.H%12+12*r.p);var i=null!=r.Z&&go!==Hn,a=new(i?Hn:go);return"j"in r?a.setFullYear(r.y,0,r.j):"W"in r||"U"in r?("w"in r||(r.w="W"in r?1:0),a.setFullYear(r.y,0,1),a.setFullYear(r.y,0,"W"in r?(r.w+6)%7+7*r.W-(a.getDay()+5)%7:r.w+7*r.U-(a.getDay()+6)%7)):a.setFullYear(r.y,r.m,r.d),a.setHours(r.H+(r.Z/100|0),r.M+r.Z%100,r.S,r.L),i?a._:a},t.toString=function(){return n},t}function e(n,t,e,r){for(var u,i,a,o=0,l=t.length,c=e.length;l>o;){if(r>=c)return-1;if(u=t.charCodeAt(o++),37===u){if(a=t.charAt(o++),i=C[a in vo?t.charAt(o++):a],!i||(r=i(n,e,r))<0)return-1}else if(u!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){N.lastIndex=0;var r=N.exec(t.slice(e));return r?(n.m=E.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,r){return e(n,A.c.toString(),t,r)}function l(n,t,r){return e(n,A.x.toString(),t,r)}function c(n,t,r){return e(n,A.X.toString(),t,r)}function s(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var f=n.dateTime,h=n.date,g=n.time,p=n.periods,v=n.days,d=n.shortDays,m=n.months,y=n.shortMonths;t.utc=function(n){function e(n){try{go=Hn;var t=new go;return t._=n,r(t)}finally{go=Date}}var r=t(n);return e.parse=function(n){try{go=Hn;var t=r.parse(n);return t&&t._}finally{go=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ct;var M=oa.map(),x=Vn(v),b=Xn(v),_=Vn(d),w=Xn(d),S=Vn(m),k=Xn(m),N=Vn(y),E=Xn(y);p.forEach(function(n,t){M.set(n.toLowerCase(),t)});var A={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return y[n.getMonth()]},B:function(n){return m[n.getMonth()]},c:t(f),d:function(n,t){return Zn(n.getDate(),t,2)},e:function(n,t){return Zn(n.getDate(),t,2)},H:function(n,t){return Zn(n.getHours(),t,2)},I:function(n,t){return Zn(n.getHours()%12||12,t,2)},j:function(n,t){return Zn(1+ho.dayOfYear(n),t,