doc comment about lock holding across session client processes.
[vamsas.git] / src / org / vamsas / client / simpleclient / ClientsFile.java
1 package org.vamsas.client.simpleclient;
2
3 import org.vamsas.client.*;
4
5 import java.io.BufferedInputStream;
6 import java.io.BufferedOutputStream;
7 import java.io.File;
8 import java.io.FileInputStream;
9 import java.io.FileNotFoundException;
10 import java.io.FileOutputStream;
11 import java.io.IOException;
12 import java.io.InputStreamReader;
13 import java.io.ObjectInputStream;
14 import java.io.ObjectOutput;
15 import java.io.ObjectOutputStream;
16 import java.io.OutputStream;
17 import java.util.Vector;
18
19 /**
20  * Handler for the clientsFile within a vamsas session thread.
21  * @author jim 
22  */
23 public class ClientsFile {
24   private File filelist;
25
26   /**
27    * number of my client in list (not known at start but used when known to make
28    * lock)
29    */
30   private int syncnum = 1;
31
32   public ClientsFile(File filelist) throws IOException {
33     this.filelist = filelist;
34     if (!this.filelist.exists())
35       this.filelist.createNewFile();
36   }
37
38   private Lock listlock = null;
39
40   protected boolean lockList(Lock extantlock) {
41     if (listlock!=null && !listlock.isLocked()) {
42       listlock.release();// tidy up invalid lock
43     }
44     listlock=extantlock;
45     return lockList();
46   }
47   /**
48    * Get a lock for the ClientsFile
49    * 
50    * @return true if lock was made
51    */
52   protected boolean lockList() {
53     if (listlock != null)
54       if (listlock.isLocked())
55         return true;
56       else 
57         // lock failed for some reason.
58         listlock.release();
59     listlock = null;
60     if (filelist != null) {
61       if (filelist.exists()) {
62         // TODO: see if we need to loop-wait for locks or they just block until
63         // lock is made...
64         do {
65           if (listlock!=null)
66             listlock.release();
67           listlock = new Lock(filelist); // TODO: wait around if we can't get the lock.
68         } while (!listlock.isLocked());
69         // listlock = new Lock(filelist);
70         return listlock.isLocked();
71       }
72     } else
73       throw new Error(
74           "org.vamsas.client.simpleclient.ClientsFile.lockList called for non-initialised ClientsFile!");
75
76     // no lock possible
77     return false;
78   }
79
80   /**
81    * Explicitly release the ClientsFile lock.
82    * 
83    * @return true if lock was released.
84    */
85   protected void unlockList() {
86     if (listlock != null) {
87       listlock.release();    
88       listlock = null;
89     }
90   }
91
92   /**
93    * internal method for getting clientList - ensures a lock has been made but
94    * does not release it.
95    * 
96    * @return list of clients
97    */
98   private ClientHandle[] retrieveClientHandles() {
99     if (lockList()) {
100       try {
101         ClientHandle[] clients=null;
102         if (this.listlock.rafile.length()>0) {
103           ObjectInputStream is = new ObjectInputStream(new BufferedInputStream(
104               new java.io.FileInputStream(filelist)));
105           Object o;
106           o=is.readObject();
107           if (o!=null) {
108             try {
109               clients = (ClientHandle[]) o;
110             }
111             catch (Exception e) {
112               System.err.println("Garbage in the clientHandle list "+this.filelist);
113             }
114           }
115         }
116         return clients;
117       } catch (FileNotFoundException e) {
118         // TODO Auto-generated catch block
119         e.printStackTrace(System.err);
120       } catch (Exception e) {
121         e.printStackTrace(System.err);
122       }
123     }
124     return null;
125   }
126   /**
127    * get the clientList from the file. May return null if lock failed!
128    * @return clientList
129    */
130   public ClientHandle[] retrieveClientList() {
131     if (lockList()) {
132       ClientHandle[] clients = retrieveClientHandles();
133       unlockList();
134       return clients;
135     }
136     return null;
137   }
138   /**
139    * get list from the locked ClientList.
140    * @param extantlock
141    * @return clientList or null if lock failed (or file was empty)
142    */
143   public ClientHandle[] retrieveClientList(Lock extantlock) {
144     if (lockList(extantlock)) {
145       ClientHandle[] clients = retrieveClientHandles();
146       unlockList();
147       return clients;
148     }
149     return null;
150   }
151   /**
152    * adds clientHandle me to the clientList under an existing lock extantLock.
153    * @param me
154    * @param extantLock
155    * @return client index in list or 0 if lock was invalid or addClient operation failed.
156    */
157   public int addClient(ClientHandle me, Lock extantLock) {
158     return addClient(me, true, extantLock);
159   }
160   
161   /**
162    * adds clientHandle me to the clientList under an existing lock.
163    * @param me - clientHandle
164    * @param disambig - if true then add will fail if an identical clientHandle already exists
165    * @param extantLock - existing lock
166    * @return client index in list or 0 if addClient (or the lock) failed.
167    */
168   
169   public int addClient(ClientHandle me, boolean disambig, Lock extantLock) {
170     if (lockList(extantLock)) {
171       syncnum = addClient(me, disambig);
172       unlockList();
173       return syncnum;
174     }
175     return 0;
176   }
177   
178   /**
179    * adds the ClientHandle to the list - if it is not unique, then the
180    * ClientHandle object is modified to make it unique in the list. returns the
181    * clientNumber for the client in the session.
182    * 
183    * @param me
184    * @return
185    */
186
187   public int addClient(ClientHandle me) {
188     syncnum = addClient(me, true);
189     unlockList();
190     return syncnum;
191   }
192
193   /**
194    * removes 'me' from the session ClientList without complaint if 'me' isn't in the clientList already.
195    * @param me client handle to be removed
196    * @param clientlock existing lock passed from watcher.
197    */
198   public void removeClient(ClientHandle me, Lock clientlock) {
199     int mynum=-1;
200     if (lockList(clientlock)) {
201       ClientHandle[] clients = retrieveClientHandles();
202       if (clients != null) {
203         if ((syncnum<=0 || syncnum>clients.length) || clients[syncnum-1]!=me) {
204           for (int i = 0, j = clients.length; i < j; i++) 
205             if (clients[i].equals(me)) {
206               mynum=i;
207               break;
208             }
209         } else {
210           mynum=syncnum-1;
211         }
212         if (mynum>-1) {
213           ClientHandle[] newlist = new ClientHandle[clients.length - 1];
214           for (int k=0,i = 0, j = clients.length; i < j; i++)
215             if (i!=mynum)
216               newlist[k++] = clients[i];
217           if (!putClientList(newlist))
218             throw new Error("Failed to write new clientList!"); // failed to put the clientList to disk.
219         }
220       }
221       unlockList();
222     } else {
223       throw new Error("Couldn't get lock for "+((filelist==null) ? "Unitialised filelist in ClientsFile" : filelist.getAbsolutePath()));
224     }
225   }
226   /**
227    * Adds a ClientHandle to the ClientList file - optionally disambiguating 
228    * the ClientHandle (modifes the URN). 
229    * Note: Caller is left to release the lock on the ClientList.
230    * @param me
231    * @param disambiguate -
232    *          flag indicating if the URN for me should be disambiguated to
233    *          differentiate between sessions.
234    * @return index of clientHandle in new list, or -1-position of existing
235    *         clientHandle (if disambiguate is true)
236    */
237   protected int addClient(ClientHandle me, boolean disambiguate) {
238     int newclient = 0;
239     if (lockList()) {
240       ClientHandle[] clients = retrieveClientHandles();
241       if (me.getClientUrn()==null) {
242         // TODO: move this into ClientUrn as a standard form method.
243         me.setClientUrn("vamsas://"+me.getClientName()+":"+me.getVersion()+"/");
244       }
245       if (clients == null) {
246         clients = new ClientHandle[1];
247         clients[0] = me;
248         newclient = 1;
249       } else {
250         int k = 0;
251         for (int i = 0, j = clients.length; i < j; i++) {
252           if (clients[i].equals(me)) {
253             if (disambiguate) {
254               while (clients[i].equals(me)) {
255                 me.setClientUrn(me.getClientUrn() + k++); // TODO: make a better
256                                                           // disambiguation of
257                                                           // urn.
258               }
259             } else {
260               // will not write the ambiguous clientHandle to disk, just return
261               // its index.
262               return -1 - i;
263             }
264           }
265         }
266         int i, j;
267         ClientHandle[] newlist = new ClientHandle[clients.length + 1];
268         for (i = 0, j = clients.length; i < j; i++)
269           newlist[i] = clients[i];
270         newlist[j] = me;
271         clients = newlist;
272         newclient = j+1;
273       }
274       if (!putClientList(clients))
275         return 0; // failed to put the clientList to disk.
276     }
277     return newclient;
278   }
279
280   /**
281    * safely writes clients array to the file referred to by filelist.
282    * 
283    * @param clients
284    * @return true if successful write. Throws Errors otherwise.
285    */
286   protected boolean putClientList(ClientHandle[] clients) {
287     if (lockList()) {
288       File templist = null;
289       try {
290         templist = File.createTempFile(filelist.getName(),".old", filelist.getParentFile());
291         FileOutputStream tos = new FileOutputStream(templist);
292         tos.getChannel().transferFrom(listlock.rafile.getChannel(), 0,
293             listlock.rafile.length());
294         tos.close();
295       } catch (FileNotFoundException e1) {
296         System.err.println("Can't create temp file for clientlist");
297         e1.printStackTrace(System.err);
298       } catch (IOException e1) {
299         System.err
300             .println("Error when copying content to temp file for clientlist");
301         e1.printStackTrace(System.err);
302       }
303       if (templist != null) {
304         try {
305           listlock.rafile.setLength(0);
306           ObjectOutputStream os = new ObjectOutputStream(
307               new BufferedOutputStream(new FileOutputStream(this.filelist)));
308           os.writeObject(clients);
309           os.close();
310           // All done - remove the backup.
311           templist.delete();
312           templist = null;
313         } catch (Exception e) {
314           if (templist != null) {
315             System.err
316                 .println("Serious - problems writing to filelist. Backup in "
317                     + templist.getAbsolutePath());
318             e.printStackTrace(System.err);
319           }
320         }
321       } else {
322         throw new Error(
323             "Couldn't create backup of the clientList before writing to it!");
324       }
325     } else {
326       throw new Error("Could not lock the clientList: "
327           + ((filelist == null) ? "Unitialized ClientsFile"
328               : " failed to get lock on " + filelist.getAbsolutePath()));
329     }
330     // successful!
331     return true;
332   }
333 }