]> git.evergreen-ils.org Git - working/Hatch.git/blob - src/org/evergreen_ils/hatch/HatchWebSocketHandler.java
Hatch: use jetty XML for all configuration; SSL recovered.
[working/Hatch.git] / src / org / evergreen_ils / hatch / HatchWebSocketHandler.java
1 package org.evergreen_ils.hatch;
2
3 import java.io.IOException;
4 import java.io.File;
5 import java.io.BufferedReader;
6 import org.eclipse.jetty.websocket.api.Session;
7 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
8 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
9 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
10 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
11 import org.eclipse.jetty.websocket.api.annotations.WebSocket;
12 import javax.servlet.ServletConfig;
13
14 import org.eclipse.jetty.util.ajax.JSON;
15 import org.eclipse.jetty.util.log.Log;
16 import org.eclipse.jetty.util.log.Logger;
17 import java.util.Arrays;
18 import java.util.List;
19 import java.util.HashMap;
20 import java.util.Map;
21
22 @WebSocket
23 public class HatchWebSocketHandler {
24
25     private Session session;
26     private static String[] trustedDomains;
27     private static boolean trustAllDomains = false;
28     private static String profileDirectory;
29     private static final Logger logger = Log.getLogger("WebSocketHandler");
30
31     public static void setTrustedDomains(String[] domains) {
32         trustedDomains = domains;
33
34         if (domains.length > 0 ) {
35
36             if ("*".equals(domains[0])) {
37                 logger.info("All domains trusted");
38                 trustAllDomains = true;
39
40             } else {
41
42                 for(String domain : trustedDomains) {
43                     logger.info("Trusted domain: " + domain);
44                 }
45             }
46         } else {
47             logger.warn("No domains are trusted");
48         }
49     }
50
51     public static void setProfileDirectory(String directory) {
52         profileDirectory = directory;
53     }
54
55
56     /**
57      * config is passed in from our WebSocketServlet container, 
58      * hence the public+static.  Possible to access directly?
59      */
60     //public static void configure(ServletConfig config) {
61     public static void configure() {
62         logger.info("WebSocketHandler.configure()");
63
64         // default to ~/.evergreen
65         if (profileDirectory == null) {
66             String home = System.getProperty("user.home");
67             profileDirectory = new File(home, ".evergreen").getPath();
68             if (profileDirectory == null) {
69                 logger.info("Unable to set profile directory");
70             }
71         }   
72     }  
73
74     protected boolean verifyOriginDomain() {
75         logger.info("received connection from IP " + 
76             session.getRemoteAddress().getAddress());
77
78         String origin = session.getUpgradeRequest().getHeader("Origin");
79
80         if (origin == null) {
81             logger.warn("No Origin header in request; Dropping connection");
82             return false;
83         } 
84
85         logger.info("connection origin is " + origin);
86
87         if (trustAllDomains) return true;
88
89         if (java.util.Arrays.asList(trustedDomains).indexOf(origin) < 0) {
90             logger.warn("Request from un-trusted domain: " + origin);
91             return false;
92         }
93
94         return true;
95     }
96
97
98     @OnWebSocketConnect
99     public void onConnect(Session session) {
100         this.session = session;
101         if (!verifyOriginDomain()) session.close();
102     }
103
104     @OnWebSocketClose
105     public void onClose(int statusCode, String reason) {
106         logger.info("onClose() statusCode=" + statusCode + ", reason=" + reason);
107         this.session = null;
108     }
109
110     private void reply(Object json, String msgid) {
111         reply(json, msgid, true);
112     }
113
114     private void reply(Object json, String msgid, boolean success) {
115
116         Map<String, Object> response = new HashMap<String, Object>();
117         response.put("msgid", msgid);
118         if (success) {
119             response.put("success", json);
120         } else {
121             response.put("error", json);
122         }
123
124         try {
125             String jsonString = JSON.toString(response);
126             if (!success) logger.warn(jsonString);
127             session.getRemote().sendString(jsonString);
128         } catch (IOException e) {
129             logger.warn(e);
130         }
131     }
132
133     @OnWebSocketMessage
134     @SuppressWarnings("unchecked") // direct casting JSON-parsed objects
135     public void onMessage(String message) {
136         if (session == null || !session.isOpen()) return;
137         logger.info("onMessage() " + message);
138
139         HashMap<String,String> params = null;
140
141         try {
142             params = (HashMap<String,String>) JSON.parse(message);
143         } catch (ClassCastException e) {
144             reply("Invalid WebSockets JSON message " + message, "", false);
145         }
146
147         FileIO io;
148         String msgid = params.get("msgid");
149         String action = params.get("action");
150         String key = params.get("key");
151         String value = params.get("value");
152         String mime = params.get("mime");
153
154         // all requets require a message ID
155         if (msgid == null || msgid.equals("")) {
156             reply("No msgid specified in request", msgid, false);
157             return;
158         }
159
160         // all requests require an action
161         if (action == null || action.equals("")) {
162             reply("No action specified in request", msgid, false);
163             return;
164         }
165
166         if (action.equals("keys")) {
167             io = new FileIO(profileDirectory);
168             String[] keys = io.keys(key); // OK for key to be null
169             if (keys != null) {
170                 reply(keys, msgid);
171             } else {
172                 reply("key lookup error", msgid, false);
173             }
174             return;
175         }
176
177         if (action.equals("printers")) {
178             List printers = new PrintManager().getPrinters();
179             reply(printers, msgid);
180             return;
181         }
182
183         if (action.equals("print")) {
184             // TODO: validate the print target first so we can respond
185             // with an error if the requested printer / attributes are
186             // not supported.  Printing occurs in a separate thread,
187             // so for now just assume it succeeded.  Maybe later add
188             // a response queue and see if this handler is capable of
189             // responding from an alternate thread.
190             Hatch.enqueueMessage(params);
191             reply("print succeeded", msgid);
192             return;
193         }
194
195         // all remaining requests require a key
196         if (key == null || key.equals("")) {
197             reply("No key specified in request", msgid, false);
198             return;
199         }
200
201         if (action.equals("get")) {
202             io = new FileIO(profileDirectory);
203             BufferedReader reader = io.get(key);
204             if (reader != null) {
205                 String line;
206                 try {
207                     while ( (line = reader.readLine()) != null) {
208                         // relay lines of text to the caller as we read them
209                         // assume the text content is JSON and return it
210                         // un-JSON-ified.
211                         reply(line, msgid);
212                     }
213                 } catch (IOException e) {
214                     logger.warn(e);
215                 }
216             } else {
217                 reply("Error accessing property " + key, msgid, false);
218             }
219             return;
220         }
221
222         if (action.equals("delete")) {
223             io = new FileIO(profileDirectory);
224             if (io.delete(key)) {
225                 reply("Delete of " + key + " successful", msgid);
226             } else {
227                 reply("Delete of " + key + " failed", msgid, false);
228             }
229             return;
230         }
231
232         // all remaining actions require value
233         if (value == null) {
234             reply("No value specified in request", msgid, false);
235             return;
236         }
237
238         switch (action) {
239
240             case "set" :
241                 io = new FileIO(profileDirectory);
242                 if (io.set(key, value)) {
243                     reply("setting value for " + key + " succeeded", msgid);
244                 } else {
245                     reply("setting value for " + key + " succeeded", msgid, false);
246                 }
247                 break;
248
249             case "append" :
250                 io = new FileIO(profileDirectory);
251                 if (io.append(key, value)) {
252                     reply("appending value for " + key + " succeeded", msgid);
253                 } else {
254                     reply("appending value for " + key + " succeeded", msgid, false);
255                 }
256                 break;
257
258             default:
259                 reply("No such action: " + action, msgid, false);
260         }
261     }
262 }