0f2f1b4319fa799950119815c31dd9bf81043ee8
[fanfix.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 // 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
37 private Socket s;
38 private boolean server;
39
40 private CryptUtils crypt;
41
42 private Object lock = new Object();
43 private NextableInputStream in;
44 private BufferedOutputStream out;
45 private boolean contentToSend;
46
47 /**
48 * Method that will be called when an action is performed on either the
49 * client or server this {@link ConnectAction} represent.
50 *
51 * @throws Exception
52 * in case of I/O error
53 */
54 abstract protected void action() throws Exception;
55
56 /**
57 * Handler called when an unexpected error occurs in the code.
58 *
59 * @param e
60 * the exception that occurred, SSLException usually denotes a
61 * crypt error
62 */
63 abstract protected void onError(Exception e);
64
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)
73 * @param key
74 * an optional key to encrypt all the communications (if NULL,
75 * everything will be sent in clear text)
76 */
77 protected ConnectAction(Socket s, boolean server, String key) {
78 this.s = s;
79 this.server = server;
80 if (key != null) {
81 crypt = new CryptUtils(key);
82 }
83 }
84
85 /**
86 * The total amount of bytes received.
87 *
88 * @return the amount of bytes received
89 */
90 public long getBytesReceived() {
91 return in.getBytesRead();
92 }
93
94 /**
95 * The total amount of bytes sent.
96 *
97 * @return the amount of bytes sent
98 */
99 public long getBytesWritten() {
100 return out.getBytesWritten();
101 }
102
103 /**
104 * Actually start the process (this is synchronous).
105 */
106 public void connect() {
107 try {
108 in = new NextableInputStream(s.getInputStream(),
109 new NextableInputStreamStep(STREAM_SEP));
110 try {
111 out = new BufferedOutputStream(s.getOutputStream());
112 try {
113 action();
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
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 *
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
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 */
154 protected Object sendObject(Object data) throws IOException,
155 NoSuchFieldException, NoSuchMethodException, ClassNotFoundException {
156 return send(out, data, false);
157 }
158
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 */
183 protected Object recObject() throws IOException, NoSuchFieldException,
184 NoSuchMethodException, ClassNotFoundException,
185 java.lang.NullPointerException {
186 return rec(false);
187 }
188
189 /**
190 * Send the given string to the counter part (and, only for client, return
191 * the answer -- the server will always receive NULL).
192 *
193 * @param line
194 * the data to send (we will add a line feed)
195 *
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
201 * @throws SSLException
202 * in case of crypt error
203 */
204 protected String sendString(String line) throws IOException {
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();
216 }
217
218 return null;
219 }
220
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 *
230 * @return the answer (which can be NULL if no more content)
231 *
232 * @throws IOException
233 * in case of I/O error
234 * @throws SSLException
235 * in case of crypt error
236 */
237 protected String recString() throws IOException {
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();
252 }
253
254 return null;
255 }
256
257 /**
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).
261 *
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
268 *
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
272 *
273 * @throws IOException
274 * in case of I/O error
275 * @throws SSLException
276 * in case of crypt error
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
287 */
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;
294 if (crypt != null) {
295 sub = crypt.encrypt64(out.open());
296 } else {
297 sub = out.open();
298 }
299
300 sub = new ReplaceOutputStream(sub, STREAM_RAW, STREAM_CODED);
301 try {
302 if (asString) {
303 sub.write(StringUtils.getBytes(data.toString()));
304 } else {
305 new Exporter(sub).append(data);
306 }
307 } finally {
308 sub.close();
309 }
310
311 out.write(STREAM_SEP);
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 }
328
329 /**
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)
351 *
352 * @throws IOException
353 * in case of I/O error
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
364 */
365 @SuppressWarnings("resource")
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() && !in.eof()) {
378 InputStream read = new ReplaceInputStream(in.open(),
379 STREAM_CODED, STREAM_RAW);
380 try {
381 if (crypt != null) {
382 read = crypt.decrypt64(read);
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;
401 }
402 }
403 }