changeset 579:100d4bff9f82

QlikWebsocketClient - working version
author Devel 1
date Fri, 29 Sep 2017 16:35:29 +0200
parents fab4175d7fec
children a521874e90ea
files stress-tester-qlik/pom.xml stress-tester-qlik/src/main/java/com/passus/st/qlik/Protocol.java stress-tester-qlik/src/main/java/com/passus/st/qlik/QlikWebsocketClient.java stress-tester-qlik/src/test/java/com/passus/st/qlik/DevelEndpoint.java stress-tester-qlik/src/test/java/com/passus/st/qlik/ProtocolTest.java stress-tester-qlik/src/test/java/com/passus/st/qlik/QlikWebsocketClientIT.java
diffstat 6 files changed, 290 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/stress-tester-qlik/pom.xml	Fri Sep 29 16:04:03 2017 +0200
+++ b/stress-tester-qlik/pom.xml	Fri Sep 29 16:35:29 2017 +0200
@@ -34,6 +34,18 @@
             <artifactId>json-smart</artifactId>
             <version>1.3.1</version>
         </dependency>
+
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-api</artifactId>
+            <version>2.7</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-core</artifactId>
+            <version>2.7</version>
+        </dependency>
+
         <dependency>
             <groupId>org.testng</groupId>
             <artifactId>testng</artifactId>
--- a/stress-tester-qlik/src/main/java/com/passus/st/qlik/Protocol.java	Fri Sep 29 16:04:03 2017 +0200
+++ b/stress-tester-qlik/src/main/java/com/passus/st/qlik/Protocol.java	Fri Sep 29 16:35:29 2017 +0200
@@ -77,11 +77,11 @@
         return (JSONArray) o;
     }
 
-    static Number number(Object o) {
+    static Integer integer(Object o) {
         if (!(o instanceof Number)) {
             throw new IllegalArgumentException();
         }
-        return (Number) o;
+        return (Integer) o;
     }
 
     static Boolean bool(Object o) {
@@ -95,16 +95,16 @@
         return jsonObject(JSONValue.parse(json));
     }
 
-    public static int getID(JSONObject root) {
+    public static Integer getID(JSONObject root) {
         Object id = root.get(ID);
-        return number(id).intValue();
+        return id == null ? null : integer(id);
     }
 
     public static int getHandle(JSONObject root) {
         JSONObject result = jsonObject(root.get(RESULT));
         JSONObject retValue = jsonObject(result.get("qReturn"));
         Object handle = retValue.get("qHandle");
-        return number(handle).intValue();
+        return integer(handle);
     }
 
     public static boolean getReturnedBoolean(JSONObject root) {
--- a/stress-tester-qlik/src/main/java/com/passus/st/qlik/QlikWebsocketClient.java	Fri Sep 29 16:04:03 2017 +0200
+++ b/stress-tester-qlik/src/main/java/com/passus/st/qlik/QlikWebsocketClient.java	Fri Sep 29 16:35:29 2017 +0200
@@ -12,7 +12,10 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
 import net.minidev.json.JSONObject;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
 import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
 import org.eclipse.jetty.websocket.api.annotations.WebSocket;
@@ -25,22 +28,31 @@
  */
 public class QlikWebsocketClient {
 
+    private static final Logger LOGGER = LogManager.getLogger(QlikWebsocketClient.class);
+
     @WebSocket(maxTextMessageSize = 64 * 1024)
-    static class QlikEndpoint {
+    public static class QlikEndpoint {
 
         Map<Integer, CompletableFuture<JSONObject>> futures = new HashMap<>();
         private Session session;
 
+        @OnWebSocketClose
+        public void onClose(int statusCode, String reason) {
+            LOGGER.debug("Connection closed: {} - {}", statusCode, reason);
+            session = null;
+        }
+
         @OnWebSocketConnect
         public void onConnect(Session session) {
-            System.out.println("connected");
+            LOGGER.debug("connected");
             this.session = session;
         }
 
         @OnWebSocketMessage
         public void onMessage(String msg) {
+            LOGGER.debug(" IN: {}", msg);
             JSONObject root = Protocol.parseObject(msg);
-            int id = Protocol.getID(root);
+            Integer id = Protocol.getID(root);
             CompletableFuture<JSONObject> future = futures.remove(id);
             if (future != null) {
                 future.complete(root);
@@ -48,6 +60,7 @@
         }
 
         public void sendMessage(String msg) throws IOException {
+            LOGGER.debug("OUT: {}", msg);
             session.getRemote().sendString(msg);
         }
 
@@ -114,6 +127,18 @@
     }
 
     public static Future<Boolean> asyncReload(String host, int port, String docName) {
-        throw new UnsupportedOperationException();
+        final CompletableFuture<Boolean> future = new CompletableFuture<>();
+        Thread task = new Thread() {
+            @Override
+            public void run() {
+                try {
+                    future.complete(reload(host, port, docName));
+                } catch (Exception ex) {
+                    future.completeExceptionally(ex);
+                }
+            }
+        };
+        task.start();
+        return future;
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stress-tester-qlik/src/test/java/com/passus/st/qlik/DevelEndpoint.java	Fri Sep 29 16:35:29 2017 +0200
@@ -0,0 +1,126 @@
+package com.passus.st.qlik;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+
+@WebSocket(maxTextMessageSize = 64 * 1024)
+public class DevelEndpoint {
+
+    public static final int PORT = 9076;//4848; // 9076
+
+    public static final String url1 = "ws://localhost:" + PORT + "/app/"
+            + "%3Ftransient%3D/identity/8phkqd"
+            + "?"
+            + "reloadUri=http%3A%2F%2Flocalhost%3A4848%2Fdev-hub%2Fengine-api-explorer";
+
+    public static final String url2 = "ws://localhost:" + PORT + "/app/"
+            + "%3Ftransient%3D"
+            + "?reloadUri=http%3A%2F%2Flocalhost%3A4848%2Fdev-hub%2Fengine-api-explorer";
+
+    public static final String url3 = "ws://localhost:" + PORT + "/app/"
+            + "C%3A%5CUsers%5Cmikolaj.podbielski%5CDocuments%5CQlik%5CSense%5CApps%5CST_final_v1_LB.qvf"
+            + "?reloadUri=http%3A%2F%2Flocalhost%3A4848%2Fdev-hub%2Fengine-api-explorer";
+
+    public static final String msgProductVersion = "{\"method\":\"ProductVersion\",\"handle\":-1,\"params\":[],\"jsonrpc\":\"2.0\",\"id\":1}";
+    public static final String msgIsPersonalMode = "{\"method\":\"IsPersonalMode\",\"handle\":-1,\"params\":[],\"jsonrpc\":\"2.0\",\"id\":2}";
+
+    public static final String msgGetDocList = "{\"method\":\"GetDocList\",\"handle\":-1,\"params\":[],\"jsonrpc\":\"2.0\",\"id\":1}";
+
+    public static final String msgOpenDoc1 = "{\"method\":\"OpenDoc\",\"handle\":-1,\"params\":[\"C:\\\\Users\\\\mikolaj.podbielski\\\\Documents\\\\Qlik\\\\Sense\\\\Apps\\\\ST_final_v1_LB.qvf\",\"\",\"\",\"\",false],\"delta\":false,\"jsonrpc\":\"2.0\",\"id\":1}";
+    public static final String msgOpenDoc2 = "{\"method\":\"OpenDoc\",\"handle\":-1,\"params\":[\"C:\\\\Users\\\\mikolaj.podbielski\\\\Documents\\\\Qlik\\\\Sense\\\\Apps\\\\ST_final_v1_LB.qvf\"],\"jsonrpc\":\"2.0\",\"id\":2}";
+    public static final String msgDoReload = "{\"method\":\"DoReload\",\"handle\":1,\"params\":{\"qMode\":0,\"qPartial\":false,\"qDebug\":false},\"jsonrpc\":\"2.0\",\"id\":4}";
+
+    private final Object lock = new Object();
+    private Session session;
+    private String lastMessage;
+
+    @OnWebSocketClose
+    public void onClose(int statusCode, String reason) {
+        System.out.printf("Connection closed: %d - %s%n", statusCode, reason);
+        session = null;
+    }
+
+    @OnWebSocketConnect
+    public void onConnect(Session session) {
+        System.out.println("connected");
+        this.session = session;
+    }
+
+    @OnWebSocketMessage
+    public void onMessage(String msg) {
+        System.out.printf(" IN: %s%n", msg);
+        synchronized (lock) {
+            lastMessage = msg;
+            lock.notifyAll();
+        }
+    }
+
+    public String waitForMessage() throws InterruptedException {
+        synchronized (lock) {
+            lock.wait();
+            return lastMessage;
+        }
+    }
+
+    public static void sendMessage(Session session, String msg) throws IOException {
+        try {
+            Thread.sleep(300);
+        } catch (InterruptedException ignore) {
+        }
+        System.out.printf("OUT: %s%n", msg);
+        session.getRemote().sendString(msg);
+    }
+
+    public void sendMessage(String msg) throws IOException {
+        try {
+            Thread.sleep(300);
+        } catch (InterruptedException ignore) {
+        }
+        System.out.printf("OUT: %s%n", msg);
+        session.getRemote().sendString(msg);
+    }
+
+    private static Session openSession(WebSocketClient client, String url, Object websocket)
+            throws IOException, InterruptedException, ExecutionException, URISyntaxException {
+        ClientUpgradeRequest request = new ClientUpgradeRequest();
+        Future<Session> future = client.connect(websocket, new URI(url), request);
+        Session session = future.get();
+        return session;
+    }
+
+    public static void main(String[] args) throws Exception {
+        String appId = "C:\\Users\\mikolaj.podbielski\\Documents\\Qlik\\Sense\\Apps\\ST_final_v1_LB.qvf";
+        String url = "ws://localhost:" + PORT + "/app/";
+        String appUrl = url + URLEncoder.encode(appId, "UTF-8");
+
+        WebSocketClient client = new WebSocketClient();
+        client.start();
+
+        DevelEndpoint endpoint = new DevelEndpoint();
+
+        try (Session session2 = openSession(client, url, endpoint)) {
+            sendMessage(session2, msgGetDocList);
+            String docListString = endpoint.waitForMessage();
+        }
+        try (Session session3 = openSession(client, appUrl, endpoint)) {
+            sendMessage(session3, msgOpenDoc2);
+            sendMessage(session3, msgDoReload);
+
+            String reloadStatus = endpoint.waitForMessage();
+//        endpoint.waitForMessage();
+        }
+
+        client.stop();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stress-tester-qlik/src/test/java/com/passus/st/qlik/ProtocolTest.java	Fri Sep 29 16:35:29 2017 +0200
@@ -0,0 +1,92 @@
+package com.passus.st.qlik;
+
+import java.util.List;
+import net.minidev.json.JSONObject;
+import static org.testng.AssertJUnit.*;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author mikolaj.podbielski
+ */
+public class ProtocolTest {
+
+    @Test
+    public void testParseGetDocListResponse() {
+        String json = "{\n"
+                + " \"jsonrpc\": \"2.0\",\n"
+                + " \"id\": 1,\n"
+                + " \"result\": {\n"
+                + "  \"qDocList\": [\n"
+                + "   {\n"
+                + "    \"qDocName\": \"Sales Discovery.qvf\",\n"
+                + "    \"qConnectedUsers\": 0,\n"
+                + "    \"qFileTime\": 42990.50472222222,\n"
+                + "    \"qFileSize\": 27262976,\n"
+                + "    \"qDocId\": \"C:\\\\Users\\\\x\\\\Documents\\\\Qlik\\\\Sense\\\\Apps\\\\Sales Discovery.qvf\",\n"
+                + "    \"qMeta\": {\n"
+                + "     \"description\": \"This application\",\n"
+                + "     \"dynamicColor\": \"hsla(210,18%,43%,1)\"\n"
+                + "    },\n"
+                + "    \"qLastReloadTime\": \"2016-06-14T14:40:01.222Z\",\n"
+                + "    \"qTitle\": \"Sales Discovery\",\n"
+                + "    \"qThumbnail\": {\n"
+                + "     \"qUrl\": \"/media/4/thumb-sales_discovery.png\"\n"
+                + "    },\n"
+                + "    \"key\": \"C:\\\\Users\\\\x\\\\Documents\\\\Qlik\\\\Sense\\\\Apps\\\\Sales Discovery.qvf\",\n"
+                + "    \"$$hashKey\": \"object:111\"\n"
+                + "   },\n"
+                + "   {\n"
+                + "    \"qDocName\": \"ST_final_v1_LB.qvf\",\n"
+                + "    \"qConnectedUsers\": 0,\n"
+                + "    \"qFileTime\": 43007.4975,\n"
+                + "    \"qFileSize\": 1179648,\n"
+                + "    \"qDocId\": \"C:\\\\Users\\\\x\\\\Documents\\\\Qlik\\\\Sense\\\\Apps\\\\ST_final_v1_LB.qvf\",\n"
+                + "    \"qMeta\": {\n"
+                + "     \"description\": \"\",\n"
+                + "     \"dynamicColor\": \"\"\n"
+                + "    },\n"
+                + "    \"qLastReloadTime\": \"2017-09-29T09:56:22.464Z\",\n"
+                + "    \"qTitle\": \"Passus Stress Tester\",\n"
+                + "    \"qThumbnail\": {\n"
+                + "     \"qUrl\": \"/media/3/snmp.png\"\n"
+                + "    },\n"
+                + "    \"key\": \"C:\\\\Users\\\\x\\\\Documents\\\\Qlik\\\\Sense\\\\Apps\\\\ST_final_v1_LB.qvf\",\n"
+                + "    \"$$hashKey\": \"object:110\"\n"
+                + "   }\n"
+                + "  ]\n"
+                + " }\n"
+                + "}";
+
+        List<Protocol.DocInfo> list = Protocol.parseGetDocListResponse(json);
+        assertEquals(2, list.size());
+
+        String expectedId = "C:\\Users\\x\\Documents\\Qlik\\Sense\\Apps\\Sales Discovery.qvf";
+        Protocol.DocInfo info = list.get(0);
+        assertEquals("Sales Discovery.qvf", info.getDocName());
+        assertEquals(expectedId, info.getDocId());
+        assertEquals("Sales Discovery", info.getTitle());
+        assertEquals(expectedId, info.getKey());
+    }
+
+    @Test
+    public void testGetId() {
+        JSONObject request = Protocol.parseObject("{\"method\":\"SomeRequest\",\"handle\":-1,\"params\":[],\"jsonrpc\":\"2.0\",\"id\":123}");
+        assertEquals(123, Protocol.getID(request).intValue());
+
+        JSONObject response = Protocol.parseObject("{\"jsonrpc\":\"2.0\",\"id\":125,\"result\":{\"qReturn\":{\"qType\":\"Doc\",\"qHandle\":3,\"qGenericId\":\"qwerty\"}}}");
+        assertEquals(125, Protocol.getID(response).intValue());
+    }
+
+    @Test
+    public void testGetHandle() {
+        JSONObject response = Protocol.parseObject("{\"jsonrpc\":\"2.0\",\"id\":125,\"result\":{\"qReturn\":{\"qType\":\"Doc\",\"qHandle\":3,\"qGenericId\":\"qwerty\"}}}");
+        assertEquals(3, Protocol.getHandle(response));
+    }
+
+    @Test
+    public void testGetBooleanResult() {
+        JSONObject response = Protocol.parseObject("{\"jsonrpc\":\"2.0\",\"id\":4,\"result\":{\"qReturn\":true},\"change\":[1]}");
+        assertEquals(true, Protocol.getReturnedBoolean(response));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stress-tester-qlik/src/test/java/com/passus/st/qlik/QlikWebsocketClientIT.java	Fri Sep 29 16:35:29 2017 +0200
@@ -0,0 +1,26 @@
+package com.passus.st.qlik;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import static org.testng.Assert.*;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author mikolaj.podbielski
+ */
+public class QlikWebsocketClientIT {
+
+    @Test
+    public void testReload() throws Exception {
+        boolean reload = QlikWebsocketClient.reload("localhost", 9076, "ST_final_v1_LB.qvf");
+        assertTrue(reload);
+    }
+
+    @Test
+    public void testAsyncReload() throws InterruptedException, ExecutionException {
+        Future<Boolean> asyncReload = QlikWebsocketClient.asyncReload("localhost", 9076, "ST_final_v1_LB.qvf");
+        assertTrue(asyncReload.get());
+    }
+
+}