]> git.evergreen-ils.org Git - working/Hatch.git/blob - src/org/evergreen_ils/hatch/Hatch.java
Rearrange hatch.xml to support jetty 9.3
[working/Hatch.git] / src / org / evergreen_ils / hatch / Hatch.java
1 /* -----------------------------------------------------------------------
2  * Copyright 2014 Equinox Software, Inc.
3  * Bill Erickson <berick@esilibrary.com>
4  *
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.
9  *
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  * -----------------------------------------------------------------------
15  */
16 package org.evergreen_ils.hatch;
17
18 import org.eclipse.jetty.util.log.Log;
19 import org.eclipse.jetty.util.log.Logger;
20
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;
34
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;
52
53 import org.eclipse.jetty.util.ajax.JSON;
54
55 import java.util.Map;
56
57 import java.io.FileInputStream;
58
59 /**
60  * Main class for Hatch.
61  *
62  * This class operates as a two-headed beast, whose heads will occasionally
63  * communicate with each other.
64  *
65  * It runs a JavaFX thread for printing HTML documents and runs a Jetty
66  * server thread for handling communication with external clients.
67  *
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.
72  *
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.
76  *
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.
80  *
81  */
82 public class Hatch extends Application {
83
84     /** Browser Region for rendering and printing HTML */
85     private BrowserView browser;
86
87     /** BrowserView requires a stage for rendering */
88     private Stage primaryStage;
89     
90     /** Our logger instance */
91     static final Logger logger = Log.getLogger("Hatch");
92
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>();
97
98     /**
99      * Printable region containing a browser
100      */
101     class BrowserView extends Region {
102         WebView webView = new WebView();
103         WebEngine webEngine = webView.getEngine();
104         public BrowserView() {
105             getChildren().add(webView);
106         }
107     }
108
109     /**
110      * Service task which listens for inbound messages from the
111      * servlet.
112      *
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.
115      */
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() {
120                     while (true) {
121                         logger.info("MsgListenService waiting for a message...");
122                         try {
123                             // take() blocks until a message is available
124                             return requestQueue.take();
125                         } catch (InterruptedException e) {
126                             // interrupted, go back and listen
127                             continue;
128                         }
129                     }
130                 }
131             };
132         }
133     }
134
135
136     /**
137      * JavaFX startup call
138      */
139     @Override
140     public void start(Stage primaryStage) {
141         this.primaryStage = primaryStage;
142         startMsgTask();
143     }
144
145     /**
146      * Queues a message for processing by the queue processing thread.
147      */
148     public static void enqueueMessage(Map<String,Object> params) {
149         logger.debug("queueing print message");
150         requestQueue.offer(params);
151     }
152
153     /**
154      * Build a browser view from the print content, tell the
155      * browser to print itself.
156      */
157     private void handlePrint(Map<String,Object> params) {
158         String content = (String) params.get("content");
159         String contentType = (String) params.get("contentType");
160
161         if (content == null) {
162             logger.warn("handlePrint() called with no content");
163             return;
164         }
165
166         browser = new BrowserView();
167         Scene scene = new Scene(browser);
168         primaryStage.setScene(scene);
169
170         browser.webEngine.getLoadWorker()
171             .stateProperty()
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");
176
177                     // Avoid nested UI event loops -- runLater
178                     Platform.runLater(new Runnable() {
179                         @Override public void run() {
180                             new PrintManager().print(browser.webEngine, params);
181                         }
182                     });
183                 }
184             });
185
186         logger.info("printing " + content.length() + " bytes of " + contentType);
187         browser.webEngine.loadContent(content, contentType);
188
189         // After queueing up the HTML for printing, go back to listening
190         // for new messages.
191         startMsgTask();
192     }
193
194     /**
195      * Fire off the Service task, which checks for queued messages.
196      *
197      * When a queued message is found, it's sent off for printing.
198      */
199     public void startMsgTask() {
200
201         MsgListenService service = new MsgListenService();
202
203         logger.info("starting MsgTask");
204
205         service.setOnSucceeded(
206             new EventHandler<WorkerStateEvent>() {
207
208             @Override
209             public void handle(WorkerStateEvent t) {
210                 logger.info("MsgTask handling message.. ");
211                 Map<String,Object> message = 
212                     (Map<String,Object>) t.getSource().getValue();
213
214                 // avoid nesting UI event loops by kicking off the print
215                 // operation from the main FX loop after this event handler 
216                 // has exited.
217                 Platform.runLater(
218                     new Runnable() {
219                         @Override public void run() {
220                             handlePrint(message);
221                         }
222                     }
223                 );
224             }
225         });
226
227         service.start();
228     }
229
230     /**
231      * Hatch main.
232      *
233      * Reads the Jetty configuration, starts the Jetty server thread, 
234      * then launches the JavaFX Application thread.
235      */
236     public static void main(String[] args) throws Exception {
237
238         // build a server from our hatch.xml configuration file
239         XmlConfiguration configuration =
240             new XmlConfiguration(new FileInputStream("hatch.xml"));
241
242         Server server = (Server) configuration.configure();
243
244         logger.info("Starting Jetty server");
245
246         // start our server, but do not join(), since we want to server
247         // to continue running in its own thread
248         server.start();
249
250         logger.info("Launching FX Application");
251
252         // launch the FX Application thread
253         launch(args);
254     }
255 }