fix Base64 but breaks compat
[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;
79ce1a49
NR
12import be.nikiroo.utils.serial.Exporter;
13import be.nikiroo.utils.serial.Importer;
08f80ac5
NR
14import be.nikiroo.utils.streams.BufferedOutputStream;
15import be.nikiroo.utils.streams.NextableInputStream;
16import be.nikiroo.utils.streams.NextableInputStreamStep;
17import be.nikiroo.utils.streams.ReplaceInputStream;
18import be.nikiroo.utils.streams.ReplaceOutputStream;
ce0974c4 19
f157aed8
NR
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 */
ce0974c4
NR
29abstract class ConnectAction {
30 private Socket s;
31 private boolean server;
ce0974c4 32
8468bb79
NR
33 private CryptUtils crypt;
34
ce0974c4 35 private Object lock = new Object();
08f80ac5
NR
36 private NextableInputStream in;
37 private BufferedOutputStream out;
ce0974c4
NR
38 private boolean contentToSend;
39
f157aed8
NR
40 /**
41 * Method that will be called when an action is performed on either the
42 * client or server this {@link ConnectAction} represent.
43 *
f157aed8
NR
44 * @throws Exception
45 * in case of I/O error
46 */
08f80ac5 47 abstract protected void action() throws Exception;
f157aed8
NR
48
49 /**
50 * Handler called when an unexpected error occurs in the code.
51 *
52 * @param e
9fb03c36
NR
53 * the exception that occurred, SSLException usually denotes a
54 * crypt error
f157aed8
NR
55 */
56 abstract protected void onError(Exception e);
ce0974c4 57
f157aed8
NR
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)
8468bb79
NR
66 * @param key
67 * an optional key to encrypt all the communications (if NULL,
68 * everything will be sent in clear text)
f157aed8 69 */
08f80ac5 70 protected ConnectAction(Socket s, boolean server, String key) {
ce0974c4
NR
71 this.s = s;
72 this.server = server;
8468bb79
NR
73 if (key != null) {
74 crypt = new CryptUtils(key);
75 }
ce0974c4
NR
76 }
77
4bb7e88e
NR
78 /**
79 * The total amount of bytes received.
80 *
81 * @return the amount of bytes received
82 */
83 public long getBytesReceived() {
08f80ac5 84 return in.getBytesRead();
4bb7e88e
NR
85 }
86
87 /**
88 * The total amount of bytes sent.
89 *
90 * @return the amount of bytes sent
91 */
08f80ac5
NR
92 public long getBytesWritten() {
93 return out.getBytesWritten();
4bb7e88e
NR
94 }
95
f157aed8
NR
96 /**
97 * Actually start the process (this is synchronous).
98 */
ce0974c4
NR
99 public void connect() {
100 try {
08f80ac5
NR
101 // TODO: assure that \b is never used, make sure \n usage is OK
102 in = new NextableInputStream(s.getInputStream(),
103 new NextableInputStreamStep('\b'));
9fb03c36 104
08f80ac5
NR
105 try {
106 out = new BufferedOutputStream(s.getOutputStream());
ce0974c4 107
08f80ac5
NR
108 try {
109 action();
ce0974c4
NR
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
f157aed8
NR
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 *
08f80ac5
NR
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
f157aed8
NR
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 */
4bb7e88e
NR
150 protected Object sendObject(Object data) throws IOException,
151 NoSuchFieldException, NoSuchMethodException, ClassNotFoundException {
08f80ac5 152 return send(out, data, false);
ce0974c4
NR
153 }
154
f157aed8
NR
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 */
4bb7e88e
NR
179 protected Object recObject() throws IOException, NoSuchFieldException,
180 NoSuchMethodException, ClassNotFoundException,
181 java.lang.NullPointerException {
08f80ac5 182 return rec(false);
ce0974c4
NR
183 }
184
cd0c27d2 185 /**
f157aed8
NR
186 * Send the given string to the counter part (and, only for client, return
187 * the answer -- the server will always receive NULL).
cd0c27d2 188 *
f157aed8
NR
189 * @param line
190 * the data to send (we will add a line feed)
cd0c27d2 191 *
f157aed8
NR
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
9fb03c36
NR
197 * @throws SSLException
198 * in case of crypt error
cd0c27d2 199 */
f157aed8 200 protected String sendString(String line) throws IOException {
08f80ac5
NR
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();
ce0974c4 212 }
08f80ac5
NR
213
214 return null;
ce0974c4
NR
215 }
216
f157aed8
NR
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 *
08f80ac5 226 * @return the answer (which can be NULL if no more content)
f157aed8
NR
227 *
228 * @throws IOException
229 * in case of I/O error
9fb03c36
NR
230 * @throws SSLException
231 * in case of crypt error
f157aed8 232 */
79ce1a49 233 protected String recString() throws IOException {
08f80ac5
NR
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();
ce0974c4 248 }
08f80ac5
NR
249
250 return null;
ce0974c4 251 }
8468bb79 252
9fb03c36 253 /**
08f80ac5
NR
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).
9fb03c36 257 *
08f80ac5
NR
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
9fb03c36 264 *
08f80ac5
NR
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
9fb03c36
NR
268 *
269 * @throws IOException
270 * in case of I/O error
271 * @throws SSLException
272 * in case of crypt error
08f80ac5
NR
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
9fb03c36 283 */
08f80ac5
NR
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;
8468bb79 290 if (crypt != null) {
a6a73de3 291 sub = crypt.encrypt64(out.open());
08f80ac5
NR
292 } else {
293 sub = out.open();
8468bb79 294 }
8468bb79 295
08f80ac5
NR
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 }
8468bb79 311
08f80ac5
NR
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 }
9af739b5 329
9fb03c36 330 /**
08f80ac5
NR
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)
9fb03c36 352 *
9fb03c36
NR
353 * @throws IOException
354 * in case of I/O error
08f80ac5
NR
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
9fb03c36 365 */
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
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) {
a6a73de3 386 read = crypt.decrypt64(read);
08f80ac5
NR
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;
8468bb79 405 }
8468bb79 406 }
ce0974c4 407}