stdin/stdout stream based hatch WIP
authorBill Erickson <berickxx@gmail.com>
Mon, 7 Nov 2016 18:50:12 +0000 (13:50 -0500)
committerBill Erickson <berickxx@gmail.com>
Mon, 7 Nov 2016 18:50:12 +0000 (13:50 -0500)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
README
run.sh
src/org/evergreen_ils/hatch/FileIO.java
src/org/evergreen_ils/hatch/Hatch.java
src/org/evergreen_ils/hatch/HatchWebSocketHandler.java [deleted file]
src/org/evergreen_ils/hatch/HatchWebSocketServlet.java [deleted file]
src/org/evergreen_ils/hatch/MessageIO.java [new file with mode: 0644]
src/org/evergreen_ils/hatch/PrintManager.java

diff --git a/README b/README
index 7c06daa..733d9f1 100644 (file)
--- a/README
+++ b/README
@@ -2,26 +2,14 @@ Hatch - Java Print / Storage / Etc Service
 
 ** ROUGH SETUP NOTES **
 
-Install Hatch on your desktop -- Linux edition: 
-
-% wget http://download.eclipse.org/jetty/stable-9/dist/jetty-distribution-9.2.5.v20141112.tar.gz 
-% tar -zxf jetty-distribution-9.2.5.v20141112.tar.gz 
-% ln -s jetty-distribution-9.2.5.v20141112 jetty
-
 # download jdk1.8 (requires license agreement) -- haven't tested on openjdk yet. 
 # http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
 # and extract in the same directory
 % ln -s jdk1.8.0_25 jdk1.8
 
 % mkdir lib
-% wget -O lib/jetty-util-ajax-9.2.5.v20141112.jar \
-    'http://central.maven.org/maven2/org/eclipse/jetty/jetty-util-ajax/9.2.5.v20141112/jetty-util-ajax-9.2.5.v20141112.jar'
-
-# create an SSL certificat for jetty
-# if you use a password other than "password", modify references to 
-# "password" in hath.xml (in the top directory).
-% cd jetty/etc/
-% ../../jdk1.8/bin/keytool -keystore keystore -alias jetty -genkey -keyalg RSA
+Download javax.json-api jar file from 
+http://search.maven.org/remotecontent?filepath=javax/json/javax.json-api/1.0/javax.json-api-1.0.jar
 
 # compile
 % ./run.sh
@@ -29,7 +17,3 @@ Install Hatch on your desktop -- Linux edition:
 # compile + run
 % ./run.sh 1
 
-# open https://localhost:8443/ in Chrome and click through the security warning.
-# Then open the browser client.
-# Set "This workstation uses a remote print / storage service ("Hatch")?" under Admin -> Workstation
-# optionally configure / test printing
diff --git a/run.sh b/run.sh
index 91d2dca..1433313 100755 (executable)
--- a/run.sh
+++ b/run.sh
@@ -1,15 +1,10 @@
 JAVA_HOME=jdk1.8
-JETTY_HOME=jetty
+CP=lib:lib/javax.json-api-1.0.jar
 
 # compile
-$JAVA_HOME/bin/javac \
-    -cp "$JETTY_HOME/lib/*:$JETTY_HOME/lib/websocket/*:lib/*" \
-    -Xdiags:verbose -d lib \
-    src/org/evergreen_ils/hatch/*.java
+$JAVA_HOME/bin/javac -cp $CP -d lib src/org/evergreen_ils/hatch/*.java
 
 [ -z "$1" ] && exit;
 
 # run
-$JAVA_HOME/bin/java \
-    -cp "$JETTY_HOME/lib/*:$JETTY_HOME/lib/websocket/*:lib/*:lib" \
-    org.evergreen_ils.hatch.Hatch
+$JAVA_HOME/bin/java -cp $CP org.evergreen_ils.hatch.Hatch
index 0aa2fc7..8813b7c 100644 (file)
@@ -18,8 +18,6 @@ package org.evergreen_ils.hatch;
 import java.io.*;
 import java.util.LinkedList;
 import java.util.Arrays;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
 
 public class FileIO {
 
@@ -60,7 +58,7 @@ public class FileIO {
     // -------------------------------------------------- 
 
     // logger
-    private static final Logger logger = Log.getLogger("FileIO");
+    //private static final Logger logger = Log.getLogger("FileIO");
 
     /**
      * Constructs a new FileIO with the provided base path.
@@ -84,7 +82,7 @@ public class FileIO {
         File dir = new File(basePath);
         if (!dir.exists()) {
             if (!dir.mkdir()) {
-                logger.info("Unable to create directory: " + dir.getName());
+                //logger.info("Unable to create directory: " + dir.getName());
                 return null;
             }
         }
@@ -93,12 +91,12 @@ public class FileIO {
         File subDir = new File(basePath, originDomain);
         if (!subDir.exists()) {
             if (!subDir.mkdir()) {
-                logger.info("Unable to create directory: " + subDir.getName());
+                //logger.info("Unable to create directory: " + subDir.getName());
                 return null;
             }
         }
 
-        logger.info("baseDir: " + subDir.getName());
+        //logger.info("baseDir: " + subDir.getName());
         return subDir;
     }
 
@@ -124,7 +122,7 @@ public class FileIO {
      * @return success or failure
      */
     public boolean set(String key, String text) {
-        logger.info("set => " + key);
+        //logger.info("set => " + key);
         File file = getFile(key);
 
         if (text == null) return false;
@@ -133,8 +131,8 @@ public class FileIO {
 
             // delete the file if it exists
             if (!file.exists() && !file.createNewFile()) {
-                logger.info(
-                    "Unable to create file: " + file.getCanonicalPath());
+                //logger.info(
+                    //"Unable to create file: " + file.getCanonicalPath());
                 return false;
             }
 
@@ -146,8 +144,8 @@ public class FileIO {
             outStream.close();
 
         } catch(IOException e) {
-            logger.warn("Error calling set() with key " + key);
-            logger.warn(e);
+            //logger.warn("Error calling set() with key " + key);
+            //logger.warn(e);
             return false;
         }
 
@@ -164,15 +162,15 @@ public class FileIO {
      * @return success or failure
      */
     public boolean append(String key, String text) {
-        logger.info("append => " + key);
+        //logger.info("append => " + key);
         File file = getFile(key);
 
         try {
 
             // create the file if it doesn's already exist
             if (!file.exists() && !file.createNewFile()) {
-                logger.info(
-                    "Unable to create file: " + file.getCanonicalPath());
+                //logger.info(
+                    //"Unable to create file: " + file.getCanonicalPath());
                 return false;
             }
 
@@ -183,8 +181,8 @@ public class FileIO {
             outStream.close();
 
         } catch(IOException e) {
-            logger.warn("Error in append() with key " + key);
-            logger.warn(e);
+            //logger.warn("Error in append() with key " + key);
+            //logger.warn(e);
             return false;
         }
 
@@ -198,7 +196,7 @@ public class FileIO {
      * @return The text content of the file
      */
     public String get(String key) {
-        logger.info("get => " + key);
+        //logger.info("get => " + key);
         File file = getFile(key);
         if (!file.exists()) return null;
 
@@ -213,8 +211,8 @@ public class FileIO {
                 buf.append(line);
             }
         } catch (IOException e) {
-            logger.warn("Error reading key: " + key);
-            logger.warn(e);
+            //logger.warn("Error reading key: " + key);
+            //logger.warn(e);
             return null;
         }
 
@@ -228,20 +226,20 @@ public class FileIO {
      * @return success or failure
      */
     public boolean remove(String key) {
-        logger.info("remove => " + key);
+        //logger.info("remove => " + key);
         File file = getFile(key);
-        try {
+        //try {
             if (file.exists() && !file.delete()) {
-                logger.info(
-                    "Unable to delete file: " + file.getCanonicalPath());
+                //logger.info(
+                    //"Unable to delete file: " + file.getCanonicalPath());
                 return false;
             }
             return true;
-        } catch (IOException e) {
-            logger.warn("Error deleting key: " + key);
-            logger.warn(e);
-            return false;
-        }
+        //} catch (IOException e) {
+            //logger.warn("Error deleting key: " + key);
+            //logger.warn(e);
+            //return false;
+        //}
     }
 
     /**
@@ -261,7 +259,7 @@ public class FileIO {
      * @return Array of keys
      */
     public String[] keys(String prefix) {
-        logger.info("keys => " + prefix);
+        //logger.info("keys => " + prefix);
 
         File dir = baseDir();
         if (dir == null || !dir.exists()) 
index 0bed93f..3b1df8d 100644 (file)
  */
 package org.evergreen_ils.hatch;
 
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.servlet.ServletHandler;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.handler.HandlerList;
-import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.xml.XmlConfiguration;
-
 import javafx.application.Application;
 import javafx.application.Platform;
 import javafx.scene.Scene;
@@ -49,8 +32,7 @@ import javafx.concurrent.Task;
 import javafx.event.EventHandler;
 import javafx.concurrent.WorkerStateEvent;
 import java.util.concurrent.LinkedBlockingQueue;
-
-import org.eclipse.jetty.util.ajax.JSON;
+import java.util.logging.*;
 
 import java.util.Map;
 
@@ -88,15 +70,11 @@ public class Hatch extends Application {
     private Stage primaryStage;
     
     /** Our logger instance */
-    static final Logger logger = Log.getLogger("Hatch");
-
-    /** Message queue for passing messages from the Jetty thread into
-     * the JavaFX Application thread */
-    private static LinkedBlockingQueue<Map> requestQueue =
-        new LinkedBlockingQueue<Map>();
+    static Logger logger;
 
+    
     /**
-     * Printable region containing a browser
+     * Printable region containing a browser.
      */
     class BrowserView extends Region {
         WebView webView = new WebView();
@@ -107,47 +85,11 @@ public class Hatch extends Application {
     }
 
     /**
-     * Service task which listens for inbound messages from the
-     * servlet.
-     *
-     * The code blocks on the concurrent queue, so it must be
-     * run in a separate thread to avoid locking the main FX thread.
-     */
-    private static class MsgListenService extends Service<Map<String,Object>> {
-        protected Task<Map<String,Object>> createTask() {
-            return new Task<Map<String,Object>>() {
-                protected Map<String,Object> call() {
-                    while (true) {
-                        logger.info("MsgListenService waiting for a message...");
-                        try {
-                            // take() blocks until a message is available
-                            return requestQueue.take();
-                        } catch (InterruptedException e) {
-                            // interrupted, go back and listen
-                            continue;
-                        }
-                    }
-                }
-            };
-        }
-    }
-
-
-    /**
      * JavaFX startup call
      */
     @Override
     public void start(Stage primaryStage) {
         this.primaryStage = primaryStage;
-        startMsgTask();
-    }
-
-    /**
-     * Queues a message for processing by the queue processing thread.
-     */
-    public static void enqueueMessage(Map<String,Object> params) {
-        logger.debug("queueing print message");
-        requestQueue.offer(params);
     }
 
     /**
@@ -159,7 +101,7 @@ public class Hatch extends Application {
         String contentType = (String) params.get("contentType");
 
         if (content == null) {
-            logger.warn("handlePrint() called with no content");
+            //logger.warn("handlePrint() called with no content");
             return;
         }
 
@@ -170,9 +112,9 @@ public class Hatch extends Application {
         browser.webEngine.getLoadWorker()
             .stateProperty()
             .addListener( (ChangeListener) (obsValue, oldState, newState) -> {
-                logger.info("browser load state " + newState);
+                //logger.info("browser load state " + newState);
                 if (newState == State.SUCCEEDED) {
-                    logger.info("Print browser page load completed");
+                    //logger.info("Print browser page load completed");
 
                     // Avoid nested UI event loops -- runLater
                     Platform.runLater(new Runnable() {
@@ -183,73 +125,145 @@ public class Hatch extends Application {
                 }
             });
 
-        logger.info("printing " + content.length() + " bytes of " + contentType);
+        //logger.info("printing " + content.length() + " bytes of " + contentType);
         browser.webEngine.loadContent(content, contentType);
-
-        // After queueing up the HTML for printing, go back to listening
-        // for new messages.
-        startMsgTask();
     }
 
-    /**
-     * Fire off the Service task, which checks for queued messages.
-     *
-     * When a queued message is found, it's sent off for printing.
-     */
-    public void startMsgTask() {
 
-        MsgListenService service = new MsgListenService();
+    /*
+    public void onMessage(String message) {
+        if (session == null || !session.isOpen()) return;
+        //logger.info("onMessage() " + message);
+
+        HashMap<String,Object> params = null;
 
-        logger.info("starting MsgTask");
+        try {
+            params = (HashMap<String,Object>) JSON.parse(message);
+        } catch (ClassCastException e) {
+            reply("Invalid WebSockets JSON message " + message, 
+                new Long(-1), false);
+        }
 
-        service.setOnSucceeded(
-            new EventHandler<WorkerStateEvent>() {
+        Long msgid = (Long) params.get("msgid");
+        String action = (String) params.get("action");
+        String key = (String) params.get("key");
+        String value = (String) params.get("value");
+        String mime = (String) params.get("mime");
 
-            @Override
-            public void handle(WorkerStateEvent t) {
-                logger.info("MsgTask handling message.. ");
-                Map<String,Object> message = 
-                    (Map<String,Object>) t.getSource().getValue();
+        //logger.info("Received request for action " + action);
 
-                // avoid nesting UI event loops by kicking off the print
-                // operation from the main FX loop after this event handler 
-                // has exited.
-                Platform.runLater(
-                    new Runnable() {
-                        @Override public void run() {
-                            handlePrint(message);
-                        }
+        // all requets require a message ID
+        if (msgid == null) {
+            reply("No msgid specified in request", msgid, false);
+            return;
+        }
+
+        // all requests require an action
+        if (action == null || action.equals("")) {
+            reply("No action specified in request", msgid, false);
+            return;
+        }
+
+        Object response = null;
+        boolean error = false;
+        FileIO io = new FileIO(profileDirectory, origin);
+
+        switch (action) {
+            case "keys":
+                response = io.keys(key);
+                break;
+
+            case "printers":
+                response = new PrintManager().getPrintersAsMaps();
+                break;
+
+            case "print":
+                // pass ourselves off to the print handler so it can reply
+                // for us after printing has completed.
+                params.put("socket", this);
+                Hatch.enqueueMessage(params);
+
+                // we don't want to return a response below, since the 
+                // FX thread will handle that for us.
+                return;
+
+            case "print-config":
+                try {
+                    response = new PrintManager().configurePrinter(params);
+                } catch(IllegalArgumentException e) {
+                    response = e.toString();
+                    error = true;
+                }
+                break;
+
+            case "get":
+                String val = io.get(key);
+                if (val != null) {
+                    // set() stores bare JSON. We must pass an 
+                    // Object to reply so that it may be embedded into
+                    // a larger JSON response object, hence the JSON.parse().
+                    try {
+                        response = JSON.parse(val);
+                    } catch(java.lang.IllegalStateException e) {
+                        error = true;
+                        response = "Error JSON-parsing stored value " + val;
                     }
-                );
-            }
-        });
+                }
+                break;
+
+            case "remove":
+                response = io.remove(key);
+                break;
+
+            case "set" :
+                response = io.set(key, value);
+                break;
+
+            case "append" :
+                response = io.append(key, value);
+                break;
+
+            default:
+                response = "No such action: " + action;
+                error = true;
+        }
 
-        service.start();
+        reply(response, msgid, !error);
     }
+    */
+
+
+    /* TODO: make me configurable, 
+     * see java.util.logging docs for xml config file 
+     */
+    public static Logger getLogger() {
+        if (logger == null) {
+            logger = Logger.getLogger("Hatch");
+            logger.setLevel(Level.ALL);
+            ConsoleHandler handler = new ConsoleHandler();
+            handler.setLevel(Level.ALL);
+            handler.setFormatter(new SimpleFormatter());
+            logger.addHandler(handler);
+            logger.setUseParentHandlers(false);
+        }
+        return logger;
+    }
+
 
     /**
      * Hatch main.
      *
-     * Reads the Jetty configuration, starts the Jetty server thread, 
-     * then launches the JavaFX Application thread.
      */
     public static void main(String[] args) throws Exception {
 
-        // build a server from our hatch.xml configuration file
-        XmlConfiguration configuration =
-            new XmlConfiguration(new FileInputStream("hatch.xml"));
-
-        Server server = (Server) configuration.configure();
-
-        logger.info("Starting Jetty server");
-
-        // start our server, but do not join(), since we want to server
-        // to continue running in its own thread
-        server.start();
-
-        logger.info("Launching FX Application");
+        // start the messageIO reader/writer threads
+        new MessageIO().start(); 
 
         // launch the FX Application thread
         launch(args);
+
+        System.out.println("Here");
+
     }
+
 }
diff --git a/src/org/evergreen_ils/hatch/HatchWebSocketHandler.java b/src/org/evergreen_ils/hatch/HatchWebSocketHandler.java
deleted file mode 100644 (file)
index bb2ee9c..0000000
+++ /dev/null
@@ -1,314 +0,0 @@
-/* -----------------------------------------------------------------------
- * Copyright 2014 Equinox Software, Inc.
- * Bill Erickson <berick@esilibrary.com>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * -----------------------------------------------------------------------
- */
-package org.evergreen_ils.hatch;
-
-import java.io.IOException;
-import java.io.File;
-import java.io.BufferedReader;
-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.OnWebSocketError;
-import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
-import org.eclipse.jetty.websocket.api.annotations.WebSocket;
-import javax.servlet.ServletConfig;
-
-import org.eclipse.jetty.util.ajax.JSON;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import java.util.Arrays;
-import java.util.List;
-import java.util.HashMap;
-import java.util.Map;
-
-@WebSocket
-public class HatchWebSocketHandler {
-
-    /** A single connection to a WebSockets client */
-    private Session session;
-
-    /** Current origin domain */
-    private String origin;
-
-    /** List of Origin domains from which we allow connections */
-    private static String[] trustedDomains;
-
-    /** True if we trust all Origin domains */
-    private static boolean trustAllDomains = false;
-
-    /** Root directory for all FileIO operations */
-    private static String profileDirectory;
-
-    /** Our logger instance */
-    private static final Logger logger = Log.getLogger("WebSocketHandler");
-
-    /**
-     * Apply trusted domains.
-     *
-     * If the first domain in the list equals "*", that signifies that
-     * all domains should be trusted.
-     *
-     * @param domains Array of domains to trust.
-     */
-    public static void setTrustedDomains(String[] domains) {
-        trustedDomains = domains;
-
-        if (domains.length > 0 ) {
-
-            if ("*".equals(domains[0])) {
-                logger.info("All domains trusted");
-                trustAllDomains = true;
-
-            } else {
-
-                for(String domain : trustedDomains) {
-                    logger.info("Trusted domain: " + domain);
-                }
-            }
-        } else {
-            logger.warn("No domains are trusted.  All requests will be denied");
-        }
-    }
-
-    /**
-     * Sets the profile directory
-     *
-     * @param directory Directory path as a String
-     */
-    public static void setProfileDirectory(String directory) {
-        profileDirectory = directory;
-    }
-
-
-    /**
-     * Runs the initial, global configuration for this handler.
-     * TODO: move this into setProfileDirectory() (which will need to
-     * be force-called regardless of config)?
-     */
-    public static void configure() {
-        logger.info("WebSocketHandler.configure()");
-
-        // default to ~/.evergreen
-        if (profileDirectory == null) {
-            String home = System.getProperty("user.home");
-            profileDirectory = new File(home, ".evergreen").getPath();
-            if (profileDirectory == null) {
-                logger.info("Unable to set profile directory");
-            }
-        }
-    }
-
-    /**
-     * Compares the Origin of the current WebSocket connection to the list
-     * of allowed domains to determine if the current connection should
-     * be allowed.
-     *
-     * @return True if the Origin domain is allowed, false otherwise.
-     */
-    protected boolean verifyOriginDomain() {
-        logger.info("received connection from IP " +
-            session.getRemoteAddress().getAddress());
-
-        origin = session.getUpgradeRequest().getHeader("Origin");
-
-        if (origin == null) {
-            logger.warn("No Origin header in request; Dropping connection");
-            return false;
-        }
-
-        logger.info("connection origin is " + origin);
-
-        if (trustAllDomains) return true;
-
-        if (java.util.Arrays.asList(trustedDomains).indexOf(origin) < 0) {
-            logger.warn("Request from un-trusted domain: " + origin);
-            return false;
-        }
-
-        return true;
-    }
-
-
-    /**
-     * WebSocket onConnect handler.
-     *
-     * Verify the Origin domain before any communication may take place
-     */
-    @OnWebSocketConnect
-    public void onConnect(Session session) {
-        this.session = session;
-        if (!verifyOriginDomain()) session.close();
-    }
-
-    /**
-     * WebSocket onClose handler.
-     *
-     * Clears our current session.
-     */
-    @OnWebSocketClose
-    public void onClose(int statusCode, String reason) {
-        logger.info("onClose() statusCode=" + statusCode + ", reason=" + reason);
-        this.session = null;
-    }
-
-    /**
-     * Send a message to our connected client.
-     *
-     * @param json A JSON-encodable object to send to the caller.
-     * @param msgid The message identifier
-     */
-    protected void reply(Object json, Long msgid) {
-        reply(json, msgid, true);
-    }
-
-    /**
-     * Send a message to our connected client.
-     *
-     * @param json A JSON-encodable object to send to the caller.
-     * @param msgid The message identifier
-     * @param success If false, the response will be packaged as an error 
-     * message.
-     */
-    protected void reply(Object json, Long msgid, boolean success) {
-
-        Map<String, Object> response = new HashMap<String, Object>();
-        response.put("msgid", msgid);
-
-        if (success) {
-            response.put("content", json);
-        } else {
-            response.put("error", json);
-        }
-
-        String jsonString = JSON.toString(response);
-        logger.info("replying with : " + jsonString);
-
-        try {
-            if (!success) logger.warn(jsonString);
-            session.getRemote().sendString(jsonString);
-        } catch (IOException e) {
-            logger.warn(e);
-        }
-    }
-
-    /**
-     * WebSocket onMessage handler.
-     *
-     * Processes the incoming message and passes the request off to the 
-     * necessary handler.  Messages must be encoded as JSON strings.
-     */
-    @OnWebSocketMessage
-    @SuppressWarnings("unchecked") // direct casting JSON-parsed objects
-    public void onMessage(String message) {
-        if (session == null || !session.isOpen()) return;
-        logger.info("onMessage() " + message);
-
-        HashMap<String,Object> params = null;
-
-        try {
-            params = (HashMap<String,Object>) JSON.parse(message);
-        } catch (ClassCastException e) {
-            reply("Invalid WebSockets JSON message " + message, 
-                new Long(-1), false);
-        }
-
-        Long msgid = (Long) params.get("msgid");
-        String action = (String) params.get("action");
-        String key = (String) params.get("key");
-        String value = (String) params.get("value");
-        String mime = (String) params.get("mime");
-
-        logger.info("Received request for action " + action);
-
-        // all requets require a message ID
-        if (msgid == null) {
-            reply("No msgid specified in request", msgid, false);
-            return;
-        }
-
-        // all requests require an action
-        if (action == null || action.equals("")) {
-            reply("No action specified in request", msgid, false);
-            return;
-        }
-
-        Object response = null;
-        boolean error = false;
-        FileIO io = new FileIO(profileDirectory, origin);
-
-        switch (action) {
-            case "keys":
-                response = io.keys(key);
-                break;
-
-            case "printers":
-                response = new PrintManager().getPrintersAsMaps();
-                break;
-
-            case "print":
-                // pass ourselves off to the print handler so it can reply
-                // for us after printing has completed.
-                params.put("socket", this);
-                Hatch.enqueueMessage(params);
-
-                // we don't want to return a response below, since the 
-                // FX thread will handle that for us.
-                return;
-
-            case "print-config":
-                try {
-                    response = new PrintManager().configurePrinter(params);
-                } catch(IllegalArgumentException e) {
-                    response = e.toString();
-                    error = true;
-                }
-                break;
-
-            case "get":
-                String val = io.get(key);
-                if (val != null) {
-                    // set() stores bare JSON. We must pass an 
-                    // Object to reply so that it may be embedded into
-                    // a larger JSON response object, hence the JSON.parse().
-                    try {
-                        response = JSON.parse(val);
-                    } catch(java.lang.IllegalStateException e) {
-                        error = true;
-                        response = "Error JSON-parsing stored value " + val;
-                    }
-                }
-                break;
-
-            case "remove":
-                response = io.remove(key);
-                break;
-
-            case "set" :
-                response = io.set(key, value);
-                break;
-
-            case "append" :
-                response = io.append(key, value);
-                break;
-
-            default:
-                response = "No such action: " + action;
-                error = true;
-        }
-
-        reply(response, msgid, !error);
-    }
-}
diff --git a/src/org/evergreen_ils/hatch/HatchWebSocketServlet.java b/src/org/evergreen_ils/hatch/HatchWebSocketServlet.java
deleted file mode 100644 (file)
index b7e07ec..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-/* -----------------------------------------------------------------------
- * Copyright 2014 Equinox Software, Inc.
- * Bill Erickson <berick@esilibrary.com>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * -----------------------------------------------------------------------
- */
-package org.evergreen_ils.hatch;
-
-import javax.servlet.annotation.WebServlet;
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletException;
-import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
-import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
-
-/**
- * Links HatchWebSocketHandler in as a Servlet handler.
- */
-public class HatchWebSocketServlet extends WebSocketServlet {
-
-    @Override
-    public void configure(WebSocketServletFactory factory) {
-        factory.register(HatchWebSocketHandler.class);
-    }
-
-    @Override
-    public void init(ServletConfig config) throws ServletException {
-        super.init(config); // required for WS
-        HatchWebSocketHandler.configure();
-    }
-}
-
diff --git a/src/org/evergreen_ils/hatch/MessageIO.java b/src/org/evergreen_ils/hatch/MessageIO.java
new file mode 100644 (file)
index 0000000..e040b27
--- /dev/null
@@ -0,0 +1,123 @@
+package org.evergreen_ils.hatch;
+
+import java.util.logging.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.nio.ByteBuffer;
+import javax.json.*;
+import java.io.IOException;
+
+public class MessageIO {
+
+    private LinkedBlockingQueue<JsonValue> inQueue;
+    private LinkedBlockingQueue<JsonValue> outQueue;
+    //private static Logger logger = Logger.getLogger("org.evergreen_ils.hatch");
+    private static Logger logger = Hatch.getLogger();
+
+    private MessageReader reader;
+    private MessageWriter writer;
+
+    public MessageIO() {
+        inQueue = new LinkedBlockingQueue<JsonValue>();
+        outQueue = new LinkedBlockingQueue<JsonValue>();
+        reader = new MessageReader();
+        writer = new MessageWriter();
+    }
+
+    public void start() {
+        writer.start();
+        reader.start();
+    }
+
+    class MessageReader extends Thread {
+
+        private class EndOfStreamException extends IOException {
+        }
+
+        private int bytesToInt(byte[] bytes) {
+            return 
+                  (bytes[3] << 24) & 0xff000000 
+                | (bytes[2] << 16) & 0x00ff0000
+                | (bytes[1] << 8)  & 0x0000ff00 
+                | (bytes[0] << 0)  & 0x000000ff;
+        }
+
+        private String readOneMessage() throws EndOfStreamException, IOException {
+            byte[] lenBytes = new byte[4];
+            int bytesRead = System.in.read(lenBytes);
+
+            if (bytesRead == -1) {
+                throw new EndOfStreamException();
+            }
+
+            int msgLength = bytesToInt(lenBytes);
+
+            if (msgLength == 0) {
+                throw new IOException("Inbound message is 0 bytes.  Interrupted?");
+            }
+
+            logger.info("MessageReader read message length: " + msgLength);
+
+            byte[] msgBytes = new byte[msgLength];
+
+            bytesRead = System.in.read(msgBytes);
+
+            if (bytesRead == -1) {
+                throw new EndOfStreamException();
+            }
+
+            String message = new String(msgBytes, "UTF-8");
+
+            logger.info("MessageReader read message " + message);
+
+            return message;
+        }
+
+        public void run() {
+
+            while (true) {
+
+                try {
+                    
+                    String message = readOneMessage();
+
+                } catch (EndOfStreamException eose) {
+
+                    logger.warning("STDIN closed.  MessageReader thread exiting");
+                    return;
+
+                } catch (IOException ioe) {
+                    logger.warning(ioe);
+                }
+
+                // TODO: convert to JSON
+                // TODO: push onto inQueue
+            }
+    
+        }
+    }
+
+    class MessageWriter extends Thread {
+
+        private byte[] intToBytes(int length) {
+            byte[] bytes = new byte[4];
+            bytes[0] = (byte) (length & 0xFF);
+            bytes[1] = (byte) ((length >> 8) & 0xFF);
+            bytes[2] = (byte) ((length >> 16) & 0xFF);
+            bytes[3] = (byte) ((length >> 24) & 0xFF);
+            return bytes;
+        }
+
+
+        public void writeOneMessage(String message) throws IOException {
+
+            System.out.write(intToBytes(message.length()));
+            System.out.write(message.getBytes("UTF-8"));
+            System.out.flush();
+        }
+
+        public void run() {
+
+        }
+    }
+    
+}
index 40a4003..f63d7e7 100644 (file)
@@ -15,9 +15,6 @@
  */
 package org.evergreen_ils.hatch;
 
-// logging
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
 
 // printing
 import javafx.print.*;
@@ -47,7 +44,7 @@ import java.util.LinkedHashSet;
 public class PrintManager {
 
     /** Our logger instance */
-    static final Logger logger = Log.getLogger("PrintManager");
+    //static final Logger logger = Log.getLogger("PrintManager");
 
     /**
      * Shows the print dialog, allowing the user to modify settings,
@@ -95,43 +92,45 @@ public class PrintManager {
         Map<String,Object> settings = 
             (Map<String,Object>) params.get("config");
 
+        /*
         HatchWebSocketHandler socket = 
             (HatchWebSocketHandler) params.get("socket");
+            */
 
         PrinterJob job = null;
 
         try {
             job = buildPrinterJob(settings);
         } catch(IllegalArgumentException e) {
-            socket.reply(e.toString(), msgid, false);
+            //socket.reply(e.toString(), msgid, false);
             return;
         }
 
         if (showDialog != null && showDialog.booleanValue()) {
-            logger.info("Print dialog requested");
+            //logger.info("Print dialog requested");
 
             if (!job.showPrintDialog(null)) {
                 // job canceled by user
-                logger.info("after dialog");
+                //logger.info("after dialog");
                 job.endJob();
-                socket.reply("Print job canceled", msgid);
+                //socket.reply("Print job canceled", msgid);
                 return;
             }
         } else {
-            logger.info("No print dialog requested");
+            //logger.info("No print dialog requested");
         }
 
         Thread[] all = new Thread[100];
         int count = Thread.currentThread().enumerate(all);
-        logger.info(count + " active threads in print");
-        logger.info("Thread " + Thread.currentThread().getId() + " printing...");
+        //logger.info(count + " active threads in print");
+        //logger.info("Thread " + Thread.currentThread().getId() + " printing...");
 
         engine.print(job);
-        logger.info("after print");
+        //logger.info("after print");
 
         job.endJob();
 
-        socket.reply("Print job succeeded", msgid);
+        //socket.reply("Print job succeeded", msgid);
     }
 
     /**
@@ -193,7 +192,7 @@ public class PrintManager {
         Set<Paper> papers = printerAttrs.getSupportedPapers();
         for (Paper source : papers) {
             if (source.getName().equals(paperName)) {
-                logger.info("Found matching paper for " + paperName);
+                //logger.info("Found matching paper for " + paperName);
                 paper = source;
                 break;
             }
@@ -258,7 +257,7 @@ public class PrintManager {
             // meaning no source.. meaning let the printer decide.
             for (PaperSource source : paperSources) {
                 if (source.getName().equals(paperSource)) {
-                    logger.info("matched paper source for " + paperSource);
+                    //logger.info("matched paper source for " + paperSource);
                     jobSettings.setPaperSource(source);
                     break;
                 }
@@ -267,7 +266,7 @@ public class PrintManager {
 
 
         if (pageRanges != null) {
-            logger.info("pageRanges = " + pageRanges.toString());
+            //logger.info("pageRanges = " + pageRanges.toString());
             List<PageRange> builtRanges = new LinkedList<PageRange>();
             int i = 0, start = 0, end = 0;
             do {
@@ -296,7 +295,7 @@ public class PrintManager {
         Map<String,Object> settings = new HashMap<String,Object>();
         JobSettings jobSettings = job.getJobSettings();
 
-        logger.info("Extracting print job settings from " + job);
+        //logger.info("Extracting print job settings from " + job);
 
         settings.put(
             jobSettings.collationProperty().getName(),
@@ -358,7 +357,7 @@ public class PrintManager {
             }
         }
 
-        logger.info("compiled printer properties: " + settings.toString());
+        //logger.info("compiled printer properties: " + settings.toString());
         return settings;
     }
 
@@ -397,7 +396,7 @@ public class PrintManager {
                 printer.getName().equals(defaultPrinter.getName())) {
                 printerMap.put("is-default", new Boolean(true));
             }
-            logger.info("found printer " + printer.getName());            
+            //logger.info("found printer " + printer.getName());            
         }
 
         return printerMaps;