** 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 org.json jar from and place into lib (modify run.sh CP)
+https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.json%22%20AND%20a%3A%22json%22
# compile
% ./run.sh
# 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
+++ /dev/null
-<?xml version="1.0"?>
-<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
-
-<Configure id="Server" class="org.eclipse.jetty.server.Server">
-
- <!--
- <Get id="Logger" class="org.eclipse.jetty.util.log.Log" name="log"/>
- <Ref id="Logger">
- <Set name="debugEnabled">true</Set>
- </Ref>
- -->
-
- <Set class="org.evergreen_ils.hatch.HatchWebSocketHandler" name="trustedDomains">
- <Array type="String">
- <!--
- List of origin domains which are allowed to connect to Hatch.
- If the first item in the list is "*", then all domains are
- trusted, which is useful for testing.
- -->
- <Item>*</Item>
- </Array>
- </Set>
-
- <!--
- <Set class="org.evergreen_ils.hatch.HatchWebSocketHandler"
- name="profileDirectory"></Set>
- -->
-
- <!-- basic HTTP setup -->
- <New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
- <Set name="secureScheme">https</Set>
- <Set name="securePort"><Property name="jetty.secure.port" default="8443" /></Set>
- <Set name="outputBufferSize"><Property name="jetty.output.buffer.size" default="32768" /></Set>
- <Set name="requestHeaderSize"><Property name="jetty.request.header.size" default="8192" /></Set>
- <Set name="responseHeaderSize"><Property name="jetty.response.header.size" default="8192" /></Set>
- <Set name="sendServerVersion"><Property name="jetty.send.server.version" default="true" /></Set>
- <Set name="sendDateHeader"><Property name="jetty.send.date.header" default="false" /></Set>
- <Set name="headerCacheSize">512</Set>
- </New>
-
- <!-- SSL configuration -->
- <!-- Using the stock Jetty certificates for now.
- To set a temporary trust on the cert, navigate to
- https://<hostname>:8443/ and confirm the cert is trusted -->
- <New id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
- <!-- TODO: make this better -->
- <Set name="KeyStorePath"><Property name="jetty.home" default="." />/jetty/etc/keystore</Set>
- <Set name="KeyStorePassword">password</Set>
- <Set name="KeyManagerPassword">password</Set>
- <Set name="TrustStorePath"><Property name="jetty.home" default="." />/jetty/etc/keystore</Set>
- <Set name="TrustStorePassword">password</Set>
- </New>
-
- <New id="sslHttpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
- <Arg><Ref refid="httpConfig"/></Arg>
- <Call name="addCustomizer">
- <Arg><New class="org.eclipse.jetty.server.SecureRequestCustomizer"/></Arg>
- </Call>
- </New>
-
- <!-- SSL HTTP connector -->
- <Call name="addConnector">
- <Arg>
- <New class="org.eclipse.jetty.server.ServerConnector">
- <Arg name="server"><Ref refid="Server" /></Arg>
- <Arg name="factories">
- <Array type="org.eclipse.jetty.server.ConnectionFactory">
- <Item>
- <New class="org.eclipse.jetty.server.SslConnectionFactory">
- <Arg name="next">http/1.1</Arg>
- <Arg name="sslContextFactory"><Ref refid="sslContextFactory"/></Arg>
- </New>
- </Item>
- <Item>
- <New class="org.eclipse.jetty.server.HttpConnectionFactory">
- <Arg name="config"><Ref refid="sslHttpConfig"/></Arg>
- </New>
- </Item>
- </Array>
- </Arg>
- <Set name="host"><Property name="jetty.host" /></Set>
- <Set name="port"><Property name="jetty.secure.port" default="8443" /></Set>
- <Set name="idleTimeout"><Property name="https.timeout" default="30000"/></Set>
- <Set name="soLingerTime"><Property name="https.soLingerTime" default="-1"/></Set>
- </New>
- </Arg>
- </Call>
-
- <!-- HTTP connector -->
- <Call name="addConnector">
- <Arg>
- <New class="org.eclipse.jetty.server.ServerConnector">
- <Arg name="server">
- <Ref refid="Server"/>
- </Arg>
- <Arg name="factories">
- <Array type="org.eclipse.jetty.server.ConnectionFactory">
- <Item>
- <New class="org.eclipse.jetty.server.HttpConnectionFactory">
- <Arg name="config"><Ref refid="httpConfig" /></Arg>
- </New>
- </Item>
- </Array>
- </Arg>
- <Set name="host"><Property name="jetty.host"/></Set>
- <Set name="port"><Property name="jetty.port" default="8080"/></Set>
- <Set name="idleTimeout"><Property name="http.timeout" default="30000"/></Set>
- <Set name="soLingerTime"><Property name="http.soLingerTime" default="-1"/></Set>
- </New>
- </Arg>
- </Call>
-
- <!-- TODO get properties working for:
- jetty.proxy.maxThreads
- jetty.proxy.maxConnections
- jetty.proxy.idleTimeout
- jetty.proxy.timeout
- -->
-
-
- <!-- wrap our websocketservlet into something the server can run -->
- <New id="context" class="org.eclipse.jetty.servlet.ServletContextHandler">
- <Set name="contextPath">/</Set>
- <Call name="addServlet">
- <Arg>org.evergreen_ils.hatch.HatchWebSocketServlet</Arg>
- <Arg>/hatch</Arg>
- </Call>
- </New>
-
- <!-- set our websocket handler as the server handler -->
- <Set name="handler">
- <New class="org.eclipse.jetty.server.handler.HandlerCollection">
- <Set name="handlers">
- <Array type="org.eclipse.jetty.server.Handler">
- <Item> <Ref refid="context" /> </Item>
- <Item>
- <New class="org.eclipse.jetty.server.handler.DefaultHandler" />
- </Item>
- </Array>
- </Set>
- </New>
- </Set>
-
-</Configure>
JAVA_HOME=jdk1.8
-JETTY_HOME=jetty
+CP=lib:lib/json-20160810.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
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 {
// --------------------------------------------------
// 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.
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;
}
}
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;
}
* @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;
// 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;
}
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;
}
* @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;
}
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;
}
* @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;
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;
}
* @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;
+ //}
}
/**
* @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())
*/
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 java.util.Map;
+import java.util.logging.*;
+import org.json.*;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.layout.Region;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
-import javafx.scene.Node;
import javafx.stage.Stage;
-import javafx.scene.transform.Scale;
import javafx.beans.value.ChangeListener;
-import javafx.concurrent.Worker;
import javafx.concurrent.Worker.State;
-import javafx.concurrent.Service;
-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.Map;
-
-import java.io.FileInputStream;
/**
* Main class for Hatch.
*
- * This class operates as a two-headed beast, whose heads will occasionally
- * communicate with each other.
- *
- * It runs a JavaFX thread for printing HTML documents and runs a Jetty
- * server thread for handling communication with external clients.
- *
- * Most of the work performed happens solely in the Jetty server thread.
- * Attempts to print, however, are passed into the JavaFX thread so that
- * the HTML may be loaded into a WebView for printing, which must happen
- * within the JavaFX thread.
- *
- * Messages are passed from the Jetty thread to the JavaFX thread via a
- * blocking thread queue, observed by a separate Service thread, whose
- * job is only to pull messages from the queue.
+ * TODO
*
* Beware: On Mac OS, the "FX Application Thread" is renamed to
* "AppKit Thread" when the first call to print() or showPrintDialog()
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();
}
/**
- * 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);
}
/**
String contentType = (String) params.get("contentType");
if (content == null) {
- logger.warn("handlePrint() called with no content");
+ logger.warning("handlePrint() called with no content");
return;
}
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.
+ /* TODO: make me configurable via config file
*/
- public void startMsgTask() {
+ public static Logger getLogger() {
+ if (logger != null) return logger;
- MsgListenService service = new MsgListenService();
+ logger = Logger.getLogger("org.evergreen_ils.hatch");
+ logger.setLevel(Level.ALL);
- logger.info("starting MsgTask");
+ ConsoleHandler handler = new ConsoleHandler();
+ handler.setLevel(Level.ALL);
+ handler.setFormatter(new SimpleFormatter());
+ logger.addHandler(handler);
- service.setOnSucceeded(
- new EventHandler<WorkerStateEvent>() {
+ logger.setUseParentHandlers(false);
- @Override
- public void handle(WorkerStateEvent t) {
- logger.info("MsgTask handling message.. ");
- Map<String,Object> message =
- (Map<String,Object>) t.getSource().getValue();
+ return logger;
+ }
- // 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);
- }
+ /*
+ 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;
- service.start();
- }
+ case "remove":
+ response = io.remove(key);
+ break;
- /**
- * 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 {
+ case "set" :
+ response = io.set(key, value);
+ break;
- // build a server from our hatch.xml configuration file
- XmlConfiguration configuration =
- new XmlConfiguration(new FileInputStream("hatch.xml"));
+ case "append" :
+ response = io.append(key, value);
+ break;
- Server server = (Server) configuration.configure();
+ default:
+ response = "No such action: " + action;
+ error = true;
+ }
- logger.info("Starting Jetty server");
+ reply(response, msgid, !error);
+ }
+ */
- // 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");
- // launch the FX Application thread
- launch(args);
+ /**
+ * Hatch main.
+ *
+ */
+ public static void main(String[] args) throws Exception {
+ new RequestHandler().start();
+ launch(args); // launch the FX Application thread
}
}
+++ /dev/null
-/* -----------------------------------------------------------------------
- * 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);
- }
-}
+++ /dev/null
-/* -----------------------------------------------------------------------
- * 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();
- }
-}
-
--- /dev/null
+package org.evergreen_ils.hatch;
+
+import java.util.logging.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.nio.ByteBuffer;
+import java.io.IOException;
+import org.json.*;
+
+
+public class MessageIO {
+
+ private LinkedBlockingQueue<JSONObject> inQueue;
+ private LinkedBlockingQueue<JSONObject> outQueue;
+ static final Logger logger = Hatch.getLogger();
+
+ private MessageReader reader;
+ private MessageWriter writer;
+
+ public MessageIO() {
+ inQueue = new LinkedBlockingQueue<JSONObject>();
+ outQueue = new LinkedBlockingQueue<JSONObject>();
+ reader = new MessageReader();
+ writer = new MessageWriter();
+ }
+
+ public void listen() {
+ writer.start();
+ reader.start();
+ }
+
+ public JSONObject recvMessage() {
+ while (true) {
+ try {
+ return inQueue.take();
+ } catch (InterruptedException e) {}
+ }
+ }
+
+ public void sendMessage(JSONObject msg) {
+ outQueue.offer(msg);
+ }
+
+ class EndOfStreamException extends IOException { }
+
+ class MessageReader extends Thread {
+
+ 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) {
+
+ String message = "";
+ JSONObject jsonMsg = null;
+
+ try {
+
+ message = readOneMessage();
+ jsonMsg = new JSONObject(message);
+
+ } catch (EndOfStreamException eose) {
+
+ logger.warning("STDIN closed... exiting");
+ System.exit(1);
+
+ } catch (IOException ioe) {
+ logger.warning(ioe.toString());
+
+ } catch (JSONException je) {
+
+ logger.warning("Error parsing JSON message on STDIN " +
+ je.toString() + " : " + message);
+
+ continue;
+ }
+
+ inQueue.offer(jsonMsg);
+
+ logger.info("inQueue contains " + inQueue.size() + " messages");
+ }
+ }
+ }
+
+ 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() {
+
+ while (true) {
+ logger.info("MessageWriter waiting for outQueue message");
+
+ try {
+
+ // take() blocks the thread until a message is available
+ JSONObject jsonMsg = outQueue.take();
+
+ writeOneMessage(jsonMsg.toString());
+
+ } catch (InterruptedException e) {
+ // interrupted, go back and listen
+ continue;
+ } catch (IOException ioe) {
+ logger.warning(
+ "Error writing message to STDOUT: " + ioe.toString());
+ }
+ }
+ }
+ }
+}
+
*/
package org.evergreen_ils.hatch;
-// logging
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
// printing
import javafx.print.*;
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,
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);
}
/**
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;
}
// 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;
}
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 {
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(),
}
}
- logger.info("compiled printer properties: " + settings.toString());
+ //logger.info("compiled printer properties: " + settings.toString());
return settings;
}
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;
--- /dev/null
+package org.evergreen_ils.hatch;
+
+import org.json.*;
+import java.util.logging.*;
+
+public class RequestHandler extends Thread {
+ private MessageIO io;
+ static final Logger logger = Hatch.getLogger();
+
+ public RequestHandler() {
+ io = new MessageIO();
+ }
+
+ void dispatchRequest(JSONObject request) throws JSONException {
+
+ long msgid = request.getLong("msgid");
+ String action = request.getString("action");
+ /*
+ String key = request.getString("key");
+ String content = request.getString("content");
+ String contentType = request.getString("contentType");
+ */
+ boolean showDialog = request.optBoolean("showDialog");
+
+ logger.info("Received message action: " + action);
+ }
+
+
+ public void run() {
+
+ io.listen(); // STDIN/STDOUT handler
+
+ while (true) {
+ try {
+ dispatchRequest(io.recvMessage());
+ } catch (JSONException je) {
+ logger.warning(
+ "JSON request protocol error: " + je.toString());
+ }
+ }
+ }
+}
+