jdoc
[nikiroo-utils.git] / src / be / nikiroo / utils / serial / server / ConnectAction.java
CommitLineData
79ce1a49 1package be.nikiroo.utils.serial.server;
ce0974c4 2
ce0974c4 3import java.io.IOException;
9af739b5 4import java.io.InputStream;
9af739b5 5import java.io.OutputStream;
ce0974c4
NR
6import java.net.Socket;
7
9fb03c36
NR
8import javax.net.ssl.SSLException;
9
8468bb79 10import be.nikiroo.utils.CryptUtils;
08f80ac5 11import be.nikiroo.utils.IOUtils;
f8147a0e 12import be.nikiroo.utils.StringUtils;
79ce1a49
NR
13import be.nikiroo.utils.serial.Exporter;
14import be.nikiroo.utils.serial.Importer;
08f80ac5
NR
15import be.nikiroo.utils.streams.BufferedOutputStream;
16import be.nikiroo.utils.streams.NextableInputStream;
17import be.nikiroo.utils.streams.NextableInputStreamStep;
18import be.nikiroo.utils.streams.ReplaceInputStream;
19import 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
NR
30abstract class ConnectAction {
31 private Socket s;
32 private boolean server;
ce0974c4 33
8468bb79
NR
34 private CryptUtils crypt;
35
ce0974c4 36 private Object lock = new Object();
08f80ac5
NR
37 private NextableInputStream in;
38 private BufferedOutputStream out;
ce0974c4
NR
39 private boolean contentToSend;
40
f157aed8
NR
41 /**
42 * Method that will be called when an action is performed on either the
43 * client or server this {@link ConnectAction} represent.
44 *
f157aed8
NR
45 * @throws Exception
46 * in case of I/O error
47 */
08f80ac5 48 abstract protected void action() throws Exception;
f157aed8
NR
49
50 /**
51 * Handler called when an unexpected error occurs in the code.
52 *
53 * @param e
9fb03c36
NR
54 * the exception that occurred, SSLException usually denotes a
55 * crypt error
f157aed8
NR
56 */
57 abstract protected void onError(Exception e);
ce0974c4 58
f157aed8
NR
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)
8468bb79
NR
67 * @param key
68 * an optional key to encrypt all the communications (if NULL,
69 * everything will be sent in clear text)
f157aed8 70 */
08f80ac5 71 protected ConnectAction(Socket s, boolean server, String key) {
ce0974c4
NR
72 this.s = s;
73 this.server = server;
8468bb79
NR
74 if (key != null) {
75 crypt = new CryptUtils(key);
76 }
ce0974c4
NR
77 }
78
4bb7e88e
NR
79 /**
80 * The total amount of bytes received.
81 *
82 * @return the amount of bytes received
83 */
84 public long getBytesReceived() {
08f80ac5 85 return in.getBytesRead();
4bb7e88e
NR
86 }
87
88 /**
89 * The total amount of bytes sent.
90 *
91 * @return the amount of bytes sent
92 */
08f80ac5
NR
93 public long getBytesWritten() {
94 return out.getBytesWritten();
4bb7e88e
NR
95 }
96
f157aed8
NR
97 /**
98 * Actually start the process (this is synchronous).
99 */
ce0974c4
NR
100 public void connect() {
101 try {
08f80ac5
NR
102 // TODO: assure that \b is never used, make sure \n usage is OK
103 in = new NextableInputStream(s.getInputStream(),
104 new NextableInputStreamStep('\b'));
9fb03c36 105
08f80ac5
NR
106 try {
107 out = new BufferedOutputStream(s.getOutputStream());
ce0974c4 108
08f80ac5
NR
109 try {
110 action();
ce0974c4
NR
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
f157aed8
NR
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 *
08f80ac5
NR
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
f157aed8
NR
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 */
4bb7e88e
NR
151 protected Object sendObject(Object data) throws IOException,
152 NoSuchFieldException, NoSuchMethodException, ClassNotFoundException {
08f80ac5 153 return send(out, data, false);
ce0974c4
NR
154 }
155
f157aed8
NR
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 */
4bb7e88e
NR
180 protected Object recObject() throws IOException, NoSuchFieldException,
181 NoSuchMethodException, ClassNotFoundException,
182 java.lang.NullPointerException {
08f80ac5 183 return rec(false);
ce0974c4
NR
184 }
185
cd0c27d2 186 /**
f157aed8
NR
187 * Send the given string to the counter part (and, only for client, return
188 * the answer -- the server will always receive NULL).
cd0c27d2 189 *
f157aed8
NR
190 * @param line
191 * the data to send (we will add a line feed)
cd0c27d2 192 *
f157aed8
NR
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
9fb03c36
NR
198 * @throws SSLException
199 * in case of crypt error
cd0c27d2 200 */
f157aed8 201 protected String sendString(String line) throws IOException {
08f80ac5
NR
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();
ce0974c4 213 }
08f80ac5
NR
214
215 return null;
ce0974c4
NR
216 }
217
f157aed8
NR
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 *
08f80ac5 227 * @return the answer (which can be NULL if no more content)
f157aed8
NR
228 *
229 * @throws IOException
230 * in case of I/O error
9fb03c36
NR
231 * @throws SSLException
232 * in case of crypt error
f157aed8 233 */
79ce1a49 234 protected String recString() throws IOException {
08f80ac5
NR
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();
ce0974c4 249 }
08f80ac5
NR
250
251 return null;
ce0974c4 252 }
8468bb79 253
9fb03c36 254 /**
08f80ac5
NR
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).
9fb03c36 258 *
08f80ac5
NR
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
9fb03c36 265 *
08f80ac5
NR
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
9fb03c36
NR
269 *
270 * @throws IOException
271 * in case of I/O error
272 * @throws SSLException
273 * in case of crypt error
08f80ac5
NR
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
9fb03c36 284 */
08f80ac5
NR
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;
8468bb79 291 if (crypt != null) {
a6a73de3 292 sub = crypt.encrypt64(out.open());
08f80ac5
NR
293 } else {
294 sub = out.open();
8468bb79 295 }
8468bb79 296
08f80ac5
NR
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) {
f8147a0e 305 sub.write(StringUtils.getBytes(data.toString()));
08f80ac5
NR
306 } else {
307 new Exporter(sub).append(data);
308 }
309 } finally {
310 sub.close();
311 }
8468bb79 312
08f80ac5
NR
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 }
9af739b5 330
9fb03c36 331 /**
08f80ac5
NR
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)
9fb03c36 353 *
9fb03c36
NR
354 * @throws IOException
355 * in case of I/O error
08f80ac5
NR
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
9fb03c36 366 */
bd86c221 367 @SuppressWarnings("resource")
08f80ac5
NR
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) {
a6a73de3 388 read = crypt.decrypt64(read);
08f80ac5
NR
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;
8468bb79 407 }
8468bb79 408 }
ce0974c4 409}