1 /* -----------------------------------------------------------------------
2 * Copyright 2014 Equinox Software, Inc.
3 * Bill Erickson <berick@esilibrary.com>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 * -----------------------------------------------------------------------
16 package org.evergreen_ils.hatch;
18 import org.eclipse.jetty.util.log.Log;
19 import org.eclipse.jetty.util.log.Logger;
21 import javax.servlet.ServletException;
22 import javax.servlet.http.HttpServlet;
23 import javax.servlet.http.HttpServletRequest;
24 import javax.servlet.http.HttpServletResponse;
25 import org.eclipse.jetty.server.Server;
26 import org.eclipse.jetty.servlet.ServletHandler;
27 import org.eclipse.jetty.servlet.ServletContextHandler;
28 import org.eclipse.jetty.servlet.ServletHolder;
29 import org.eclipse.jetty.server.handler.ContextHandler;
30 import org.eclipse.jetty.server.Handler;
31 import org.eclipse.jetty.server.handler.HandlerList;
32 import org.eclipse.jetty.util.resource.Resource;
33 import org.eclipse.jetty.xml.XmlConfiguration;
35 import javafx.application.Application;
36 import javafx.application.Platform;
37 import javafx.scene.Scene;
38 import javafx.scene.layout.Region;
39 import javafx.scene.web.WebEngine;
40 import javafx.scene.web.WebView;
41 import javafx.scene.Node;
42 import javafx.stage.Stage;
43 import javafx.scene.transform.Scale;
44 import javafx.beans.value.ChangeListener;
45 import javafx.concurrent.Worker;
46 import javafx.concurrent.Worker.State;
47 import javafx.concurrent.Service;
48 import javafx.concurrent.Task;
49 import javafx.event.EventHandler;
50 import javafx.concurrent.WorkerStateEvent;
51 import java.util.concurrent.LinkedBlockingQueue;
53 import org.eclipse.jetty.util.ajax.JSON;
57 import java.io.FileInputStream;
60 * Main class for Hatch.
62 * This class operates as a two-headed beast, whose heads will occasionally
63 * communicate with each other.
65 * It runs a JavaFX thread for printing HTML documents and runs a Jetty
66 * server thread for handling communication with external clients.
68 * Most of the work performed happens solely in the Jetty server thread.
69 * Attempts to print, however, are passed into the JavaFX thread so that
70 * the HTML may be loaded into a WebView for printing, which must happen
71 * within the JavaFX thread.
73 * Messages are passed from the Jetty thread to the JavaFX thread via a
74 * blocking thread queue, observed by a separate Service thread, whose
75 * job is only to pull messages from the queue.
77 * Beware: On Mac OS, the "FX Application Thread" is renamed to
78 * "AppKit Thread" when the first call to print() or showPrintDialog()
79 * [in PrintManager] is made. This is highly confusing when viewing logs.
82 public class Hatch extends Application {
84 /** Browser Region for rendering and printing HTML */
85 private BrowserView browser;
87 /** BrowserView requires a stage for rendering */
88 private Stage primaryStage;
90 /** Our logger instance */
91 static final Logger logger = Log.getLogger("Hatch");
93 /** Message queue for passing messages from the Jetty thread into
94 * the JavaFX Application thread */
95 private static LinkedBlockingQueue<Map> requestQueue =
96 new LinkedBlockingQueue<Map>();
99 * Printable region containing a browser
101 class BrowserView extends Region {
102 WebView webView = new WebView();
103 WebEngine webEngine = webView.getEngine();
104 public BrowserView() {
105 getChildren().add(webView);
110 * Service task which listens for inbound messages from the
113 * The code blocks on the concurrent queue, so it must be
114 * run in a separate thread to avoid locking the main FX thread.
116 private static class MsgListenService extends Service<Map<String,Object>> {
117 protected Task<Map<String,Object>> createTask() {
118 return new Task<Map<String,Object>>() {
119 protected Map<String,Object> call() {
121 logger.info("MsgListenService waiting for a message...");
123 // take() blocks until a message is available
124 return requestQueue.take();
125 } catch (InterruptedException e) {
126 // interrupted, go back and listen
137 * JavaFX startup call
140 public void start(Stage primaryStage) {
141 this.primaryStage = primaryStage;
146 * Queues a message for processing by the queue processing thread.
148 public static void enqueueMessage(Map<String,Object> params) {
149 logger.debug("queueing print message");
150 requestQueue.offer(params);
154 * Build a browser view from the print content, tell the
155 * browser to print itself.
157 private void handlePrint(Map<String,Object> params) {
158 String content = (String) params.get("content");
159 String contentType = (String) params.get("contentType");
161 if (content == null) {
162 logger.warn("handlePrint() called with no content");
166 browser = new BrowserView();
167 Scene scene = new Scene(browser);
168 primaryStage.setScene(scene);
170 browser.webEngine.getLoadWorker()
172 .addListener( (ChangeListener) (obsValue, oldState, newState) -> {
173 logger.info("browser load state " + newState);
174 if (newState == State.SUCCEEDED) {
175 logger.info("Print browser page load completed");
177 // Avoid nested UI event loops -- runLater
178 Platform.runLater(new Runnable() {
179 @Override public void run() {
180 new PrintManager().print(browser.webEngine, params);
186 logger.info("printing " + content.length() + " bytes of " + contentType);
187 browser.webEngine.loadContent(content, contentType);
189 // After queueing up the HTML for printing, go back to listening
195 * Fire off the Service task, which checks for queued messages.
197 * When a queued message is found, it's sent off for printing.
199 public void startMsgTask() {
201 MsgListenService service = new MsgListenService();
203 logger.info("starting MsgTask");
205 service.setOnSucceeded(
206 new EventHandler<WorkerStateEvent>() {
209 public void handle(WorkerStateEvent t) {
210 logger.info("MsgTask handling message.. ");
211 Map<String,Object> message =
212 (Map<String,Object>) t.getSource().getValue();
214 // avoid nesting UI event loops by kicking off the print
215 // operation from the main FX loop after this event handler
219 @Override public void run() {
220 handlePrint(message);
233 * Reads the Jetty configuration, starts the Jetty server thread,
234 * then launches the JavaFX Application thread.
236 public static void main(String[] args) throws Exception {
238 // build a server from our hatch.xml configuration file
239 XmlConfiguration configuration =
240 new XmlConfiguration(new FileInputStream("hatch.xml"));
242 Server server = (Server) configuration.configure();
244 logger.info("Starting Jetty server");
246 // start our server, but do not join(), since we want to server
247 // to continue running in its own thread
250 logger.info("Launching FX Application");
252 // launch the FX Application thread