Source code: nectar/reda/client/base/ClientComThread.java
1 /*
2 Copyright (C) 2003 Kai Schutte
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18 * ClientComThread.java
19 *
20 * Created on April 7, 2003, 4:57 PM
21 */
22
23 package nectar.reda.client.base;
24
25 import nectar.reda.io.*;
26
27 import nectar.reda.client.action.*;
28 import java.beans.PropertyChangeSupport;
29 import java.beans.PropertyChangeListener;
30 /** Client Communication Thread -- handles all the data communication with the server.
31 * @author Kai Schutte skander@skander.com
32 */
33 public class ClientComThread extends ControllerThread {
34
35 private RedaClientConnection client = new RedaClientConnection();
36 private String login = "login";
37 private String password = "";
38 private Long myUserId = null;
39 private Long myProject = null;
40 private boolean authenticated = false;
41 private ClientJFrame mainWindow = null;
42 private ConnectionStats connStats = null;
43 private ActionPump pump = null;
44 private java.io.ObjectInputStream in = null;
45 private java.io.ObjectOutputStream out = null;
46
47 private ConnectionStatusJDialog statusDialog = null;
48
49 protected PropertyChangeSupport statusChangeListeners;
50 /** Property Change Event description, indicating the percentage progress of the connection establishement.
51 */
52 public static final String CONNECTION_PROGRESS_PERCENT = RedaClientConnection.CONNECTION_PROGRESS_PERCENT;
53 /** Property Change Event description, indicating the currently running operation in the connection establishement process.
54 */
55 public static final String CONNECTION_PROGRESS_STRING = RedaClientConnection.CONNECTION_PROGRESS_STRING;
56 /** Property Change Event description, indicating whether the connection is established or not.
57 */
58 public static final String CONNECTION_STATUS = "CONNECTION_STATUS";
59
60 protected String progressString = null;
61 protected int progressPercent = 0;
62
63 /** Creates a new instance of ClientComThread
64 * @param mainWindow The ClientJFrame instance.
65 * @param pump The ActionPump instance.
66 */
67 public ClientComThread(ClientJFrame mainWindow, ActionPump pump) {
68 super(new ThreadGroup("ClientComThread"), "ClientComThread");
69 this.mainWindow = mainWindow;
70 this.pump = pump;
71 connStats = new ConnectionStats(pump, this);
72 statusChangeListeners = new PropertyChangeSupport(this);
73 }
74
75 /** Reloads configuration variables from the Configuration object. This operation can *ONLY* be performed when the communication is inactive -- when the client is disconnected.
76 * @param config The Configuration instance to copy variables from.
77 * @throws IOException thrown when the connection is active.
78 */
79 public void reloadConfig(Configuration config) throws java.io.IOException {
80 client.setServerHostname(config.get("serverHostname"));
81 client.setServerPort(config.get("serverPort"));
82 client.setSoTimeout(config.get("soTimeout"));
83 login = config.get("login");
84 password = config.get("password");
85 }
86
87 /** Attempts to disconnect from the server.
88 * @throws IOException Any IOException, including when this method is called an a disconnected connection.
89 */
90 public void disconnect() throws java.io.IOException {
91 this.statusChangeListeners.firePropertyChange(CONNECTION_STATUS, new Boolean(true), new Boolean(false));
92 authenticated = false;
93 client.disconnect();
94 }
95
96 public void setStatus(String status, int progress) {
97 String oldStatus = this.progressString;
98 int oldPercent = this.progressPercent;
99 this.progressString = status;
100 this.progressPercent = progress;
101 statusChangeListeners.firePropertyChange(CONNECTION_PROGRESS_PERCENT, new Integer(oldPercent), new Integer(progress));
102 statusChangeListeners.firePropertyChange(CONNECTION_PROGRESS_STRING, oldStatus, status);
103 }
104
105 /** The main running loop for the communication, called when the Thread is started.
106 */
107 public void runThread() {
108 Exception dataExchangeException = null;
109 authenticated = false;
110 try {
111 client.connect();
112 setStatus("Reda: Initializing Object I/O Streams", 65);
113 in = new java.io.ObjectInputStream(client.getInputStream()) {
114 protected Class resolveClass(java.io.ObjectStreamClass desc) throws java.io.IOException, ClassNotFoundException {
115 return ClientJFrame.getClassLoader().loadClass(desc.getName().trim());
116 }
117 };
118 setStatus("Reda: Initializing Object I/O Streams", 70);
119 out = new java.io.ObjectOutputStream(client.getOutputStream());
120 out.flush();
121 } catch (java.io.IOException e) {
122 e.printStackTrace();
123 statusDialog.hide();
124 statusDialog.dispose();
125 mainWindow.displayException(e);
126 this.statusChangeListeners.firePropertyChange(CONNECTION_STATUS, new Boolean(true), new Boolean(false));
127 return;
128 }
129
130 setStatus("Logging in...", 100);
131 // do authentication
132 boolean authResult = false;
133 while (!authResult) {
134 if (this.login != null && this.login.length() > 0 && this.password != null && this.password.length() > 0) {
135 try {
136 authResult = authenticate(in, out);
137 } catch (Exception e) {
138 e.printStackTrace();
139 mainWindow.displayException(e);
140 this.statusChangeListeners.firePropertyChange(CONNECTION_STATUS, new Boolean(true), new Boolean(false));
141 return;
142 }
143 if (authResult) {
144 authenticated = true;
145 setStatus("Connection established", 100);
146 } // else loop
147 }
148 if (!authResult) {
149 LoginJDialog loginDialog = new LoginJDialog(statusDialog, true, this.login);
150 loginDialog.show();
151 if (loginDialog.hasBeenCancelled()) {
152 try {
153 disconnect();
154 } catch (java.io.IOException e) {
155 }
156 return;
157 } else {
158 this.login = loginDialog.getLogin();
159 this.password = loginDialog.getPassword();
160 }
161 }
162 }
163 statusChangeListeners.firePropertyChange(CONNECTION_STATUS, new Boolean(false), new Boolean(true));
164
165 connStats.start();
166 boolean loop = true;
167 if (dataExchangeException == null) {
168 while (keepRunning() && client.isConnected() && loop) {
169 try {
170 Object actionObj = in.readObject();
171 this.pump.returnAction((ClientAction)actionObj); // this can throw a ClassCastException if it wants...
172 } catch (ClassNotFoundException e) {
173 e.printStackTrace();
174 // should log this -- might point to a bug in the updater if we're
175 // getting Object's that we should have a class for...
176 dataExchangeException = e;
177 loop = false; // kill this connection;
178 } catch (java.io.InvalidClassException e) {
179 e.printStackTrace();
180 // ??
181 dataExchangeException = e;
182 loop = false; // kill this connection;
183 } catch (java.io.StreamCorruptedException e) {
184 e.printStackTrace();
185 // this is bad... perhaps the connection got reset, in that case we
186 // should disconnect and display a warning...
187 // else, we probably have a communication bug in the underlying StreamLayers
188 // that corrupted our data, since it's improbable that it's due to the actual
189 // TCP transmission...
190 dataExchangeException = e;
191 loop = false; // kill this connection;
192 } catch (java.io.OptionalDataException e) {
193 e.printStackTrace();
194 // probably a bug in the StreamLayers... there shouldn't be any raw data in
195 // this stream, all lower levels commands should have been recuperated by the
196 // layers...
197 dataExchangeException = e;
198 loop = false; // kill this connection;
199 } catch (java.io.EOFException e) {
200 // abrupt disconnect
201 loop = false; // kill this connection;
202 } catch (java.io.InterruptedIOException e) {
203 // ControllerThread interrupted this thread, perhaps wanting us to shutdown?
204 loop = false; // kill this connection;
205 } catch (java.net.SocketException e) {
206 loop = false;
207 } catch (java.io.IOException e) {
208 // an unknown Exception;
209 dataExchangeException = e;
210 e.printStackTrace();
211 loop = false; // kill this connection;
212 }
213 }
214 }
215
216 try {
217 System.err.println("disconnecting...");
218 System.err.flush();
219 disconnect();
220 } catch (java.io.IOException e) {
221 e.printStackTrace();
222 }
223 if (dataExchangeException != null) {
224 dataExchangeException.printStackTrace();
225 statusDialog.hide();
226 statusDialog.dispose();
227 mainWindow.displayException(dataExchangeException);
228 this.statusChangeListeners.firePropertyChange(CONNECTION_STATUS, new Boolean(true), new Boolean(false));
229 }
230 }
231
232 public void addPropertyChangeListener(PropertyChangeListener listener) {
233 client.addPropertyChangeListener(listener);
234 this.statusChangeListeners.addPropertyChangeListener(listener);
235 }
236
237 public void addPropertyChangeListener(String property, PropertyChangeListener listener) {
238 client.addPropertyChangeListener(property, listener);
239 this.statusChangeListeners.addPropertyChangeListener(property, listener);
240 }
241
242 public void removePropertyChangeListener(PropertyChangeListener listener) {
243 client.removePropertyChangeListener(listener);
244 this.statusChangeListeners.removePropertyChangeListener(listener);
245 }
246
247 /** check whether the connection is established.
248 * @return true if connected, false otherwise.
249 */
250 public boolean isConnected() {
251 return client.isConnected();
252 }
253 /** Check whether authentication has been successful on the current connection.
254 * @return true if authenticated, false otherwise.
255 */
256 public boolean isAuthenticated() {
257 return authenticated;
258 }
259
260 /** Sends a ClientAction to the Server -- Note that you should use the ActionPump mechanism to send commands and receive responses.
261 * @param action The ClientAction to send.
262 * @throws ClientActionException If the action execution, or it's transport through the connection cause any trouble.
263 * @see nectar.reda.client.action.ActionPump
264 * @see nectar.reda.client.action.ClientAction
265 * @see nectar.reda.client.action.ActionPump#doAction
266 */
267 public synchronized void sendAction(ClientAction action) throws nectar.reda.client.action.ClientActionException {
268 if (!this.isConnected() || !this.isAuthenticated())
269 throw new nectar.reda.client.action.ClientActionNotConnectedException();
270 try {
271 this.out.writeObject(action);
272 this.out.flush();
273 } catch (java.io.IOException e) {
274 e.printStackTrace();
275 throw new nectar.reda.client.action.ClientActionException();
276 }
277 }
278
279 public ConnectionStats getConnectionStats() {
280 return this.connStats;
281 }
282
283 public void setStatusDialog(ConnectionStatusJDialog dialog) {
284 this.statusDialog = dialog;
285 }
286
287 public boolean authenticate(java.io.ObjectInputStream in, java.io.ObjectOutputStream out) throws Exception {
288 LoginAction loginAction = new LoginAction();
289 loginAction.setLogin(this.login);
290 loginAction.setPassword(this.password);
291 out.writeObject(loginAction);
292 out.flush();
293 Object obj = in.readObject();
294 if (!(obj instanceof LoginAction))
295 throw new java.io.IOException("Invalid Authentication Response.");
296 loginAction = (LoginAction)obj;
297 if (loginAction.isLoggedIn()) {
298 setMyUserId(loginAction.getUserId());
299 setMyProject(loginAction.getProjectId());
300 return true;
301 }
302 return false;
303 }
304
305 private void setMyUserId(Long id) {
306 this.myUserId = id;
307 }
308
309 public Long getMyUserId() {
310 return myUserId;
311 }
312
313 private void setMyProject(Long project) {
314 this.myProject = project;
315 }
316
317 public Long getMyProject() {
318 return myProject;
319 }
320 }