Commit | Line | Data |
---|---|---|
79ce1a49 | 1 | package be.nikiroo.utils.serial.server; |
ce0974c4 | 2 | |
ce0974c4 | 3 | import java.io.IOException; |
9af739b5 | 4 | import java.io.InputStream; |
9af739b5 | 5 | import java.io.OutputStream; |
ce0974c4 NR |
6 | import java.net.Socket; |
7 | ||
9fb03c36 NR |
8 | import javax.net.ssl.SSLException; |
9 | ||
8468bb79 | 10 | import be.nikiroo.utils.CryptUtils; |
08f80ac5 | 11 | import be.nikiroo.utils.IOUtils; |
f8147a0e | 12 | import be.nikiroo.utils.StringUtils; |
79ce1a49 NR |
13 | import be.nikiroo.utils.serial.Exporter; |
14 | import be.nikiroo.utils.serial.Importer; | |
08f80ac5 NR |
15 | import be.nikiroo.utils.streams.BufferedOutputStream; |
16 | import be.nikiroo.utils.streams.NextableInputStream; | |
17 | import be.nikiroo.utils.streams.NextableInputStreamStep; | |
18 | import be.nikiroo.utils.streams.ReplaceInputStream; | |
19 | import be.nikiroo.utils.streams.ReplaceOutputStream; | |
ce0974c4 | 20 | |
f157aed8 NR |
21 | /** |
22 | * Base class used for the client/server basic handling. | |
23 | * <p> | |
24 | * It represents a single action: a client is expected to only execute one | |
25 | * action, while a server is expected to execute one action for each client | |
26 | * action. | |
27 | * | |
28 | * @author niki | |
29 | */ | |
ce0974c4 | 30 | abstract class ConnectAction { |
579c8b90 NR |
31 | // We separate each "packet" we send with this character and make sure it |
32 | // does not occurs in the message itself. | |
33 | static private char STREAM_SEP = '\b'; | |
34 | static private String[] STREAM_RAW = new String[] { "\\", "\b" }; | |
35 | static private String[] STREAM_CODED = new String[] { "\\\\", "\\b" }; | |
36 | ||
ce0974c4 NR |
37 | private Socket s; |
38 | private boolean server; | |
ce0974c4 | 39 | |
8468bb79 NR |
40 | private CryptUtils crypt; |
41 | ||
ce0974c4 | 42 | private Object lock = new Object(); |
08f80ac5 NR |
43 | private NextableInputStream in; |
44 | private BufferedOutputStream out; | |
ce0974c4 NR |
45 | private boolean contentToSend; |
46 | ||
f157aed8 NR |
47 | /** |
48 | * Method that will be called when an action is performed on either the | |
49 | * client or server this {@link ConnectAction} represent. | |
50 | * | |
f157aed8 NR |
51 | * @throws Exception |
52 | * in case of I/O error | |
53 | */ | |
08f80ac5 | 54 | abstract protected void action() throws Exception; |
f157aed8 NR |
55 | |
56 | /** | |
57 | * Handler called when an unexpected error occurs in the code. | |
58 | * | |
59 | * @param e | |
9fb03c36 NR |
60 | * the exception that occurred, SSLException usually denotes a |
61 | * crypt error | |
f157aed8 NR |
62 | */ |
63 | abstract protected void onError(Exception e); | |
ce0974c4 | 64 | |
f157aed8 NR |
65 | /** |
66 | * Create a new {@link ConnectAction}. | |
67 | * | |
68 | * @param s | |
69 | * the socket to bind to | |
70 | * @param server | |
71 | * TRUE for a server action, FALSE for a client action (will | |
72 | * impact the process) | |
8468bb79 NR |
73 | * @param key |
74 | * an optional key to encrypt all the communications (if NULL, | |
75 | * everything will be sent in clear text) | |
f157aed8 | 76 | */ |
08f80ac5 | 77 | protected ConnectAction(Socket s, boolean server, String key) { |
ce0974c4 NR |
78 | this.s = s; |
79 | this.server = server; | |
8468bb79 NR |
80 | if (key != null) { |
81 | crypt = new CryptUtils(key); | |
82 | } | |
ce0974c4 NR |
83 | } |
84 | ||
4bb7e88e NR |
85 | /** |
86 | * The total amount of bytes received. | |
87 | * | |
88 | * @return the amount of bytes received | |
89 | */ | |
90 | public long getBytesReceived() { | |
08f80ac5 | 91 | return in.getBytesRead(); |
4bb7e88e NR |
92 | } |
93 | ||
94 | /** | |
95 | * The total amount of bytes sent. | |
96 | * | |
97 | * @return the amount of bytes sent | |
98 | */ | |
08f80ac5 NR |
99 | public long getBytesWritten() { |
100 | return out.getBytesWritten(); | |
4bb7e88e NR |
101 | } |
102 | ||
f157aed8 NR |
103 | /** |
104 | * Actually start the process (this is synchronous). | |
105 | */ | |
ce0974c4 NR |
106 | public void connect() { |
107 | try { | |
08f80ac5 | 108 | in = new NextableInputStream(s.getInputStream(), |
579c8b90 | 109 | new NextableInputStreamStep(STREAM_SEP)); |
08f80ac5 NR |
110 | try { |
111 | out = new BufferedOutputStream(s.getOutputStream()); | |
08f80ac5 NR |
112 | try { |
113 | action(); | |
ce0974c4 NR |
114 | } finally { |
115 | out.close(); | |
116 | } | |
117 | } finally { | |
118 | in.close(); | |
119 | } | |
120 | } catch (Exception e) { | |
121 | onError(e); | |
122 | } finally { | |
123 | try { | |
124 | s.close(); | |
125 | } catch (Exception e) { | |
126 | onError(e); | |
127 | } | |
128 | } | |
129 | } | |
130 | ||
f157aed8 NR |
131 | /** |
132 | * Serialise and send the given object to the counter part (and, only for | |
133 | * client, return the deserialised answer -- the server will always receive | |
134 | * NULL). | |
135 | * | |
136 | * @param data | |
137 | * the data to send | |
138 | * | |
08f80ac5 NR |
139 | * @return the answer (which can be NULL if no answer, or NULL for an answer |
140 | * which is NULL) if this action is a client, always NULL if it is a | |
141 | * server | |
f157aed8 NR |
142 | * |
143 | * @throws IOException | |
144 | * in case of I/O error | |
145 | * @throws NoSuchFieldException | |
146 | * if the serialised data contains information about a field | |
147 | * which does actually not exist in the class we know of | |
148 | * @throws NoSuchMethodException | |
149 | * if a class described in the serialised data cannot be created | |
150 | * because it is not compatible with this code | |
151 | * @throws ClassNotFoundException | |
152 | * if a class described in the serialised data cannot be found | |
153 | */ | |
4bb7e88e NR |
154 | protected Object sendObject(Object data) throws IOException, |
155 | NoSuchFieldException, NoSuchMethodException, ClassNotFoundException { | |
08f80ac5 | 156 | return send(out, data, false); |
ce0974c4 NR |
157 | } |
158 | ||
f157aed8 NR |
159 | /** |
160 | * Reserved for the server: flush the data to the client and retrieve its | |
161 | * answer. | |
162 | * <p> | |
163 | * Also used internally for the client (only do something if there is | |
164 | * contentToSend). | |
165 | * <p> | |
166 | * Will only flush the data if there is contentToSend. | |
167 | * | |
168 | * @return the deserialised answer (which can actually be NULL) | |
169 | * | |
170 | * @throws IOException | |
171 | * in case of I/O error | |
172 | * @throws NoSuchFieldException | |
173 | * if the serialised data contains information about a field | |
174 | * which does actually not exist in the class we know of | |
175 | * @throws NoSuchMethodException | |
176 | * if a class described in the serialised data cannot be created | |
177 | * because it is not compatible with this code | |
178 | * @throws ClassNotFoundException | |
179 | * if a class described in the serialised data cannot be found | |
180 | * @throws java.lang.NullPointerException | |
181 | * if the counter part has no data to send | |
182 | */ | |
4bb7e88e NR |
183 | protected Object recObject() throws IOException, NoSuchFieldException, |
184 | NoSuchMethodException, ClassNotFoundException, | |
185 | java.lang.NullPointerException { | |
08f80ac5 | 186 | return rec(false); |
ce0974c4 NR |
187 | } |
188 | ||
cd0c27d2 | 189 | /** |
f157aed8 NR |
190 | * Send the given string to the counter part (and, only for client, return |
191 | * the answer -- the server will always receive NULL). | |
cd0c27d2 | 192 | * |
f157aed8 NR |
193 | * @param line |
194 | * the data to send (we will add a line feed) | |
cd0c27d2 | 195 | * |
f157aed8 NR |
196 | * @return the answer if this action is a client (without the added line |
197 | * feed), NULL if it is a server | |
198 | * | |
199 | * @throws IOException | |
200 | * in case of I/O error | |
9fb03c36 NR |
201 | * @throws SSLException |
202 | * in case of crypt error | |
cd0c27d2 | 203 | */ |
f157aed8 | 204 | protected String sendString(String line) throws IOException { |
08f80ac5 NR |
205 | try { |
206 | return (String) send(out, line, true); | |
207 | } catch (NoSuchFieldException e) { | |
208 | // Cannot happen | |
209 | e.printStackTrace(); | |
210 | } catch (NoSuchMethodException e) { | |
211 | // Cannot happen | |
212 | e.printStackTrace(); | |
213 | } catch (ClassNotFoundException e) { | |
214 | // Cannot happen | |
215 | e.printStackTrace(); | |
ce0974c4 | 216 | } |
08f80ac5 NR |
217 | |
218 | return null; | |
ce0974c4 NR |
219 | } |
220 | ||
f157aed8 NR |
221 | /** |
222 | * Reserved for the server (externally): flush the data to the client and | |
223 | * retrieve its answer. | |
224 | * <p> | |
225 | * Also used internally for the client (only do something if there is | |
226 | * contentToSend). | |
227 | * <p> | |
228 | * Will only flush the data if there is contentToSend. | |
229 | * | |
08f80ac5 | 230 | * @return the answer (which can be NULL if no more content) |
f157aed8 NR |
231 | * |
232 | * @throws IOException | |
233 | * in case of I/O error | |
9fb03c36 NR |
234 | * @throws SSLException |
235 | * in case of crypt error | |
f157aed8 | 236 | */ |
79ce1a49 | 237 | protected String recString() throws IOException { |
08f80ac5 NR |
238 | try { |
239 | return (String) rec(true); | |
240 | } catch (NoSuchFieldException e) { | |
241 | // Cannot happen | |
242 | e.printStackTrace(); | |
243 | } catch (NoSuchMethodException e) { | |
244 | // Cannot happen | |
245 | e.printStackTrace(); | |
246 | } catch (ClassNotFoundException e) { | |
247 | // Cannot happen | |
248 | e.printStackTrace(); | |
249 | } catch (NullPointerException e) { | |
250 | // Should happen | |
251 | e.printStackTrace(); | |
ce0974c4 | 252 | } |
08f80ac5 NR |
253 | |
254 | return null; | |
ce0974c4 | 255 | } |
8468bb79 | 256 | |
9fb03c36 | 257 | /** |
08f80ac5 NR |
258 | * Serialise and send the given object to the counter part (and, only for |
259 | * client, return the deserialised answer -- the server will always receive | |
260 | * NULL). | |
9fb03c36 | 261 | * |
08f80ac5 NR |
262 | * @param out |
263 | * the stream to write to | |
264 | * @param data | |
265 | * the data to write | |
266 | * @param asString | |
267 | * TRUE to write it as a String, FALSE to write it as an Object | |
9fb03c36 | 268 | * |
08f80ac5 NR |
269 | * @return the answer (which can be NULL if no answer, or NULL for an answer |
270 | * which is NULL) if this action is a client, always NULL if it is a | |
271 | * server | |
9fb03c36 NR |
272 | * |
273 | * @throws IOException | |
274 | * in case of I/O error | |
275 | * @throws SSLException | |
276 | * in case of crypt error | |
08f80ac5 NR |
277 | * @throws IOException |
278 | * in case of I/O error | |
279 | * @throws NoSuchFieldException | |
280 | * if the serialised data contains information about a field | |
281 | * which does actually not exist in the class we know of | |
282 | * @throws NoSuchMethodException | |
283 | * if a class described in the serialised data cannot be created | |
284 | * because it is not compatible with this code | |
285 | * @throws ClassNotFoundException | |
286 | * if a class described in the serialised data cannot be found | |
9fb03c36 | 287 | */ |
08f80ac5 NR |
288 | private Object send(BufferedOutputStream out, Object data, boolean asString) |
289 | throws IOException, NoSuchFieldException, NoSuchMethodException, | |
290 | ClassNotFoundException, java.lang.NullPointerException { | |
291 | ||
292 | synchronized (lock) { | |
293 | OutputStream sub; | |
8468bb79 | 294 | if (crypt != null) { |
a6a73de3 | 295 | sub = crypt.encrypt64(out.open()); |
08f80ac5 NR |
296 | } else { |
297 | sub = out.open(); | |
8468bb79 | 298 | } |
8468bb79 | 299 | |
579c8b90 | 300 | sub = new ReplaceOutputStream(sub, STREAM_RAW, STREAM_CODED); |
08f80ac5 NR |
301 | try { |
302 | if (asString) { | |
f8147a0e | 303 | sub.write(StringUtils.getBytes(data.toString())); |
08f80ac5 NR |
304 | } else { |
305 | new Exporter(sub).append(data); | |
306 | } | |
307 | } finally { | |
308 | sub.close(); | |
309 | } | |
8468bb79 | 310 | |
579c8b90 | 311 | out.write(STREAM_SEP); |
08f80ac5 NR |
312 | |
313 | if (server) { | |
314 | out.flush(); | |
315 | return null; | |
316 | } | |
317 | ||
318 | contentToSend = true; | |
319 | try { | |
320 | return rec(asString); | |
321 | } catch (NullPointerException e) { | |
322 | // We accept no data here for Objects | |
323 | } | |
324 | ||
325 | return null; | |
326 | } | |
327 | } | |
9af739b5 | 328 | |
9fb03c36 | 329 | /** |
08f80ac5 NR |
330 | * Reserved for the server: flush the data to the client and retrieve its |
331 | * answer. | |
332 | * <p> | |
333 | * Also used internally for the client (only do something if there is | |
334 | * contentToSend). | |
335 | * <p> | |
336 | * Will only flush the data if there is contentToSend. | |
337 | * <p> | |
338 | * Note that the behaviour is slightly different for String and Object | |
339 | * reading regarding exceptions: | |
340 | * <ul> | |
341 | * <li>NULL means that the counter part has no more data to send</li> | |
342 | * <li>All the exceptions except {@link IOException} are there for Object | |
343 | * conversion</li> | |
344 | * </ul> | |
345 | * | |
346 | * @param asString | |
347 | * TRUE for String reading, FALSE for Object reading (which can | |
348 | * still be a String) | |
349 | * | |
350 | * @return the deserialised answer (which can actually be NULL) | |
9fb03c36 | 351 | * |
9fb03c36 NR |
352 | * @throws IOException |
353 | * in case of I/O error | |
08f80ac5 NR |
354 | * @throws NoSuchFieldException |
355 | * if the serialised data contains information about a field | |
356 | * which does actually not exist in the class we know of | |
357 | * @throws NoSuchMethodException | |
358 | * if a class described in the serialised data cannot be created | |
359 | * because it is not compatible with this code | |
360 | * @throws ClassNotFoundException | |
361 | * if a class described in the serialised data cannot be found | |
362 | * @throws java.lang.NullPointerException | |
363 | * for Objects only: if the counter part has no data to send | |
9fb03c36 | 364 | */ |
bd86c221 | 365 | @SuppressWarnings("resource") |
08f80ac5 NR |
366 | private Object rec(boolean asString) throws IOException, |
367 | NoSuchFieldException, NoSuchMethodException, | |
368 | ClassNotFoundException, java.lang.NullPointerException { | |
369 | ||
370 | synchronized (lock) { | |
371 | if (server || contentToSend) { | |
372 | if (contentToSend) { | |
373 | out.flush(); | |
374 | contentToSend = false; | |
375 | } | |
376 | ||
d2219aa0 | 377 | if (in.next() && !in.eof()) { |
579c8b90 NR |
378 | InputStream read = new ReplaceInputStream(in.open(), |
379 | STREAM_CODED, STREAM_RAW); | |
08f80ac5 NR |
380 | try { |
381 | if (crypt != null) { | |
a6a73de3 | 382 | read = crypt.decrypt64(read); |
08f80ac5 NR |
383 | } |
384 | ||
385 | if (asString) { | |
386 | return IOUtils.readSmallStream(read); | |
387 | } | |
388 | ||
389 | return new Importer().read(read).getValue(); | |
390 | } finally { | |
391 | read.close(); | |
392 | } | |
393 | } | |
394 | ||
395 | if (!asString) { | |
396 | throw new NullPointerException(); | |
397 | } | |
398 | } | |
399 | ||
400 | return null; | |
8468bb79 | 401 | } |
8468bb79 | 402 | } |
ce0974c4 | 403 | } |