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