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