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 "";
+ }
+
+ }
+
+}