From 5861fec4401d8b5573e8f0b6d9dee044cab73e66 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Tue, 18 Mar 2014 14:11:53 -0400 Subject: [PATCH] more websockety bits Signed-off-by: Bill Erickson --- websocket/fetch_comp_run.sh | 62 ++++++ websocket/org/evergreen_ils/hatch/EventClient.java | 46 +++++ websocket/org/evergreen_ils/hatch/EventServer.java | 44 ++++ websocket/org/evergreen_ils/hatch/EventSocket.java | 39 ++++ websocket/org/evergreen_ils/hatch/FileIO.java | 145 +++++++++++++ websocket/org/evergreen_ils/hatch/PrintDriver.java | 226 +++++++++++++++++++++ 6 files changed, 562 insertions(+) create mode 100755 websocket/fetch_comp_run.sh create mode 100644 websocket/org/evergreen_ils/hatch/EventClient.java create mode 100644 websocket/org/evergreen_ils/hatch/EventServer.java create mode 100644 websocket/org/evergreen_ils/hatch/EventSocket.java create mode 100644 websocket/org/evergreen_ils/hatch/FileIO.java create mode 100644 websocket/org/evergreen_ils/hatch/PrintDriver.java diff --git a/websocket/fetch_comp_run.sh b/websocket/fetch_comp_run.sh new file mode 100755 index 000000000..4c0b4f3c9 --- /dev/null +++ b/websocket/fetch_comp_run.sh @@ -0,0 +1,62 @@ +#!/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; diff --git a/websocket/org/evergreen_ils/hatch/EventClient.java b/websocket/org/evergreen_ils/hatch/EventClient.java new file mode 100644 index 000000000..25a903d6b --- /dev/null +++ b/websocket/org/evergreen_ils/hatch/EventClient.java @@ -0,0 +1,46 @@ +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); + } + } +} diff --git a/websocket/org/evergreen_ils/hatch/EventServer.java b/websocket/org/evergreen_ils/hatch/EventServer.java new file mode 100644 index 000000000..7966725b1 --- /dev/null +++ b/websocket/org/evergreen_ils/hatch/EventServer.java @@ -0,0 +1,44 @@ +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); + } + } +} diff --git a/websocket/org/evergreen_ils/hatch/EventSocket.java b/websocket/org/evergreen_ils/hatch/EventSocket.java new file mode 100644 index 000000000..26b3986f1 --- /dev/null +++ b/websocket/org/evergreen_ils/hatch/EventSocket.java @@ -0,0 +1,39 @@ +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); + } +} diff --git a/websocket/org/evergreen_ils/hatch/FileIO.java b/websocket/org/evergreen_ils/hatch/FileIO.java new file mode 100644 index 000000000..95fc012c1 --- /dev/null +++ b/websocket/org/evergreen_ils/hatch/FileIO.java @@ -0,0 +1,145 @@ +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 nameList = new LinkedList(); + 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]); + } +} diff --git a/websocket/org/evergreen_ils/hatch/PrintDriver.java b/websocket/org/evergreen_ils/hatch/PrintDriver.java new file mode 100644 index 000000000..5247e3818 --- /dev/null +++ b/websocket/org/evergreen_ils/hatch/PrintDriver.java @@ -0,0 +1,226 @@ +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 getPrinters() { + + List printers = new LinkedList(); + PrintService[] printServices = + PrintServiceLookup.lookupPrintServices(null, null); + + String defaultPrinter = ""; + PrintService def = PrintServiceLookup.lookupDefaultPrintService(); + if (def != null) defaultPrinter = def.getName(); + + for (PrintService service : printServices) { + HashMap printer = new HashMap(); + 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); + } +} + + -- 2.11.0