1 package be
.nikiroo
.utils
.serial
.server
;
3 import java
.io
.BufferedReader
;
4 import java
.io
.IOException
;
5 import java
.io
.InputStreamReader
;
6 import java
.io
.OutputStreamWriter
;
7 import java
.net
.Socket
;
9 import be
.nikiroo
.utils
.CryptUtils
;
10 import be
.nikiroo
.utils
.Version
;
11 import be
.nikiroo
.utils
.serial
.Exporter
;
12 import be
.nikiroo
.utils
.serial
.Importer
;
15 * Base class used for the client/server basic handling.
17 * It represents a single action: a client is expected to only execute one
18 * action, while a server is expected to execute one action for each client
23 abstract class ConnectAction
{
25 private boolean server
;
26 private Version version
;
27 private Version clientVersion
;
29 private CryptUtils crypt
;
31 private Object lock
= new Object();
32 private BufferedReader in
;
33 private OutputStreamWriter out
;
34 private boolean contentToSend
;
36 private long bytesReceived
;
37 private long bytesSent
;
40 * Method that will be called when an action is performed on either the
41 * client or server this {@link ConnectAction} represent.
44 * the counter part version
47 * in case of I/O error
49 abstract protected void action(Version version
) throws Exception
;
52 * Method called when we negotiate the version with the client.
54 * Thus, it is only called on the server.
56 * Will return the actual server version by default.
58 * @param clientVersion
61 * @return the version to send to the client
63 abstract protected Version
negotiateVersion(Version clientVersion
);
66 * Handler called when an unexpected error occurs in the code.
69 * the exception that occurred
71 abstract protected void onError(Exception e
);
74 * Create a new {@link ConnectAction}.
77 * the socket to bind to
79 * TRUE for a server action, FALSE for a client action (will
82 * an optional key to encrypt all the communications (if NULL,
83 * everything will be sent in clear text)
85 * the version of this client-or-server
87 protected ConnectAction(Socket s
, boolean server
, String key
,
92 crypt
= new CryptUtils(key
);
95 if (version
== null) {
96 this.version
= new Version();
98 this.version
= version
;
101 clientVersion
= new Version();
105 * The version of this client-or-server.
107 * @return the version
109 public Version
getVersion() {
114 * The total amount of bytes received.
116 * @return the amount of bytes received
118 public long getBytesReceived() {
119 return bytesReceived
;
123 * The total amount of bytes sent.
125 * @return the amount of bytes sent
127 public long getBytesSent() {
132 * Actually start the process (this is synchronous).
134 public void connect() {
136 in
= new BufferedReader(new InputStreamReader(s
.getInputStream(),
139 out
= new OutputStreamWriter(s
.getOutputStream(), "UTF-8");
142 String line
= readLine(in
);
143 if (line
!= null && line
.startsWith("VERSION ")) {
144 // "VERSION client-version" (VERSION 1.0.0)
145 Version clientVersion
= new Version(
146 line
.substring("VERSION ".length()));
147 this.clientVersion
= clientVersion
;
148 Version v
= negotiateVersion(clientVersion
);
153 sendString("VERSION " + v
.toString());
156 action(clientVersion
);
158 String v
= sendString("VERSION " + version
.toString());
159 if (v
!= null && v
.startsWith("VERSION ")) {
160 v
= v
.substring("VERSION ".length());
163 action(new Version(v
));
173 } catch (Exception e
) {
178 } catch (Exception e
) {
185 * Serialise and send the given object to the counter part (and, only for
186 * client, return the deserialised answer -- the server will always receive
192 * @return the answer (which can be NULL) if this action is a client, always
193 * NULL if it is a server
195 * @throws IOException
196 * in case of I/O error
197 * @throws NoSuchFieldException
198 * if the serialised data contains information about a field
199 * which does actually not exist in the class we know of
200 * @throws NoSuchMethodException
201 * if a class described in the serialised data cannot be created
202 * because it is not compatible with this code
203 * @throws ClassNotFoundException
204 * if a class described in the serialised data cannot be found
206 protected Object
sendObject(Object data
) throws IOException
,
207 NoSuchFieldException
, NoSuchMethodException
, ClassNotFoundException
{
208 synchronized (lock
) {
209 String rep
= sendString(new Exporter().append(data
).toString(true,
212 return new Importer().read(rep
).getValue();
220 * Reserved for the server: flush the data to the client and retrieve its
223 * Also used internally for the client (only do something if there is
226 * Will only flush the data if there is contentToSend.
228 * @return the deserialised answer (which can actually be NULL)
230 * @throws IOException
231 * in case of I/O error
232 * @throws NoSuchFieldException
233 * if the serialised data contains information about a field
234 * which does actually not exist in the class we know of
235 * @throws NoSuchMethodException
236 * if a class described in the serialised data cannot be created
237 * because it is not compatible with this code
238 * @throws ClassNotFoundException
239 * if a class described in the serialised data cannot be found
240 * @throws java.lang.NullPointerException
241 * if the counter part has no data to send
243 protected Object
recObject() throws IOException
, NoSuchFieldException
,
244 NoSuchMethodException
, ClassNotFoundException
,
245 java
.lang
.NullPointerException
{
246 String str
= recString();
248 throw new NullPointerException("No more data available");
251 return new Importer().read(str
).getValue();
255 * Send the given string to the counter part (and, only for client, return
256 * the answer -- the server will always receive NULL).
259 * the data to send (we will add a line feed)
261 * @return the answer if this action is a client (without the added line
262 * feed), NULL if it is a server
264 * @throws IOException
265 * in case of I/O error
267 protected String
sendString(String line
) throws IOException
{
268 synchronized (lock
) {
269 writeLine(out
, line
);
276 contentToSend
= true;
282 * Reserved for the server (externally): flush the data to the client and
283 * retrieve its answer.
285 * Also used internally for the client (only do something if there is
288 * Will only flush the data if there is contentToSend.
290 * @return the answer (which can be NULL)
292 * @throws IOException
293 * in case of I/O error
295 protected String
recString() throws IOException
{
296 synchronized (lock
) {
297 if (server
|| contentToSend
) {
300 contentToSend
= false;
310 private String
readLine(BufferedReader in
) throws IOException
{
311 String line
= in
.readLine();
313 bytesReceived
+= line
.length();
315 line
= crypt
.decrypt64s(line
, false);
322 private void writeLine(OutputStreamWriter out
, String line
)
326 bytesSent
+= line
.length();
328 // TODO: how NOT to create so many big Strings?
329 String b64
= crypt
.encrypt64(line
, false);
331 bytesSent
+= b64
.length();