You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@edgent.apache.org by dl...@apache.org on 2016/07/21 13:17:17 UTC

[16/54] [abbrv] [partial] incubator-quarks git commit: add "org.apache." prefix to edgent package names

http://git-wip-us.apache.org/repos/asf/incubator-quarks/blob/a129aa16/connectors/wsclient-javax.websocket/src/test/java/org/apache/edgent/tests/connectors/wsclient/javax/websocket/WebSocketClientTest.java
----------------------------------------------------------------------
diff --git a/connectors/wsclient-javax.websocket/src/test/java/org/apache/edgent/tests/connectors/wsclient/javax/websocket/WebSocketClientTest.java b/connectors/wsclient-javax.websocket/src/test/java/org/apache/edgent/tests/connectors/wsclient/javax/websocket/WebSocketClientTest.java
new file mode 100644
index 0000000..9d657ab
--- /dev/null
+++ b/connectors/wsclient-javax.websocket/src/test/java/org/apache/edgent/tests/connectors/wsclient/javax/websocket/WebSocketClientTest.java
@@ -0,0 +1,856 @@
+/*
+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.tests.connectors.wsclient.javax.websocket;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assume.assumeTrue;
+
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.edgent.connectors.wsclient.WebSocketClient;
+import org.apache.edgent.connectors.wsclient.javax.websocket.Jsr356WebSocketClient;
+import org.apache.edgent.test.connectors.common.ConnectorTestBase;
+import org.apache.edgent.test.connectors.common.TestRepoPath;
+import org.apache.edgent.topology.TSink;
+import org.apache.edgent.topology.TStream;
+import org.apache.edgent.topology.Topology;
+import org.apache.edgent.topology.json.JsonFunctions;
+import org.apache.edgent.topology.plumbing.PlumbingStreams;
+import org.junit.After;
+import org.junit.Test;
+
+import com.google.gson.JsonObject;
+
+public class WebSocketClientTest extends ConnectorTestBase {
+    private final static int SEC_TMO = 5;
+    WebSocketServerEcho wsServer;
+    boolean isExternalServer;// = true;
+    int wsServerPort = !isExternalServer ? 0 : 49460;
+    String wsUriPath = "/echo";  // match what WsServerEcho is using
+    private final static String str1 = "one";
+    private final static String str2 = "two";
+    private final static String str3 = "three-post-reconnect";
+    private final static String str4 = "four";
+    
+    public String getStr1() {
+        return str1;
+    }
+
+    public String getStr2() {
+        return str2;
+    }
+
+    public String getStr3() {
+        return str3;
+    }
+
+    public String getStr4() {
+        return str4;
+    }
+
+    @After
+    public void cleanup() {
+        if (wsServer != null)
+            wsServer.stop();
+        wsServer = null;
+    }
+    
+    private enum ServerMode { WS, SSL, SSL_CLIENT_AUTH }
+    private void startEchoer() {
+        startEchoer(ServerMode.WS);
+    }
+    private void startEchoer(ServerMode mode) {
+        try {
+            if (!isExternalServer) {
+                URI uri;
+                if (mode==ServerMode.WS) {
+                    uri = new URI("ws://localhost:0");
+                    wsServer = new WebSocketServerEcho();
+                }
+                else {
+                    uri = new URI("wss://localhost:0");
+                    wsServer = new WebSocketServerEcho();
+                }
+                wsServer.start(uri, mode==ServerMode.SSL_CLIENT_AUTH);
+                wsServerPort = wsServer.getPort();
+            }
+        } catch (Exception e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+            throw new RuntimeException("startEchoer",e );
+        }
+    }
+    private void restartEchoer(int secDelay) {
+        wsServer.restart(secDelay);
+    }
+    
+    Properties getConfig() {
+        return getWsConfig();
+    }
+
+    Properties getWsConfig() {
+        Properties config = new Properties();
+        config.setProperty("ws.uri", getWsUri());
+        return config;
+    }
+
+    Properties getWssConfig() {
+        Properties config = new Properties();
+        config.setProperty("ws.uri", getWssUri());
+        config.setProperty("ws.trustStore", getStorePath("clientTrustStore.jks"));
+        config.setProperty("ws.trustStorePassword", "passw0rd");
+        config.setProperty("ws.keyStore", getStorePath("clientKeyStore.jks"));
+        config.setProperty("ws.keyStorePassword", "passw0rd");
+        // default: expect key to have the default alias
+        return config;
+    }
+    
+    String getWsUri() {
+        int port = wsServerPort==0 ? 8080 : wsServerPort;
+        return "ws://localhost:"+port+wsUriPath;
+    }
+    
+    String getWssUri() {
+        int port = wsServerPort==0 ? 443 : wsServerPort;
+        return "wss://localhost:"+port+wsUriPath;
+    }
+    
+    private String getStorePath(String storeLeaf) {
+        return TestRepoPath.getPath("connectors", "wsclient-javax.websocket", "src", "test", "keystores", storeLeaf);
+    }
+
+    @Test
+    public void testBasicStaticStuff() {
+        Topology t = newTopology("testBasicStaticStuff");
+
+        Properties config = getConfig();
+        WebSocketClient wsClient1 = new Jsr356WebSocketClient(t, config);
+        
+        TStream<String> s1 = wsClient1.receiveString();
+        assertNotNull("s1", s1);
+        
+        TSink<String> sink1 = wsClient1.sendString(t.strings(getStr1(), getStr2()));
+        assertNotNull("sink1", sink1);
+        
+        WebSocketClient wsClient2 = new Jsr356WebSocketClient(t, config);
+        TStream<String> s2 = wsClient2.receiveString();
+        assertNotSame("s1 s2", s1, s2);
+        
+        TSink<String> sink2 = wsClient2.sendString(t.strings(getStr1(), getStr2()));
+        assertNotSame("sink1 sink2", sink1, sink2);        
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void testMissingWsUri() {
+        Topology t = newTopology("testMissingWsUri");
+        new Jsr356WebSocketClient(t, new Properties());
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void testMalformedWsUri() {
+        Topology t = newTopology("testMalformedWsUri");
+        Properties config = new Properties();
+        config.setProperty("ws.uri", "localhost"); // missing scheme
+        new Jsr356WebSocketClient(t, config);
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void testNotWsUri() {
+        Topology t = newTopology("testNotWsUri");
+        Properties config = new Properties();
+        config.setProperty("ws.uri", "tcp://localhost");
+        new Jsr356WebSocketClient(t, config);
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void testWssTrustStorePasswordNeg() {
+        Topology t = newTopology("testWssTrustStorePasswordNeg");
+        Properties config = new Properties();
+        config.setProperty("ws.uri", getWssUri());
+        config.setProperty("ws.trustStore", "xyzzy"); // not checked till runtime
+        // missing trustStorePassword
+        new Jsr356WebSocketClient(t, config);
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void testWssKeyStorePasswordNeg() {
+        Topology t = newTopology("testWssKeyStorePasswordNeg");
+        Properties config = new Properties();
+        config.setProperty("ws.uri", getWssUri());
+        config.setProperty("ws.keyStore", "xyzzy"); // not checked till runtime
+        // missing keyStorePassword
+        new Jsr356WebSocketClient(t, config);
+    }
+    
+    @Test
+    public void testWssConfig() {
+        Topology t = newTopology("testWssConfig");
+        Properties config = new Properties();
+        config.setProperty("ws.uri", getWssUri());
+        config.setProperty("ws.trustStore", "xyzzy"); // not checked till runtime
+        config.setProperty("ws.trustStorePassword", "xyzzy"); // not checked till runtime
+        new Jsr356WebSocketClient(t, config);
+    }
+    
+    @Test(expected = IllegalStateException.class)
+    public void testTooManySendersNeg() {
+        Topology t = newTopology("testTooManySendersNeg");
+        TStream<String> s1 = t.strings(getStr1(), getStr2());
+        TStream<String> s2 = t.strings(getStr1(), getStr2());
+
+        Properties config = getConfig();
+        WebSocketClient wsClient = new Jsr356WebSocketClient(t, config);
+        wsClient.sendString(s1);
+        wsClient.sendString(s2); // should throw
+    }
+    
+    @Test(expected = IllegalStateException.class)
+    public void testTooManyReceiversNeg() {
+        Topology t = newTopology("testTooManyReceiversNeg");
+
+        Properties config = getConfig();
+        WebSocketClient wsClient = new Jsr356WebSocketClient(t, config);
+        @SuppressWarnings("unused")
+        TStream<String> s1 = wsClient.receiveString();
+        @SuppressWarnings("unused")
+        TStream<String> s2 = wsClient.receiveString(); // should throw
+    }
+    
+    @Test
+    public void testJson() throws Exception {
+        Topology t = newTopology("testJson");
+        System.out.println("===== "+t.getName());
+        
+        startEchoer();  // before getConfig() so it gets the port
+        
+        Properties config = getConfig();
+        WebSocketClient wsClient = new Jsr356WebSocketClient(t, config);
+        
+        String[] expected = new String[] {
+                "{\"id\":\"" + getStr1() + "\",\"value\":27}",
+                "{\"id\":\"" + getStr2() + "\",\"value\":13}"
+        };
+        
+        TStream<JsonObject> s = t.strings(expected)
+                                .map(JsonFunctions.fromString());
+        s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
+        wsClient.send(s);
+        
+        TStream<String> rcvd = wsClient.receive()
+                                .map(JsonFunctions.asString());
+        
+        completeAndValidate("", t, rcvd, SEC_TMO, expected);
+    }
+    
+    @Test
+    public void testString() throws Exception {
+        Topology t = newTopology("testString");
+        System.out.println("===== "+t.getName());
+        
+        startEchoer();  // before getConfig() so it gets the port
+        
+        Properties config = getConfig();
+        WebSocketClient wsClient = new Jsr356WebSocketClient(t, config);
+        
+        String[] expected = new String[] { getStr1(), getStr2() };
+        
+        TStream<String> s = t.strings(expected);
+        s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
+        wsClient.sendString(s);
+        
+        TStream<String> rcvd = wsClient.receiveString();
+        
+        completeAndValidate("", t, rcvd, SEC_TMO, expected);
+    }
+    
+    @Test
+    public void testBytes() throws Exception {
+        Topology t = newTopology("testBytes");
+        System.out.println("===== "+t.getName());
+
+        startEchoer();  // before getConfig() so it gets the port
+        
+        Properties config = getConfig();
+        WebSocketClient wsClient = new Jsr356WebSocketClient(t, config);
+        
+        String[] expected = new String[] { getStr1(), getStr2() };
+        
+        TStream<byte[]> s = t.strings(expected)
+                                .map(tup -> tup.getBytes());
+        s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
+        wsClient.sendBytes(s);
+        
+        TStream<String> rcvd = wsClient.receiveBytes()
+                                .map(tup -> new String(tup));
+        
+        completeAndValidate("", t, rcvd, SEC_TMO, expected);
+    }
+    
+    @Test
+    public void testReconnect() throws Exception {
+        /*
+         * It's becomming apparent that the reconnect series of tests
+         * aren't reliable so skip them for ci. See jira EDGENT-122 for
+         * more info.
+         */
+        assumeTrue(!Boolean.getBoolean("edgent.build.ci"));
+
+        Topology t = newTopology("testReconnect");
+        System.out.println("===== "+t.getName());
+
+        startEchoer();  // before getConfig() so it gets the port
+
+        Properties config = getConfig();
+        WebSocketClient wsClient = new Jsr356WebSocketClient(t, config);
+        
+        String[] expected = new String[] { getStr1(), getStr2(), getStr3(), getStr4() };
+        
+        TStream<String> s = t.strings(expected);
+        s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
+        
+        // send one, two, restart the server to force reconnect, send the next
+        AtomicInteger numSent = new AtomicInteger();
+        int restartAfterTupleCnt = 2;
+        CountDownLatch latch = new CountDownLatch(restartAfterTupleCnt);
+        s = s.filter(tuple -> {
+            if (numSent.getAndIncrement() != restartAfterTupleCnt )
+                return true;
+            else {
+                // to keep validation sane/simple wait till the tuples are rcvd before restarting
+                try { latch.await(); } catch (Exception e) {};
+                restartEchoer(2/*secDelay*/);
+                return true;
+            }
+        });
+        wsClient.sendString(s);
+        
+        TStream<String> rcvd = wsClient.receiveString()
+                                    .peek(tuple -> latch.countDown());
+
+        
+        completeAndValidate("", t, rcvd, SEC_TMO + 10, expected);
+    }
+    
+    @Test
+    public void testReconnectBytes() throws Exception {
+        /*
+         * It's becomming apparent that the reconnect series of tests
+         * aren't reliable so skip them for ci. See jira EDGENT-122 for
+         * more info.
+         */
+        assumeTrue(!Boolean.getBoolean("edgent.build.ci"));
+
+        Topology t = newTopology("testReconnectBytes");
+        System.out.println("===== "+t.getName());
+
+        startEchoer();  // before getConfig() so it gets the port
+
+        Properties config = getConfig();
+        WebSocketClient wsClient = new Jsr356WebSocketClient(t, config);
+        
+        String[] expected = new String[] { getStr1(), getStr2(), getStr3(), getStr4() };
+        
+        TStream<byte[]> s = t.strings(expected).map(tup -> tup.getBytes());
+        s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
+        
+        // send one, two, restart the server to force reconnect, send the next
+        AtomicInteger numSent = new AtomicInteger();
+        int restartAfterTupleCnt = 2;
+        CountDownLatch latch = new CountDownLatch(restartAfterTupleCnt);
+        s = s.filter(tuple -> {
+            if (numSent.getAndIncrement() != restartAfterTupleCnt )
+                return true;
+            else {
+                // to keep validation sane/simple wait till the tuples are rcvd before restarting
+                try { latch.await(); } catch (Exception e) {};
+                restartEchoer(2/*secDelay*/);
+                return true;
+            }
+        });
+        wsClient.sendBytes(s);
+        
+        TStream<String> rcvd = wsClient.receiveBytes()
+                                .peek(tuple -> latch.countDown())
+                                .map(tup -> new String(tup));
+        
+        completeAndValidate("", t, rcvd, SEC_TMO + 10, expected);
+    }
+
+    private class SslSystemPropMgr {
+        private final Map<String,String> origProps = new HashMap<>();
+        
+        public void set() {
+            set("javax.net.ssl.trustStore", getStorePath("clientTrustStore.jks"));
+            set("javax.net.ssl.trustStorePassword", "passw0rd");
+            set("javax.net.ssl.keyStore", getStorePath("clientKeyStore.jks"));
+            set("javax.net.ssl.keyStorePassword", "passw0rd");
+        }
+        
+        private void set(String prop, String defaultVal) {
+            origProps.put(prop, System.setProperty(prop, defaultVal));
+        }
+        
+        public void restore() {
+            restore("javax.net.ssl.trustStore");
+            restore("javax.net.ssl.trustStorePassword");
+            restore("javax.net.ssl.keyStore");
+            restore("javax.net.ssl.keyStorePassword");
+        }
+        
+        private void restore(String prop) {
+            String origValue = origProps.get(prop);
+            if (origValue == null)
+                System.getProperties().remove(prop);
+            else
+                System.setProperty(prop, origValue);
+        }
+    }
+    
+    @Test
+    public void testSslSystemProperty() throws Exception {
+        Topology t = newTopology("testSslSystemProperty");
+        System.out.println("===== "+t.getName());
+        
+        startEchoer(ServerMode.SSL);  // before getConfig() so it gets the port
+        
+        Properties config = getConfig();  // no SSL config stuff
+        config.setProperty("ws.uri", getWssUri());
+
+        SslSystemPropMgr sslProps = new SslSystemPropMgr();
+        try {
+            // a trust store that contains the server's cert
+            sslProps.set();
+    
+            // System.setProperty("javax.net.debug", "ssl"); // or "all"; "help" for full list
+            
+            WebSocketClient wsClient = new Jsr356WebSocketClient(t, config);
+            
+            String[] expected = new String[] { getStr1(), getStr2() };
+            
+            TStream<String> s = t.strings(expected);
+            s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
+            wsClient.sendString(s);
+            
+            TStream<String> rcvd = wsClient.receiveString();
+            
+            completeAndValidate("", t, rcvd, SEC_TMO, expected);
+        }
+        finally {
+            sslProps.restore();
+        }
+    }
+    
+    @Test
+    public void testSslClientAuthSystemProperty() throws Exception {
+        Topology t = newTopology("testSslClientAuthSystemProperty");
+        System.out.println("===== "+t.getName());
+        
+        startEchoer(ServerMode.SSL_CLIENT_AUTH);  // before getConfig() so it gets the port
+        
+        Properties config = getConfig();  // no SSL config stuff
+        config.setProperty("ws.uri", getWssUri());
+
+        SslSystemPropMgr sslProps = new SslSystemPropMgr();
+        try {
+            // a trust store that contains the server's cert
+            sslProps.set();
+    
+            // System.setProperty("javax.net.debug", "ssl"); // or "all"; "help" for full list
+            
+            WebSocketClient wsClient = new Jsr356WebSocketClient(t, config);
+            
+            String[] expected = new String[] { getStr1(), getStr2() };
+            
+            TStream<String> s = t.strings(expected);
+            s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
+            wsClient.sendString(s);
+            
+            TStream<String> rcvd = wsClient.receiveString();
+            
+            completeAndValidate("", t, rcvd, SEC_TMO, expected);
+        }
+        finally {
+            sslProps.restore();
+        }
+    }
+    
+    @Test
+    public void testSsl() throws Exception {
+
+        Topology t = newTopology("testSsl");
+        System.out.println("===== "+t.getName());
+
+        startEchoer(ServerMode.SSL);  // before getConfig() so it gets the port
+        
+        Properties config = getWssConfig();
+
+        // System.setProperty("javax.net.debug", "ssl"); // or "all"; "help" for full list
+        
+        WebSocketClient wsClient = new Jsr356WebSocketClient(t, config);
+        
+        String[] expected = new String[] { getStr1(), getStr2() };
+        
+        TStream<String> s = t.strings(expected);
+        s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
+        wsClient.sendString(s);
+        
+        TStream<String> rcvd = wsClient.receiveString();
+        
+        completeAndValidate("", t, rcvd, SEC_TMO, expected);
+    }
+    
+     @Test
+     public void testSslReconnect() throws Exception {
+         /*
+          * It's becomming apparent that the reconnect series of tests
+          * aren't reliable so skip them for ci. See jira EDGENT-122 for
+          * more info.
+          */
+         assumeTrue(!Boolean.getBoolean("edgent.build.ci"));
+    
+         Topology t = newTopology("testSslReconnect");
+         System.out.println("===== "+t.getName());
+    
+         startEchoer(ServerMode.SSL);  // before getConfig() so it gets the port
+    
+         Properties config = getWssConfig();
+         WebSocketClient wsClient = new Jsr356WebSocketClient(t, config);
+         
+         String[] expected = new String[] { getStr1(), getStr2(), getStr3(), getStr4() };
+         
+         TStream<String> s = t.strings(expected);
+         s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
+         
+         // send one, two, restart the server to force reconnect, send the next
+         AtomicInteger numSent = new AtomicInteger();
+         int restartAfterTupleCnt = 2;
+         CountDownLatch latch = new CountDownLatch(restartAfterTupleCnt);
+         s = s.filter(tuple -> {
+             if (numSent.getAndIncrement() != restartAfterTupleCnt )
+                 return true;
+             else {
+                 // to keep validation sane/simple wait till the tuples are rcvd before restarting
+                 try { latch.await(); } catch (Exception e) {};
+                 restartEchoer(2/*secDelay*/);
+                 return true;
+             }
+         });
+         wsClient.sendString(s);
+         
+         TStream<String> rcvd = wsClient.receiveString()
+                                 .peek(tuple -> latch.countDown());
+         
+         completeAndValidate("", t, rcvd, SEC_TMO + 10, expected);
+     }
+    
+    @Test
+    public void testSslNeg() throws Exception {
+
+        Topology t = newTopology("testSslNeg");
+        System.out.println("===== "+t.getName());
+
+        startEchoer(ServerMode.SSL);  // before getConfig() so it gets the port
+        
+        // since our server uses a self-signed cert, if we don't have
+        // a truststore setup with it in it, the client will fail to connect
+        // and ultimately the connect will fail and the test will
+        // receive nothing.
+
+        Properties config = getConfig();  // no SSL config stuff
+        config.setProperty("ws.uri", getWssUri());
+
+        // System.setProperty("javax.net.debug", "ssl"); // or "all"; "help" for full list
+        
+        WebSocketClient wsClient = new Jsr356WebSocketClient(t, config);
+        
+        String[] expected = new String[] { getStr1(), getStr2() };
+        
+        TStream<String> s = t.strings(expected);
+        s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
+        wsClient.sendString(s);
+        
+        TStream<String> rcvd = wsClient.receiveString();
+        
+        completeAndValidate("", t, rcvd, SEC_TMO, new String[0]);  //rcv nothing
+    }
+    
+    @Test
+    public void testSslClientAuth() throws Exception {
+
+        Topology t = newTopology("testSslClientAuth");
+        System.out.println("===== "+t.getName());
+
+        startEchoer(ServerMode.SSL_CLIENT_AUTH);  // before getConfig() so it gets the port
+        
+        Properties config = getWssConfig();
+
+        // System.setProperty("javax.net.debug", "ssl"); // or "all"; "help" for full list
+        
+        WebSocketClient wsClient = new Jsr356WebSocketClient(t, config);
+        
+        String[] expected = new String[] { getStr1(), getStr2() };
+        
+        TStream<String> s = t.strings(expected);
+        s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
+        wsClient.sendString(s);
+        
+        TStream<String> rcvd = wsClient.receiveString();
+        
+        completeAndValidate("", t, rcvd, SEC_TMO, expected);
+    }
+    
+    @Test
+    public void testSslClientAuthDefault() throws Exception {
+
+        Topology t = newTopology("testSslClientAuthDefault");
+        System.out.println("===== "+t.getName());
+
+        startEchoer(ServerMode.SSL_CLIENT_AUTH);  // before getConfig() so it gets the port
+        
+        // explicitly specify client's "default" certificate
+        Properties config = getWssConfig();
+        config.setProperty("ws.keyCertificateAlias", "default");
+
+        // System.setProperty("javax.net.debug", "ssl"); // or "all"; "help" for full list
+        
+        WebSocketClient wsClient = new Jsr356WebSocketClient(t, config);
+        
+        String[] expected = new String[] { getStr1(), getStr2() };
+        
+        TStream<String> s = t.strings(expected);
+        s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
+        wsClient.sendString(s);
+        
+        TStream<String> rcvd = wsClient.receiveString();
+        
+        completeAndValidate("", t, rcvd, SEC_TMO, expected);
+    }
+    
+    @Test
+    public void testSslClientAuthMy2ndCertNeg() throws Exception {
+
+        Topology t = newTopology("testSslClientAuthMy2ndCertNeg");
+        System.out.println("===== "+t.getName());
+
+        startEchoer(ServerMode.SSL_CLIENT_AUTH);  // before getConfig() so it gets the port
+        
+        // explicitly specify client's "my2ndcert" certificate - unknown to server
+        Properties config = getWssConfig();
+        config.setProperty("ws.keyCertificateAlias", "my2ndcert");
+
+        // System.setProperty("javax.net.debug", "ssl"); // or "all"; "help" for full list
+        
+        WebSocketClient wsClient = new Jsr356WebSocketClient(t, config);
+        
+        String[] expected = new String[] { getStr1(), getStr2() };
+        
+        TStream<String> s = t.strings(expected);
+        s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
+        wsClient.sendString(s);
+        
+        TStream<String> rcvd = wsClient.receiveString();
+        
+        completeAndValidate("", t, rcvd, SEC_TMO, new String[0]); // rcv nothing
+    }
+    
+    @Test
+    public void testSslClientAuthMy3rdCert() throws Exception {
+
+        Topology t = newTopology("testSslClientAuthMy3rdCert");
+        System.out.println("===== "+t.getName());
+
+        startEchoer(ServerMode.SSL_CLIENT_AUTH);  // before getConfig() so it gets the port
+        
+        // explicitly specify client's "my3rdcert" certificate
+        Properties config = getWssConfig();
+        config.setProperty("ws.keyCertificateAlias", "my3rdcert");
+
+        // System.setProperty("javax.net.debug", "ssl"); // or "all"; "help" for full list
+        
+        WebSocketClient wsClient = new Jsr356WebSocketClient(t, config);
+        
+        String[] expected = new String[] { getStr1(), getStr2() };
+        
+        TStream<String> s = t.strings(expected);
+        s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
+        wsClient.sendString(s);
+        
+        TStream<String> rcvd = wsClient.receiveString();
+        
+        completeAndValidate("", t, rcvd, SEC_TMO, expected);
+    }
+    
+    @Test
+    public void testSslClientAuthNeg() throws Exception {
+
+        Topology t = newTopology("testSslClientAuthNeg");
+        System.out.println("===== "+t.getName());
+
+        startEchoer(ServerMode.SSL_CLIENT_AUTH);  // before getConfig() so it gets the port
+
+        // since our server will require client auth, if we don't have
+        // a keystore setup with it in it, the client will fail to connect
+        // and ultimately the connect will fail and the test will
+        // receive nothing.
+
+        Properties config = getConfig();  // no SSL config stuff
+        config.setProperty("ws.uri", getWssUri());
+        
+        // System.setProperty("javax.net.debug", "ssl"); // or "all"; "help" for full list
+        
+        WebSocketClient wsClient = new Jsr356WebSocketClient(t, config);
+        
+        String[] expected = new String[] { getStr1(), getStr2() };
+        
+        TStream<String> s = t.strings(expected);
+        s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
+        wsClient.sendString(s);
+        
+        TStream<String> rcvd = wsClient.receiveString();
+        
+        completeAndValidate("", t, rcvd, SEC_TMO, new String[0]);  //rcv nothing
+    }
+    
+    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);
+        }
+    }
+    
+    @Test
+    public void testPublicServer() throws Exception {
+        Topology t = newTopology("testPublicServer");
+        System.out.println("===== "+t.getName());
+        
+        // startEchoer();  // before getConfig() so it gets the port
+        
+        Properties config = getConfig();
+        config.setProperty("ws.uri", "ws://echo.websocket.org");
+        skipTestIfCantConnect(config);
+
+        // System.setProperty("javax.net.debug", "ssl"); // or "all"; "help" for full list
+        
+        WebSocketClient wsClient = new Jsr356WebSocketClient(t, config);
+        
+        String[] expected = new String[] { getStr1(), getStr2() };
+        
+        TStream<String> s = t.strings(expected);
+        s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
+        wsClient.sendString(s);
+        
+        TStream<String> rcvd = wsClient.receiveString();
+        
+        completeAndValidate("", t, rcvd, SEC_TMO, expected);
+    }
+    
+    @Test
+    public void testSslPublicServer() throws Exception {
+        Topology t = newTopology("testSslPublicServer");
+        System.out.println("===== "+t.getName());
+        
+        // startEchoer();  // before getConfig() so it gets the port
+        
+        // Check operation against a trusted CA signed server certificate.
+        //
+        // this public wss echo server should "just work" if you have
+        // connectivity.  no additional ssl trustStore config is needed
+        // as the site has a certificate signed by a recognized CA.
+        
+        Properties config = getConfig();
+        config.setProperty("ws.uri", "wss://echo.websocket.org");
+        skipTestIfCantConnect(config);
+
+        // System.setProperty("javax.net.debug", "ssl"); // or "all"; "help" for full list
+        
+        WebSocketClient wsClient = new Jsr356WebSocketClient(t, config);
+        
+        String[] expected = new String[] { getStr1(), getStr2() };
+        
+        TStream<String> s = t.strings(expected);
+        s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
+        wsClient.sendString(s);
+        
+        TStream<String> rcvd = wsClient.receiveString();
+        
+        completeAndValidate("", t, rcvd, SEC_TMO, expected);
+    }
+    
+    @Test
+    public void testSslPublicServerBadTrustStoreSystemPropertyNeg() throws Exception {
+        Topology t = newTopology("testSslPublicServerBadTrustStoreSystemPropertyNeg");
+        System.out.println("===== "+t.getName());
+        
+        // startEchoer();  // before getConfig() so it gets the port
+        
+        // this public wss echo server should "just work" if you have
+        // connectivity.  no additional ssl trustStore config is needed
+        // as the site has a certificate signed by a recognized CA.
+        
+        // Set a trust store that doesn't contain the public server's cert nor CAs
+        // and ultimately the connect will fail and the test will
+        // receive nothing.
+
+        Properties config = getConfig();
+        config.setProperty("ws.uri", "wss://echo.websocket.org");
+        skipTestIfCantConnect(config);
+
+        SslSystemPropMgr sslProps = new SslSystemPropMgr();
+        try {
+            sslProps.set();
+    
+            // System.setProperty("javax.net.debug", "ssl"); // or "all"; "help" for full list
+            
+            WebSocketClient wsClient = new Jsr356WebSocketClient(t, config);
+            
+            String[] expected = new String[] { getStr1(), getStr2() };
+            
+            TStream<String> s = t.strings(expected);
+            s = PlumbingStreams.blockingOneShotDelay(s, 2, TimeUnit.SECONDS);
+            wsClient.sendString(s);
+            
+            TStream<String> rcvd = wsClient.receiveString();
+            
+            completeAndValidate("", t, rcvd, SEC_TMO, new String[0]);  //rcv nothing
+        }
+        finally {
+            sslProps.restore();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-quarks/blob/a129aa16/connectors/wsclient-javax.websocket/src/test/java/org/apache/edgent/tests/connectors/wsclient/javax/websocket/WebSocketServerEcho.java
----------------------------------------------------------------------
diff --git a/connectors/wsclient-javax.websocket/src/test/java/org/apache/edgent/tests/connectors/wsclient/javax/websocket/WebSocketServerEcho.java b/connectors/wsclient-javax.websocket/src/test/java/org/apache/edgent/tests/connectors/wsclient/javax/websocket/WebSocketServerEcho.java
new file mode 100644
index 0000000..53e7d8e
--- /dev/null
+++ b/connectors/wsclient-javax.websocket/src/test/java/org/apache/edgent/tests/connectors/wsclient/javax/websocket/WebSocketServerEcho.java
@@ -0,0 +1,241 @@
+/*
+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.tests.connectors.wsclient.javax.websocket;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpoint;
+
+import org.apache.edgent.test.connectors.common.TestRepoPath;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
+
+/**
+ * Simple WebSocket server program to echo received messages.
+ * <p>
+ * See https://github.com/jetty-project/embedded-jetty-websocket-examples
+ */
+@ServerEndpoint(value="/echo")
+public class WebSocketServerEcho {
+    private final String svrName = this.getClass().getSimpleName();
+    private Server server;
+    private ServerConnector connector;
+    private URI curEndpointURI;
+    private boolean curNeedClientAuth;
+    private final ScheduledExecutorService schedExecutor = Executors.newScheduledThreadPool(0);
+    
+    public static void main(String[] args) throws Exception {
+        URI uri = new URI("ws://localhost:0");
+        boolean needClientAuth = false;
+        if (args.length > 0)
+            uri = new URI(args[0]);
+        if (args.length > 1)
+            needClientAuth = "needClientAuth".equals(args[1]);
+        WebSocketServerEcho srvr = new WebSocketServerEcho();
+        srvr.start(uri, needClientAuth);
+    }
+    
+    public void start(URI endpointURI) {
+        start(endpointURI, false);
+    }
+    
+    public void start(URI endpointURI, boolean needClientAuth) {
+        curEndpointURI = endpointURI;
+        curNeedClientAuth = needClientAuth;
+
+        System.out.println(svrName+" "+endpointURI + " needClientAuth="+needClientAuth);
+
+        server = createServer(endpointURI, needClientAuth);
+        connector = (ServerConnector)server.getConnectors()[0];
+
+        // Setup the basic application "context" for this application at "/"
+        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
+        context.setContextPath("/");
+        server.setHandler(context);
+        
+        try {
+            // Initialize javax.websocket layer
+            ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context);
+
+            // Add WebSocket endpoint to javax.websocket layer
+            wscontainer.addEndpoint(this.getClass());
+
+            // System.setProperty("javax.net.debug", "ssl"); // or "all"; "help" for full list
+
+            server.start();
+            System.out.println(svrName+" started "+connector);
+            // server.dump(System.err);            
+        }
+        catch (Exception e) {
+            throw new RuntimeException("start", e);
+        }
+    }
+    
+    private Server createServer(URI endpointURI, boolean needClientAuth) {
+        if ("ws".equals(endpointURI.getScheme())) {
+            return new Server(endpointURI.getPort());
+        }
+        else if ("wss".equals(endpointURI.getScheme())) {
+            // see http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/examples/embedded/src/main/java/org/eclipse/jetty/embedded/ManyConnectors.java
+            //     http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java
+            
+            Server server = new Server();
+            
+            SslContextFactory sslContextFactory = new SslContextFactory();
+            sslContextFactory.setKeyStorePath(getStorePath("serverKeyStore.jks"));
+            sslContextFactory.setKeyStorePassword("passw0rd");
+            sslContextFactory.setKeyManagerPassword("passw0rd");
+            sslContextFactory.setCertAlias("default");
+            sslContextFactory.setNeedClientAuth(needClientAuth);
+            sslContextFactory.setTrustStorePath(getStorePath("serverTrustStore.jks"));
+            sslContextFactory.setTrustStorePassword("passw0rd");
+            
+            HttpConfiguration httpsConfig = new HttpConfiguration();
+            httpsConfig.addCustomizer(new SecureRequestCustomizer());
+            
+            ServerConnector https= new ServerConnector(server,
+                    new SslConnectionFactory(sslContextFactory,
+                            HttpVersion.HTTP_1_1.asString()),
+                    new HttpConnectionFactory(httpsConfig));
+            https.setPort(endpointURI.getPort());
+            
+            server.addConnector(https);
+            return server;
+        }
+        else
+            throw new IllegalArgumentException("unrecognized uri: "+endpointURI);
+    }
+    
+    private String getStorePath(String storeLeaf) {
+        return TestRepoPath.getPath("connectors", "wsclient-javax.websocket", "src", "test", "keystores", storeLeaf);
+    }
+    
+    public int getPort() {
+        // returns -1 if called before started
+        return connector.getLocalPort();
+    }
+    
+    /** restart a running server on the same port, etc: stop, delay, start
+     * @param secDelay the amount to delay in seconds before initiating the restart 
+     */
+    public void restart(int secDelay) {
+        // stop, schedule delay&start and return
+        URI endpointURI = setPort(curEndpointURI, getPort());
+        try {
+            System.out.println(svrName+" restart: stop "+connector);
+            connector.stop();
+        } catch (Exception e) {
+            throw new RuntimeException("restart", e);
+        }
+        System.out.println(svrName+" restart: scheduling start after "+secDelay+"sec");
+        schedExecutor.schedule(() -> {
+            System.out.println(svrName+" restart: starting...");
+            start(endpointURI, curNeedClientAuth);
+        }, secDelay, TimeUnit.SECONDS);
+    }
+    
+    private URI setPort(URI endpointURI, int port) {
+        try {
+            URI uri = endpointURI;
+            if (uri.getPort() != port) {
+                uri = new URI(uri.getScheme(), uri.getUserInfo(),
+                        uri.getHost(), port,
+                        uri.getPath(), uri.getQuery(), uri.getFragment());
+            }
+            return uri;
+        } catch (URISyntaxException e) {
+            throw new RuntimeException("Unable to create URI", e);
+        }
+    }
+    
+    public void stop() {
+        if (connector != null) {
+            try {
+                System.out.println(svrName+" stop "+connector);
+                connector.stop();
+            } catch (Exception e) {
+                // TODO Auto-generated catch block
+                e.printStackTrace();
+            }
+            finally {
+                connector = null;
+            }
+        }
+    }
+    
+    @OnOpen
+    public void opOpen(Session session) {
+        System.out.println(svrName+" onOpen ");
+    }
+    
+    @OnClose
+    public void onClose(Session session, CloseReason reason) {
+        System.out.println(svrName+" onClose reason="+reason);
+    }
+    
+    @OnMessage
+    public void onStringMessage(Session session, String message) {
+        System.out.println(svrName+" onStringMessage msg="+message);
+        try {
+            session.getBasicRemote().sendText(message);
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }  // "echo" response
+    }
+    
+    @OnMessage
+    public void onByteMessage(Session session, ByteBuffer message) {
+        System.out.println(svrName+" onByteMessage "+message.array().length+" bytes");
+        try {
+            session.getBasicRemote().sendBinary(message);
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }  // "echo" response
+    }
+    
+    @OnError
+    public void onError(Throwable cause) {
+        System.err.println(svrName+" onError " + cause);
+        cause.printStackTrace(System.err);
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-quarks/blob/a129aa16/connectors/wsclient/src/main/java/edgent/connectors/wsclient/WebSocketClient.java
----------------------------------------------------------------------
diff --git a/connectors/wsclient/src/main/java/edgent/connectors/wsclient/WebSocketClient.java b/connectors/wsclient/src/main/java/edgent/connectors/wsclient/WebSocketClient.java
deleted file mode 100644
index dcb6d55..0000000
--- a/connectors/wsclient/src/main/java/edgent/connectors/wsclient/WebSocketClient.java
+++ /dev/null
@@ -1,132 +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 edgent.connectors.wsclient;
-
-import com.google.gson.JsonObject;
-
-import edgent.topology.TSink;
-import edgent.topology.TStream;
-import edgent.topology.TopologyElement;
-
-/**
- * A generic connector for sending and receiving messages to a WebSocket Server.
- * <p>
- * A connector is bound to its configuration specified 
- * {@code javax.websockets} WebSocket URI.
- * <p> 
- * A single connector instance supports sinking at most one stream
- * and sourcing at most one stream.
- * <p>
- * Sample use:
- * <pre>{@code
- * // assuming a properties file containing at least:
- * // ws.uri=ws://myWsServerHost/myService
- *  
- * String propsPath = <path to properties file>; 
- * Properties properties = new Properties();
- * properties.load(Files.newBufferedReader(new File(propsPath).toPath()));
- *
- * Topology t = ...;
- * Jsr356WebSocketClient wsclient = new SomeWebSocketClient(t, properties);
- * 
- * // send a stream's JsonObject tuples as JSON WebSocket text messages
- * TStream<JsonObject> s = ...;
- * wsclient.send(s);
- * 
- * // create a stream of JsonObject tuples from received JSON WebSocket text messages
- * TStream<JsonObject> r = wsclient.receive();
- * r.print();
- * }</pre>
- * <p>
- * Note, the WebSocket protocol differentiates between text/String and 
- * binary/byte messages.
- * A receiver only receives the messages of the type that it requests.
- * <p> 
- * Implementations are strongly encouraged to support construction from
- * Properties with the following configuration parameters:
- * <ul>
- * <li>ws.uri - "ws://host[:port][/path]", "wss://host[:port][/path]"
- *   the default port is 80 and 443 for "ws" and "wss" respectively.
- *   The optional path must match the server's configuration.</li>
- * <li>ws.trustStore - optional. Only used with "wss:".
- *     Path to trust store file in JKS format.
- *     If not set, the standard JRE and javax.net.ssl system properties
- *     control the SSL behavior.
- *     Generally not required if server has a CA-signed certificate.</li>
- * <li>ws.trustStorePassword - required if ws.trustStore is set</li>
- * <li>ws.keyStore - optional. Only used with "wss:" when the
- *     server is configured for client auth.
- *     Path to key store file in JKS format.
- *     If not set, the standard JRE and javax.net.ssl system properties
- *     control the SSL behavior.</li>
- * <li>ws.keyStorePassword - required if ws.keyStore is set.</li>
- * <li>ws.keyPassword - defaults to ws.keyStorePassword value</li>
- * <li>ws.keyCertificateAlias - alias for certificate in key store. defaults to "default"</li>
- * </ul>
- */
-public interface WebSocketClient extends TopologyElement {
-
-    /**
-     * Send a stream's JsonObject tuples as JSON in a WebSocket text message.
-     * @param stream the stream
-     * @return sink
-     */
-    TSink<JsonObject> send(TStream<JsonObject> stream);
-
-    /**
-     * Send a stream's String tuples in a WebSocket text message.
-     * @param stream the stream
-     * @return sink
-     */
-    TSink<String> sendString(TStream<String> stream);
-    
-    /**
-     * Send a stream's byte[] tuples in a WebSocket binary message.
-     * @param stream the stream
-     * @return sink
-     */
-    TSink<byte[]> sendBytes(TStream<byte[]> stream);
-
-    /**
-     * Create a stream of JsonObject tuples from received JSON WebSocket text messages.
-     * @return the stream
-     */
-    TStream<JsonObject> receive();
-    
-    /**
-     * Create a stream of String tuples from received WebSocket text messages.
-     * <p>
-     * Note, the WebSocket protocol differentiates between text/String and
-     * binary/byte messages.  This method only receives messages sent as text.
-     * 
-     * @return the stream
-     */
-    TStream<String> receiveString();
-
-    /**
-     * Create a stream of byte[] tuples from received WebSocket binary messages.
-     * <p>
-     * Note, the WebSocket protocol differentiates between text/String and
-     * binary/byte messages.  This method only receives messages sent as bytes.
-     * 
-     * @return the stream
-     */
-    TStream<byte[]> receiveBytes();
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-quarks/blob/a129aa16/connectors/wsclient/src/main/java/edgent/connectors/wsclient/package-info.java
----------------------------------------------------------------------
diff --git a/connectors/wsclient/src/main/java/edgent/connectors/wsclient/package-info.java b/connectors/wsclient/src/main/java/edgent/connectors/wsclient/package-info.java
deleted file mode 100644
index dd38b04..0000000
--- a/connectors/wsclient/src/main/java/edgent/connectors/wsclient/package-info.java
+++ /dev/null
@@ -1,22 +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.
-*/
-/**
- * WebSocket Client Connector API for sending and receiving messages to a WebSocket Server.
- */
-package edgent.connectors.wsclient;

http://git-wip-us.apache.org/repos/asf/incubator-quarks/blob/a129aa16/connectors/wsclient/src/main/java/org/apache/edgent/connectors/wsclient/WebSocketClient.java
----------------------------------------------------------------------
diff --git a/connectors/wsclient/src/main/java/org/apache/edgent/connectors/wsclient/WebSocketClient.java b/connectors/wsclient/src/main/java/org/apache/edgent/connectors/wsclient/WebSocketClient.java
new file mode 100644
index 0000000..44110e0
--- /dev/null
+++ b/connectors/wsclient/src/main/java/org/apache/edgent/connectors/wsclient/WebSocketClient.java
@@ -0,0 +1,132 @@
+/*
+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.connectors.wsclient;
+
+import org.apache.edgent.topology.TSink;
+import org.apache.edgent.topology.TStream;
+import org.apache.edgent.topology.TopologyElement;
+
+import com.google.gson.JsonObject;
+
+/**
+ * A generic connector for sending and receiving messages to a WebSocket Server.
+ * <p>
+ * A connector is bound to its configuration specified 
+ * {@code javax.websockets} WebSocket URI.
+ * <p> 
+ * A single connector instance supports sinking at most one stream
+ * and sourcing at most one stream.
+ * <p>
+ * Sample use:
+ * <pre>{@code
+ * // assuming a properties file containing at least:
+ * // ws.uri=ws://myWsServerHost/myService
+ *  
+ * String propsPath = <path to properties file>; 
+ * Properties properties = new Properties();
+ * properties.load(Files.newBufferedReader(new File(propsPath).toPath()));
+ *
+ * Topology t = ...;
+ * Jsr356WebSocketClient wsclient = new SomeWebSocketClient(t, properties);
+ * 
+ * // send a stream's JsonObject tuples as JSON WebSocket text messages
+ * TStream<JsonObject> s = ...;
+ * wsclient.send(s);
+ * 
+ * // create a stream of JsonObject tuples from received JSON WebSocket text messages
+ * TStream<JsonObject> r = wsclient.receive();
+ * r.print();
+ * }</pre>
+ * <p>
+ * Note, the WebSocket protocol differentiates between text/String and 
+ * binary/byte messages.
+ * A receiver only receives the messages of the type that it requests.
+ * <p> 
+ * Implementations are strongly encouraged to support construction from
+ * Properties with the following configuration parameters:
+ * <ul>
+ * <li>ws.uri - "ws://host[:port][/path]", "wss://host[:port][/path]"
+ *   the default port is 80 and 443 for "ws" and "wss" respectively.
+ *   The optional path must match the server's configuration.</li>
+ * <li>ws.trustStore - optional. Only used with "wss:".
+ *     Path to trust store file in JKS format.
+ *     If not set, the standard JRE and javax.net.ssl system properties
+ *     control the SSL behavior.
+ *     Generally not required if server has a CA-signed certificate.</li>
+ * <li>ws.trustStorePassword - required if ws.trustStore is set</li>
+ * <li>ws.keyStore - optional. Only used with "wss:" when the
+ *     server is configured for client auth.
+ *     Path to key store file in JKS format.
+ *     If not set, the standard JRE and javax.net.ssl system properties
+ *     control the SSL behavior.</li>
+ * <li>ws.keyStorePassword - required if ws.keyStore is set.</li>
+ * <li>ws.keyPassword - defaults to ws.keyStorePassword value</li>
+ * <li>ws.keyCertificateAlias - alias for certificate in key store. defaults to "default"</li>
+ * </ul>
+ */
+public interface WebSocketClient extends TopologyElement {
+
+    /**
+     * Send a stream's JsonObject tuples as JSON in a WebSocket text message.
+     * @param stream the stream
+     * @return sink
+     */
+    TSink<JsonObject> send(TStream<JsonObject> stream);
+
+    /**
+     * Send a stream's String tuples in a WebSocket text message.
+     * @param stream the stream
+     * @return sink
+     */
+    TSink<String> sendString(TStream<String> stream);
+    
+    /**
+     * Send a stream's byte[] tuples in a WebSocket binary message.
+     * @param stream the stream
+     * @return sink
+     */
+    TSink<byte[]> sendBytes(TStream<byte[]> stream);
+
+    /**
+     * Create a stream of JsonObject tuples from received JSON WebSocket text messages.
+     * @return the stream
+     */
+    TStream<JsonObject> receive();
+    
+    /**
+     * Create a stream of String tuples from received WebSocket text messages.
+     * <p>
+     * Note, the WebSocket protocol differentiates between text/String and
+     * binary/byte messages.  This method only receives messages sent as text.
+     * 
+     * @return the stream
+     */
+    TStream<String> receiveString();
+
+    /**
+     * Create a stream of byte[] tuples from received WebSocket binary messages.
+     * <p>
+     * Note, the WebSocket protocol differentiates between text/String and
+     * binary/byte messages.  This method only receives messages sent as bytes.
+     * 
+     * @return the stream
+     */
+    TStream<byte[]> receiveBytes();
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-quarks/blob/a129aa16/connectors/wsclient/src/main/java/org/apache/edgent/connectors/wsclient/package-info.java
----------------------------------------------------------------------
diff --git a/connectors/wsclient/src/main/java/org/apache/edgent/connectors/wsclient/package-info.java b/connectors/wsclient/src/main/java/org/apache/edgent/connectors/wsclient/package-info.java
new file mode 100644
index 0000000..c2cc0cb
--- /dev/null
+++ b/connectors/wsclient/src/main/java/org/apache/edgent/connectors/wsclient/package-info.java
@@ -0,0 +1,22 @@
+/*
+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.
+*/
+/**
+ * WebSocket Client Connector API for sending and receiving messages to a WebSocket Server.
+ */
+package org.apache.edgent.connectors.wsclient;

http://git-wip-us.apache.org/repos/asf/incubator-quarks/blob/a129aa16/console/server/src/main/java/edgent/console/server/HttpServer.java
----------------------------------------------------------------------
diff --git a/console/server/src/main/java/edgent/console/server/HttpServer.java b/console/server/src/main/java/edgent/console/server/HttpServer.java
deleted file mode 100644
index cc27960..0000000
--- a/console/server/src/main/java/edgent/console/server/HttpServer.java
+++ /dev/null
@@ -1,175 +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 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;
-import org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker;
-import org.eclipse.jetty.server.handler.ContextHandlerCollection;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.webapp.WebAppContext;
-
-public class HttpServer {
-
-	/**
-	 * The only constructor.  A private no-argument constructor.  Called only once from the static HttpServerHolder class.
-	 */
-    private HttpServer() {
-    }
-    
-    /** 
-	 * The static class that creates the singleton HttpServer object.
-	 */
-    private static class HttpServerHolder {
-        // use port 0 so we know the server will always start
-        private static final Server JETTYSERVER = new Server(0);
-        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.";
-    }
-
-    /**
-     * Gets the jetty server associated with this class
-     * @return the org.eclipse.jetty.server.Server
-     */
-    private static Server getJettyServer() {
-        return HttpServerHolder.JETTYSERVER;
-    }
-    /**
-     * Initialization of the context path for the web application "/console" occurs in this method
-     * and the handler for the web application is set.  This only occurs once.
-     * @return HttpServer: the singleton instance of this class
-     * @throws Exception on failure
-     */
-    public static HttpServer getInstance() throws Exception {
-        if (!HttpServerHolder.INITIALIZED) {
-            HttpServerHolder.WEBAPP.setContextPath("/console");
-            ServletContextHandler contextJobs = new ServletContextHandler(ServletContextHandler.SESSIONS);
-            contextJobs.setContextPath("/jobs");
-            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);
-            	}
-            } else {
-            	HttpServerHolder.WEBAPP.setWar(commandWarFilePath);
-            }
-
-
-            
-            HttpServerHolder.WEBAPP.addAliasCheck(new AllowSymLinkAliasChecker()); 
-            ContextHandlerCollection contexts = new ContextHandlerCollection();
-            contexts.setHandlers(new Handler[] { contextJobs, contextMetrics, HttpServerHolder.WEBAPP });
-            HttpServerHolder.JETTYSERVER.setHandler(contexts);
-            HttpServerHolder.INITIALIZED = true;
-        }
-        return HttpServerHolder.INSTANCE;
-    }
-
-    /**
-     * 
-     * @return the ServerConnector object for the jetty server
-     */
-    private static ServerConnector getServerConnector() {
-        return (ServerConnector) HttpServerHolder.JETTYSERVER.getConnectors()[0];
-    }
-
-    /**
-     * 
-     * @return a String containing the context path to the console web application
-     * @throws Exception on failure
-     */
-    public String getConsoleContextPath() throws Exception {
-        return HttpServerHolder.WEBAPP.getContextPath();
-    }
-
-    /**
-     * Starts the jetty web server
-     * @throws Exception on failure
-     */
-    public void startServer() throws Exception {
-        getJettyServer().start();
-    }
-
-    /**
-     * Stops the jetty web server
-     * @throws Exception
-     */
-    @SuppressWarnings("unused")
-    private static void stopServer() throws Exception {
-        getJettyServer().stop();
-    }
-
-    /**
-     * Checks to see if the jetty web server is started
-     * @return a boolean: true if the server is started, false if not
-     */
-    public boolean isServerStarted() {
-        if (getJettyServer().isStarted() || getJettyServer().isStarting() || getJettyServer().isRunning()) {
-            return true;
-        }
-        else {
-            return false;
-        }
-    }
-
-    /**
-     * Checks to see if the server is in a "stopping" or "stopped" state
-     * @return a boolean: true if the server is stopping or stopped, false otherwise
-     */
-    public boolean isServerStopped() {
-        if (getJettyServer().isStopping() || getJettyServer().isStopped()) {
-            return true;
-        }
-        else {
-            return false;
-        }
-    }
-    /**
-     * Returns the port number the console is running on.  Each time the console is started a different port number may be returned.
-     * @return an int: the port number the jetty server is listening on
-     */
-    public int getConsolePortNumber() {
-        return getServerConnector().getLocalPort();
-    }
-    
-    /**
-     * Returns the url for the web application at the "console" context path.  Localhost is always assumed
-     * @return the url for the web application at the "console" context path.
-     * @throws Exception on failure
-     */
-    public String getConsoleUrl() throws Exception {
-        return new String("http://localhost" + ":" + getConsolePortNumber() + getConsoleContextPath());
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-quarks/blob/a129aa16/console/server/src/main/java/edgent/console/server/ServerUtil.java
----------------------------------------------------------------------
diff --git a/console/server/src/main/java/edgent/console/server/ServerUtil.java b/console/server/src/main/java/edgent/console/server/ServerUtil.java
deleted file mode 100644
index 8e9fb6b..0000000
--- a/console/server/src/main/java/edgent/console/server/ServerUtil.java
+++ /dev/null
@@ -1,126 +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 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;
-
-public class ServerUtil {
-
-	/**
-	 *  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;
-                }
-            });
-        } catch (IOException e) {
-          // end of file searching
-        }
-        if (foundFiles.size() == 1) {
-            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 "";
-        }
-	
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-quarks/blob/a129aa16/console/server/src/main/java/org/apache/edgent/console/server/HttpServer.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..5844aeb
--- /dev/null
+++ b/console/server/src/main/java/org/apache/edgent/console/server/HttpServer.java
@@ -0,0 +1,175 @@
+/*
+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.console.server;
+
+import java.security.ProtectionDomain;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+public class HttpServer {
+
+	/**
+	 * The only constructor.  A private no-argument constructor.  Called only once from the static HttpServerHolder class.
+	 */
+    private HttpServer() {
+    }
+    
+    /** 
+	 * The static class that creates the singleton HttpServer object.
+	 */
+    private static class HttpServerHolder {
+        // use port 0 so we know the server will always start
+        private static final Server JETTYSERVER = new Server(0);
+        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.";
+    }
+
+    /**
+     * Gets the jetty server associated with this class
+     * @return the org.eclipse.jetty.server.Server
+     */
+    private static Server getJettyServer() {
+        return HttpServerHolder.JETTYSERVER;
+    }
+    /**
+     * Initialization of the context path for the web application "/console" occurs in this method
+     * and the handler for the web application is set.  This only occurs once.
+     * @return HttpServer: the singleton instance of this class
+     * @throws Exception on failure
+     */
+    public static HttpServer getInstance() throws Exception {
+        if (!HttpServerHolder.INITIALIZED) {
+            HttpServerHolder.WEBAPP.setContextPath("/console");
+            ServletContextHandler contextJobs = new ServletContextHandler(ServletContextHandler.SESSIONS);
+            contextJobs.setContextPath("/jobs");
+            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);
+            	}
+            } else {
+            	HttpServerHolder.WEBAPP.setWar(commandWarFilePath);
+            }
+
+
+            
+            HttpServerHolder.WEBAPP.addAliasCheck(new AllowSymLinkAliasChecker()); 
+            ContextHandlerCollection contexts = new ContextHandlerCollection();
+            contexts.setHandlers(new Handler[] { contextJobs, contextMetrics, HttpServerHolder.WEBAPP });
+            HttpServerHolder.JETTYSERVER.setHandler(contexts);
+            HttpServerHolder.INITIALIZED = true;
+        }
+        return HttpServerHolder.INSTANCE;
+    }
+
+    /**
+     * 
+     * @return the ServerConnector object for the jetty server
+     */
+    private static ServerConnector getServerConnector() {
+        return (ServerConnector) HttpServerHolder.JETTYSERVER.getConnectors()[0];
+    }
+
+    /**
+     * 
+     * @return a String containing the context path to the console web application
+     * @throws Exception on failure
+     */
+    public String getConsoleContextPath() throws Exception {
+        return HttpServerHolder.WEBAPP.getContextPath();
+    }
+
+    /**
+     * Starts the jetty web server
+     * @throws Exception on failure
+     */
+    public void startServer() throws Exception {
+        getJettyServer().start();
+    }
+
+    /**
+     * Stops the jetty web server
+     * @throws Exception
+     */
+    @SuppressWarnings("unused")
+    private static void stopServer() throws Exception {
+        getJettyServer().stop();
+    }
+
+    /**
+     * Checks to see if the jetty web server is started
+     * @return a boolean: true if the server is started, false if not
+     */
+    public boolean isServerStarted() {
+        if (getJettyServer().isStarted() || getJettyServer().isStarting() || getJettyServer().isRunning()) {
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+
+    /**
+     * Checks to see if the server is in a "stopping" or "stopped" state
+     * @return a boolean: true if the server is stopping or stopped, false otherwise
+     */
+    public boolean isServerStopped() {
+        if (getJettyServer().isStopping() || getJettyServer().isStopped()) {
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+    /**
+     * Returns the port number the console is running on.  Each time the console is started a different port number may be returned.
+     * @return an int: the port number the jetty server is listening on
+     */
+    public int getConsolePortNumber() {
+        return getServerConnector().getLocalPort();
+    }
+    
+    /**
+     * Returns the url for the web application at the "console" context path.  Localhost is always assumed
+     * @return the url for the web application at the "console" context path.
+     * @throws Exception on failure
+     */
+    public String getConsoleUrl() throws Exception {
+        return new String("http://localhost" + ":" + getConsolePortNumber() + getConsoleContextPath());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-quarks/blob/a129aa16/console/server/src/main/java/org/apache/edgent/console/server/ServerUtil.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..02020f0
--- /dev/null
+++ b/console/server/src/main/java/org/apache/edgent/console/server/ServerUtil.java
@@ -0,0 +1,126 @@
+/*
+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.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;
+
+public class ServerUtil {
+
+	/**
+	 *  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;
+                }
+            });
+        } catch (IOException e) {
+          // end of file searching
+        }
+        if (foundFiles.size() == 1) {
+            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 "";
+        }
+	
+    }
+
+}