--- /dev/null
+#!/bin/bash
+set -e
+
+BASE_DIR=$PWD
+JETTY_VERSION=9.1.2.v20140210
+JETTY_DIR="jetty-distribution-$JETTY_VERSION"
+JETTY_PACKAGE="$JETTY_DIR.tar.gz"
+JETTY_UTIL_AJAX=jetty-util-ajax-$JETTY_VERSION.jar
+
+OPT_FETCH=0;
+OPT_COMPILE=0
+OPT_RUN=0
+
+DEPENDS="javax.websocket-api-1.0.jar javax-websocket-client-impl-$JETTY_VERSION.jar javax-websocket-server-impl-$JETTY_VERSION.jar jetty-http-$JETTY_VERSION.jar jetty-io-$JETTY_VERSION.jar jetty-security-$JETTY_VERSION.jar jetty-server-$JETTY_VERSION.jar jetty-servlet-$JETTY_VERSION.jar jetty-util-$JETTY_VERSION.jar servlet-api-3.1.jar websocket-api-$JETTY_VERSION.jar websocket-client-$JETTY_VERSION.jar websocket-common-$JETTY_VERSION.jar websocket-server-$JETTY_VERSION.jar websocket-servlet-$JETTY_VERSION.jar"
+
+# return here on exit
+trap "{ cd $BASE_DIR; }" EXIT
+
+function usage() {
+ echo ""
+ echo "Fetch dependencies, compile, and run Hatch:";
+ echo ""
+ echo "$0 -fcr";
+ echo ""
+}
+
+while getopts "fcrh" flag; do
+ case $flag in
+ "f") OPT_FETCH=1;;
+ "c") OPT_COMPILE=1;;
+ "r") OPT_RUN=1;;
+ "h"|*) usage;;
+ esac;
+done
+
+if [ $OPT_FETCH -eq 1 ]; then
+
+ mkdir -p lib;
+
+ if [ ! -f $JETTY_PACKAGE ]; then
+ echo "Fetching Jetty package...";
+ wget "http://carroll.aset.psu.edu/pub/eclipse/jetty/$JETTY_VERSION/dist/$JETTY_PACKAGE";
+ fi;
+
+ if [ ! -d $JETTY_DIR ]; then
+ echo "Unpacking Jetty...";
+ tar xf $JETTY_PACKAGE;
+ for jar in $DEPENDS; do
+ find $JETTY_DIR -name "$jar" -exec cp -v {} lib/ \;
+ done;
+ fi;
+fi;
+
+if [ $OPT_COMPILE -eq 1 ]; then
+ echo "Compiling..."
+ javac -Xlint:unchecked -cp "lib/*" org/evergreen_ils/hatch/*.java
+fi;
+
+if [ $OPT_RUN -eq 1 ]; then
+ echo "Running..."
+ java -cp ".:lib/*" org.evergreen_ils.hatch.EventServer
+fi;
--- /dev/null
+package org.evergreen_ils.hatch;
+
+import java.net.URI;
+import javax.websocket.ContainerProvider;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.util.component.LifeCycle;
+
+public class EventClient
+{
+ public static void main(String[] args)
+ {
+ URI uri = URI.create("ws://localhost:8080/hatch/");
+
+ try
+ {
+ WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+
+ try
+ {
+ // Attempt Connect
+ Session session = container.connectToServer(EventSocket.class,uri);
+ // Send a message
+ session.getBasicRemote().sendText("Hello");
+ // Close session
+ session.close();
+ }
+ finally
+ {
+ // Force lifecycle stop when done with container.
+ // This is to free up threads and resources that the
+ // JSR-356 container allocates. But unfortunately
+ // the JSR-356 spec does not handle lifecycles (yet)
+ if (container instanceof LifeCycle)
+ {
+ ((LifeCycle)container).stop();
+ }
+ }
+ }
+ catch (Throwable t)
+ {
+ t.printStackTrace(System.err);
+ }
+ }
+}
--- /dev/null
+package org.evergreen_ils.hatch;
+
+// Code derived from
+// https://github.com/jetty-project/embedded-jetty-websocket-examples/tree/master/javax.websocket-example
+
+import javax.websocket.server.ServerContainer;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
+
+public class EventServer
+{
+ public static void main(String[] args)
+ {
+ Server server = new Server();
+ ServerConnector connector = new ServerConnector(server);
+ connector.setPort(8080);
+ server.addConnector(connector);
+
+ // Setup the basic application "context" for this application at "/"
+ // This is also known as the handler tree (in jetty speak)
+ ServletContextHandler context =
+ new ServletContextHandler(ServletContextHandler.SESSIONS);
+ context.setContextPath("/");
+ server.setHandler(context);
+
+ try {
+ // Initialize javax.websocket layer
+ ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context);
+
+ // Add WebSocket endpoint to javax.websocket layer
+ wscontainer.addEndpoint(EventSocket.class);
+
+ server.start();
+ server.dump(System.err);
+ server.join();
+
+ } catch (Throwable t) {
+ t.printStackTrace(System.err);
+ }
+ }
+}
--- /dev/null
+package org.evergreen_ils.hatch;
+
+import javax.websocket.ClientEndpoint;
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+@ClientEndpoint
+@ServerEndpoint(value="/hatch/")
+public class EventSocket
+{
+ @OnOpen
+ public void onWebSocketConnect(Session sess)
+ {
+ System.out.println("Socket Connected: " + sess);
+ }
+
+ @OnMessage
+ public void onWebSocketText(String message)
+ {
+ System.out.println("Received TEXT message: " + message);
+ }
+
+ @OnClose
+ public void onWebSocketClose(CloseReason reason)
+ {
+ System.out.println("Socket Closed: " + reason);
+ }
+
+ @OnError
+ public void onWebSocketError(Throwable cause)
+ {
+ cause.printStackTrace(System.err);
+ }
+}
--- /dev/null
+package org.evergreen_ils.hatch;
+
+import java.io.*;
+import java.util.LinkedList;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class FileIO {
+
+ String basePath;
+ private static final Logger logger = Log.getLogger("FileIO");
+
+ public FileIO(String directory) {
+ basePath = directory;
+ }
+
+ protected File getFile(String key) {
+ File dir = new File(basePath);
+ if (!dir.exists()) {
+ if (!dir.mkdir()) {
+ logger.info("Unable to create director: " + basePath);
+ return null;
+ }
+ }
+ return new File(dir, key);
+ }
+
+ public boolean set(String key, String text) {
+ logger.info("set => " + key);
+ File file = getFile(key);
+
+ try {
+
+ // delete the file if it exists
+ if (!file.exists() && !file.createNewFile()) {
+ logger.info(
+ "Unable to create file: " + file.getCanonicalPath());
+ return false;
+ }
+
+ // destructive write (replace existing text)
+ Writer outStream = new BufferedWriter(
+ new FileWriter(file.getAbsoluteFile()));
+
+ outStream.write(text);
+ outStream.close();
+
+ } catch(IOException e) {
+ logger.warn("Error calling set() with key " + key);
+ logger.warn(e);
+ return false;
+ }
+
+ return true;
+ }
+
+ public boolean append(String key, String text) {
+ 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());
+ return false;
+ }
+
+ // non-destructive write (append)
+ Writer outStream = new BufferedWriter(
+ new FileWriter(file.getAbsoluteFile(), true));
+ outStream.write(text);
+ outStream.close();
+
+ } catch(IOException e) {
+ logger.warn("Error in append() with key " + key);
+ logger.warn(e);
+ return false;
+ }
+
+ return true;
+ }
+
+ public BufferedReader get(String key) {
+ logger.info("get => " + key);
+ File file = getFile(key);
+ if (!file.exists()) return null;
+
+ StringBuffer sbuf = new StringBuffer();
+ try {
+ return new BufferedReader(
+ new FileReader(file.getAbsoluteFile()));
+ } catch (IOException e) {
+ logger.warn("Error reading key: " + key);
+ logger.warn(e);
+ return null;
+ }
+ }
+
+ public boolean delete(String key) {
+ logger.info("delete => " + key);
+ File file = getFile(key);
+ try {
+ if (file.exists() && !file.delete()) {
+ 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;
+ }
+ }
+
+ public String[] keys() {
+ return keys(null);
+ }
+
+ public String[] keys(String prefix) {
+ logger.info("keys => " + prefix);
+ File dir = new File(basePath);
+ if (!dir.exists()) return new String[0];
+
+ LinkedList<String> nameList = new LinkedList<String>();
+ File[] files = dir.listFiles();
+
+ for (File file : files) {
+ if (file.isFile()) {
+ String name = file.getName();
+ if (prefix == null) {
+ nameList.add(name);
+ } else {
+ if (name.startsWith(prefix)) {
+ nameList.add(name);
+ }
+ }
+ }
+ }
+
+ return (String[]) nameList.toArray(new String[0]);
+ }
+}
--- /dev/null
+package org.evergreen_ils.hatch;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.awt.print.*;
+
+import javax.print.PrintService;
+import javax.print.PrintServiceLookup;
+import javax.print.attribute.Attribute;
+import javax.print.attribute.AttributeSet;
+
+import java.awt.font.FontRenderContext;
+import java.awt.font.LineBreakMeasurer;
+import java.awt.font.TextAttribute;
+import java.awt.font.TextLayout;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.text.AttributedString;
+
+import java.util.Map;
+import java.util.List;
+import java.util.HashMap;
+import java.util.LinkedList;
+
+public class PrintDriver implements Printable {
+
+ private String printText;
+ private static final Logger logger = Log.getLogger("PrintDriver");
+
+ private final static int POINTS_PER_INCH = 72;
+
+ public int print(Graphics g, PageFormat pf, int page)
+ throws PrinterException {
+
+ // for now, assume we only have one page
+ if (page > 0) return NO_SUCH_PAGE;
+
+ // find the imageable area
+ Graphics2D g2d = (Graphics2D)g;
+ g2d.translate(pf.getImageableX(), pf.getImageableY());
+
+ /* --------------------------------------------- */
+ /* -- rendering the print text as paragraph --- */
+ Point2D.Double pen = new Point2D.Double(
+ 0.25 * POINTS_PER_INCH, 0.25 * POINTS_PER_INCH);
+ double width = 7.5 * POINTS_PER_INCH;
+ AttributedString paragraphText = new AttributedString(printText);
+
+ LineBreakMeasurer lineBreaker = new LineBreakMeasurer(
+ paragraphText.getIterator(), new FontRenderContext(null, true, true));
+
+ //--- Create the TextLayout object
+ TextLayout layout;
+
+ //--- LineBreakMeasurer will wrap each line to correct length and
+ //--- return it as a TextLayout object
+ while ((layout = lineBreaker.nextLayout((float) width)) != null) {
+ //--- Align the Y pen to the ascend of the font, remember that
+ //--- the ascend is origin (0, 0) of a font. Refer to figure 1
+ pen.y += layout.getAscent();
+
+ //--- Draw the line of text
+ layout.draw(g2d, (float) pen.x, (float) pen.y);
+
+ //--- Move the pen to the next position adding the descent and
+ //--- the leading of the font
+ pen.y += layout.getDescent() + layout.getLeading();
+ }
+ /* -------------------------------------------- */
+
+ /*
+ int x = 5;
+ int y = 5;
+ for (String line : printText.split("\n"))
+ g.drawString(line, x, y += g.getFontMetrics().getHeight());
+ */
+
+ return PAGE_EXISTS;
+ }
+
+ /**
+ * Spawns standard JAVA-driven print dialog and prints text
+ */
+ public boolean printWithDialog(String key, String text) {
+ debugPrintService(null); // testing
+ printText = text;
+ PrinterJob job = PrinterJob.getPrinterJob();
+ job.setPrintable(this);
+ if (!job.printDialog()) return true; // print canceled by user
+ try {
+ job.print();
+ } catch (PrinterException ex) {
+ logger.warn("Error printing document for key " + key);
+ logger.warn(ex);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Print using defaults
+ *
+ * Sends the print job to the configured printer based on the key
+ * and user settings.
+ */
+ public boolean printWithoutDialog(String key, String text) {
+ printText = text;
+ PrinterJob job = PrinterJob.getPrinterJob();
+ job.setPrintable(this);
+
+ // TODO: load user settings, find the right printer, send the
+ // correct attributes, etc.
+
+ try {
+ job.print();
+ } catch (PrinterException ex) {
+ logger.warn("Error printing document for key " + key);
+ logger.warn(ex);
+ return false;
+ }
+ return true;
+ }
+
+ private void debugPrintService(PrintService printer) {
+
+ PrintService[] printServices;
+ String defaultPrinter = "";
+
+ if (printer != null) {
+ printServices = new PrintService[] {printer};
+ } else {
+ printServices = PrintServiceLookup.lookupPrintServices(null, null);
+ PrintService def = PrintServiceLookup.lookupDefaultPrintService();
+ if (def != null) defaultPrinter = def.getName();
+ }
+
+ for (PrintService service : printServices) {
+ logger.info("Printer Debug: found printer " + service.getName());
+ if (service.getName().equals(defaultPrinter)) {
+ logger.info(" Printer Debug: Is Default");
+ }
+
+ AttributeSet attributes = service.getAttributes();
+ for (Attribute a : attributes.toArray()) {
+ String name = a.getName();
+ String value = attributes.get(a.getClass()).toString();
+ logger.info(" Printer Debug: " + name + " => " + value);
+ }
+ }
+ }
+
+ public List<HashMap> getPrinters() {
+
+ List<HashMap> printers = new LinkedList<HashMap>();
+ PrintService[] printServices =
+ PrintServiceLookup.lookupPrintServices(null, null);
+
+ String defaultPrinter = "";
+ PrintService def = PrintServiceLookup.lookupDefaultPrintService();
+ if (def != null) defaultPrinter = def.getName();
+
+ for (PrintService service : printServices) {
+ HashMap<String, Object> printer = new HashMap<String, Object>();
+ printers.add(printer);
+
+ if (service.getName().equals(defaultPrinter))
+ printer.put("is-default", new Boolean(true));
+
+ AttributeSet attributes = service.getAttributes();
+ for (Attribute a : attributes.toArray()) {
+ String name = a.getName();
+ String value = attributes.get(a.getClass()).toString();
+ printer.put(name, value);
+ }
+ }
+
+ return printers;
+ }
+
+ // experiment
+ // show our own print dialog before the real print action takes over
+ // currently just shows Print and Cancel.
+ // Not sure if there is a need for such a thing..
+ public void printWithCustomDialog(String key, String msg) {
+ UIManager.put("swing.boldMetal", Boolean.FALSE);
+ JFrame f = new JFrame("Hello World Printer");
+
+ // close the frame when Cancel / X are clicked
+ f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+
+ final String printMsg = msg;
+ final String printKey = key;
+ JButton printButton = new JButton("Print '" + msg + "'");
+ printButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ printWithDialog(printKey, printMsg);
+ }
+ });
+
+ JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Component component = (Component) e.getSource();
+ JFrame frame = (JFrame) SwingUtilities.getRoot(component);
+ WindowEvent windowClosing =
+ new WindowEvent(frame, WindowEvent.WINDOW_CLOSING);
+ frame.dispatchEvent(windowClosing);
+ }
+ });
+
+ JPanel grid = new JPanel(new FlowLayout(FlowLayout.LEFT,3,3));
+ f.add(grid);
+ grid.add(cancelButton);
+ grid.add(printButton);
+ f.pack();
+ f.setVisible(true);
+ }
+}
+
+