Revert Server behaviour to what it was :
[nikiroo-utils.git] / src / be / nikiroo / utils / serial / ConnectAction.java
1 package be.nikiroo.utils.serial;
2
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;
8
9 import be.nikiroo.utils.Version;
10
11 /**
12 * Base class used for the client/server basic handling.
13 * <p>
14 * It represents a single action: a client is expected to only execute one
15 * action, while a server is expected to execute one action for each client
16 * action.
17 *
18 * @author niki
19 */
20 abstract class ConnectAction {
21 private Socket s;
22 private boolean server;
23 private Version version;
24 private Version clientVersion;
25
26 private Object lock = new Object();
27 private BufferedReader in;
28 private OutputStreamWriter out;
29 private boolean contentToSend;
30
31 /**
32 * Method that will be called when an action is performed on either the
33 * client or server this {@link ConnectAction} represent.
34 *
35 * @param version
36 * the counter part version
37 *
38 * @throws Exception
39 * in case of I/O error
40 */
41 abstract protected void action(Version version) throws Exception;
42
43 /**
44 * Method called when we negotiate the version with the client.
45 * <p>
46 * Thus, it is only called on the server.
47 * <p>
48 * Will return the actual server version by default.
49 *
50 * @param clientVersion
51 * the client version
52 *
53 * @return the version to send to the client
54 */
55 abstract protected Version negotiateVersion(Version clientVersion);
56
57 /**
58 * Handler called when an unexpected error occurs in the code.
59 *
60 * @param e
61 * the exception that occurred
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 version
74 * the version of this client-or-server
75 */
76 protected ConnectAction(Socket s, boolean server, Version version) {
77 this.s = s;
78 this.server = server;
79
80 if (version == null) {
81 this.version = new Version();
82 } else {
83 this.version = version;
84 }
85
86 clientVersion = new Version();
87 }
88
89 /**
90 * The version of this client-or-server.
91 *
92 * @return the version
93 */
94 public Version getVersion() {
95 return version;
96 }
97
98 /**
99 * Actually start the process (this is synchronous).
100 */
101 public void connect() {
102 try {
103 in = new BufferedReader(new InputStreamReader(s.getInputStream(),
104 "UTF-8"));
105 try {
106 out = new OutputStreamWriter(s.getOutputStream(), "UTF-8");
107 try {
108 if (server) {
109 action(clientVersion);
110 } else {
111 String v = sendString("VERSION " + version.toString());
112 if (v != null && v.startsWith("VERSION ")) {
113 v = v.substring("VERSION ".length());
114 }
115
116 action(new Version(v));
117 }
118 } finally {
119 out.close();
120 }
121 } finally {
122 in.close();
123 }
124 } catch (Exception e) {
125 onError(e);
126 } finally {
127 try {
128 s.close();
129 } catch (Exception e) {
130 onError(e);
131 }
132 }
133 }
134
135 /**
136 * Serialise and send the given object to the counter part (and, only for
137 * client, return the deserialised answer -- the server will always receive
138 * NULL).
139 *
140 * @param data
141 * the data to send
142 *
143 * @return the answer (which can be NULL) if this action is a client, always
144 * NULL if it is a server
145 *
146 * @throws IOException
147 * in case of I/O error
148 * @throws NoSuchFieldException
149 * if the serialised data contains information about a field
150 * which does actually not exist in the class we know of
151 * @throws NoSuchMethodException
152 * if a class described in the serialised data cannot be created
153 * because it is not compatible with this code
154 * @throws ClassNotFoundException
155 * if a class described in the serialised data cannot be found
156 */
157 protected Object send(Object data) throws IOException,
158 NoSuchFieldException, NoSuchMethodException, ClassNotFoundException {
159 synchronized (lock) {
160 String rep = sendString(new Exporter().append(data).toString(true));
161 if (rep != null) {
162 return new Importer().read(rep).getValue();
163 }
164
165 return null;
166 }
167 }
168
169 /**
170 * Reserved for the server: flush the data to the client and retrieve its
171 * answer.
172 * <p>
173 * Also used internally for the client (only do something if there is
174 * contentToSend).
175 * <p>
176 * Will only flush the data if there is contentToSend.
177 *
178 * @return the deserialised answer (which can actually be NULL)
179 *
180 * @throws IOException
181 * in case of I/O error
182 * @throws NoSuchFieldException
183 * if the serialised data contains information about a field
184 * which does actually not exist in the class we know of
185 * @throws NoSuchMethodException
186 * if a class described in the serialised data cannot be created
187 * because it is not compatible with this code
188 * @throws ClassNotFoundException
189 * if a class described in the serialised data cannot be found
190 * @throws java.lang.NullPointerException
191 * if the counter part has no data to send
192 */
193 protected Object rec() throws IOException, NoSuchFieldException,
194 NoSuchMethodException, ClassNotFoundException,
195 java.lang.NullPointerException {
196 String str = flushString();
197 if (str == null) {
198 throw new NullPointerException("No more data available");
199 }
200
201 return new Importer().read(str).getValue();
202 }
203
204 /**
205 * Send the given string to the counter part (and, only for client, return
206 * the answer -- the server will always receive NULL).
207 *
208 * @param line
209 * the data to send (we will add a line feed)
210 *
211 * @return the answer if this action is a client (without the added line
212 * feed), NULL if it is a server
213 *
214 * @throws IOException
215 * in case of I/O error
216 */
217 protected String sendString(String line) throws IOException {
218 synchronized (lock) {
219 out.write(line);
220 out.write("\n");
221
222 if (server) {
223 out.flush();
224 return null;
225 }
226
227 contentToSend = true;
228 return flushString();
229 }
230 }
231
232 /**
233 * Reserved for the server (externally): flush the data to the client and
234 * retrieve its answer.
235 * <p>
236 * Also used internally for the client (only do something if there is
237 * contentToSend).
238 * <p>
239 * Will only flush the data if there is contentToSend.
240 *
241 * @return the answer (which can be NULL)
242 *
243 * @throws IOException
244 * in case of I/O error
245 */
246 protected String flushString() throws IOException {
247 synchronized (lock) {
248 if (server || contentToSend) {
249 if (contentToSend) {
250 out.flush();
251 contentToSend = false;
252 }
253
254 String line = in.readLine();
255 if (server && line != null && line.startsWith("VERSION ")) {
256 // "VERSION client-version" (VERSION 1.0.0)
257 Version clientVersion = new Version(
258 line.substring("VERSION ".length()));
259 this.clientVersion = clientVersion;
260 Version v = negotiateVersion(clientVersion);
261 if (v == null) {
262 v = new Version();
263 }
264 sendString("VERSION " + v.toString());
265
266 line = in.readLine();
267 }
268
269 return line;
270 }
271
272 return null;
273 }
274 }
275 }