View Javadoc

1   /*
2    * Copyright (c) 2005 The University of Reading
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    * 1. Redistributions of source code must retain the above copyright
9    *    notice, this list of conditions and the following disclaimer.
10   * 2. Redistributions in binary form must reproduce the above copyright
11   *    notice, this list of conditions and the following disclaimer in the
12   *    documentation and/or other materials provided with the distribution.
13   * 3. Neither the name of the University of Reading, nor the names of the
14   *    authors or contributors may be used to endorse or promote products
15   *    derived from this software without specific prior written permission.
16   * 
17   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20   * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27   */
28  
29  package uk.ac.rdg.resc.jstyx.gridservice.client;
30  
31  import java.util.Vector;
32      
33  import org.apache.mina.common.ByteBuffer;
34  
35  import uk.ac.rdg.resc.jstyx.messages.TreadMessage;
36  import uk.ac.rdg.resc.jstyx.messages.TwriteMessage;
37  import uk.ac.rdg.resc.jstyx.messages.StyxBuffer;
38  import uk.ac.rdg.resc.jstyx.client.StyxConnection;
39  import uk.ac.rdg.resc.jstyx.client.CStyxFile;
40  import uk.ac.rdg.resc.jstyx.client.CStyxFileChangeAdapter;
41  import uk.ac.rdg.resc.jstyx.types.DirEntry;
42  import uk.ac.rdg.resc.jstyx.StyxException;
43  import uk.ac.rdg.resc.jstyx.StyxUtils;
44  import uk.ac.rdg.resc.jstyx.gridservice.config.SGSConfig;
45  import uk.ac.rdg.resc.jstyx.gridservice.config.SGSConfigException;
46  
47  /***
48   * Client for a Styx Grid Service.
49   *
50   * @author Jon Blower
51   * $Revision: 588 $
52   * $Date: 2006-02-20 17:34:27 +0000 (Mon, 20 Feb 2006) $
53   * $Log$
54   * Revision 1.16  2006/02/20 17:34:27  jonblower
55   * Added getConnection() method
56   *
57   * Revision 1.15  2006/01/05 16:06:34  jonblower
58   * SGS clients now deal with possibility that client could be created on a different server
59   *
60   * Revision 1.14  2005/12/07 17:50:48  jonblower
61   * Fixed bug and comments for getConfig()
62   *
63   * Revision 1.13  2005/12/07 08:53:08  jonblower
64   * Improved getConfig() and changed in line with change to SGSInstanceClient constructor
65   *
66   * Revision 1.12  2005/12/01 08:29:47  jonblower
67   * Refactored XML config handling to simplify clients
68   *
69   * Revision 1.11  2005/11/07 21:03:22  jonblower
70   * Added getConfigXML() method
71   *
72   * Revision 1.9  2005/09/11 18:51:52  jonblower
73   * Added error() method and changed name from getInstances() to getInstancesAsync()
74   *
75   * Revision 1.8  2005/08/12 08:08:39  jonblower
76   * Developments to support web interface
77   *
78   * Revision 1.7  2005/07/06 17:53:43  jonblower
79   * Implementing automatic update of SGS instances in SGS Explorer
80   *
81   * Revision 1.6  2005/05/17 15:10:40  jonblower
82   * Changed structure of SGS to put instances in a directory of their own
83   *
84   * Revision 1.5  2005/05/11 18:24:59  jonblower
85   * Implementing automatic detection of service data elements
86   *
87   * Revision 1.4  2005/03/22 17:44:17  jonblower
88   * Changed to use CStyxFileChangeAdapter instead of Listener and removed empty methods
89   *
90   * Revision 1.3  2005/03/19 21:47:02  jonblower
91   * Further fixes relating to releasing ByteBuffers
92   *
93   * Revision 1.2  2005/03/18 13:55:59  jonblower
94   * Improved freeing of ByteBuffers, and bug fixes
95   *
96   * Revision 1.1  2005/03/16 22:16:44  jonblower
97   * Added Styx Grid Service classes to core module
98   *
99   * Revision 1.3  2005/03/16 17:59:35  jonblower
100  * Changed following changes to core JStyx library (replacement of java.nio.ByteBuffers with MINA's ByteBuffers)
101  *
102  * Revision 1.2  2005/02/21 18:12:09  jonblower
103  * Following changes to core JStyx library
104  *
105  * Revision 1.1  2005/02/16 19:22:29  jonblower
106  * Commit adding of SGS files to CVS
107  *
108  */
109 public class SGSClient extends CStyxFileChangeAdapter
110 {
111     
112     private CStyxFile sgsRoot;   // The file at the root of the SGS
113     private CStyxFile cloneFile; // The file that we read to create new instances
114     private CStyxFile instancesFile; // The special file (behaves like a directory)
115                                  // that is read to give the instances of the
116                                  // services
117     private String description;  // The short description of this SGS
118     private SGSConfig config;    // Object describing how each instance is configured
119     private Vector instances;    // Vector of CStyxFiles representing the root of
120                                  // each instance
121     private boolean gettingInstances;
122     private Vector changeListeners; // The objects that will be notified of changes to this SGS
123     
124     /***
125      * Creates a new instance of SGSClient.
126      * @param sgsRoot The CStyxFile representing the root of the SGS
127      */
128     public SGSClient(CStyxFile sgsRoot)
129     {
130         this.sgsRoot = sgsRoot;
131         this.cloneFile = this.sgsRoot.getFile("clone");
132         this.instancesFile = this.sgsRoot.getFile(".instances");
133         this.cloneFile.addChangeListener(this);
134         this.instancesFile.addChangeListener(this);
135         this.gettingInstances = false;
136         this.changeListeners = new Vector();
137         this.instances = new Vector();
138     }
139     
140     /***
141      * @return the connection object
142      */
143     public StyxConnection getConnection()
144     {
145         return this.sgsRoot.getConnection();
146     }
147     
148     /***
149      * @return the name of this SGS (i.e. the name of the root CStyxFile)
150      */
151     public String getName()
152     {
153         return this.sgsRoot.getName();
154     }
155     
156     /***
157      * Gets the short description of this Styx Grid Service.  If the description
158      * has not previously been set, this method will sent a message to get the
159      * new description and this method will block until the reply arrives.
160      * @throws StyxException if an error occurs when getting the description from
161      * the server.  Will never be thrown if the description has already been set.
162      */
163     public String getDescription() throws StyxException
164     {
165         if (this.description == null)
166         {
167             CStyxFile descFile = this.sgsRoot.getFile("docs/description");
168             this.description = descFile.getContents();
169         }
170         return this.description;
171     }
172     
173     /***
174      * <p>Reads the configuration file from the server so that we know how to parse
175      * parameters, deal with input files etc.  Some of this information cannot be
176      * gleaned simply from interpreting the namespace itself.</p>
177      * <p>The first time this is called, the server will be queried for the 
178      * config information.  This information will be cached so that further
179      * calls to this method will not lead to the server being queried again,
180      * but will return the cached object.</p>
181      * @throws StyxException if there was an error reading the configuration
182      * from the server
183      */
184     public synchronized SGSConfig getConfig() throws StyxException
185     {
186         try
187         {
188             if (this.config == null)
189             {
190                 this.config = new SGSConfig(this.sgsRoot.getFile("config").getContents());
191             }
192             return this.config;
193         }
194         catch (SGSConfigException sce)
195         {
196             // This is unlikely to happen: the server should return valid
197             // configuration XML
198             throw new StyxException(sce.getMessage());
199         }
200     }
201     
202     /***
203      * Requests creation of a new instance of the SGS on the server.  This method
204      * blocks until the instance has been created.
205      * @return The full URL to the root of the new instance, e.g.
206      * <code>styx://thehost.com:9092/mySGS/instances/1234567890abcde</code>. 
207      * Note that the new instance may be created on a different server (for
208      * load balancing purposes, for example).
209      */
210     public String createNewInstance() throws StyxException
211     {
212         return this.cloneFile.getContents();
213     }
214     
215     /***
216      * Gets a file that represents the root of the SGS instance with the given
217      * ID.  Makes a blocking read to the server to check to see if the instance
218      * exists.
219      * @return A CStyxFile representing the root of the instance
220      * @throws StyxException if the instance does not exist
221      */
222     public CStyxFile getInstanceFile(String instanceID) throws StyxException
223     {
224         CStyxFile instanceFile = this.sgsRoot.getFile("instances/" + instanceID);
225         if (!instanceFile.exists())
226         {
227             throw new StyxException("There is no instance with ID " + instanceID
228                 + " on this server.");
229         }
230         return instanceFile;
231     }
232     
233     /***
234      * Sends message to get all the instances of this SGS.  When the instances
235      * arrive, the gotInstances() event will be fired on all registered
236      * change listeners.  This method does not block.
237      */
238     public void getInstancesAsync()
239     {
240         // If we have already called this method, do nothing; the event will
241         // still be fired on all change listeners
242         if (!this.gettingInstances)
243         {
244             this.gettingInstances = true;
245             this.instancesFile.readAsync(0);
246         }
247     }
248     
249     /***
250      * Required by the CStyxFileChangeListener interface
251      */
252     public synchronized void dataArrived(CStyxFile file, TreadMessage tReadMsg, 
253         ByteBuffer data)
254     {
255         if (file == this.instancesFile)
256         {
257             // TODO: This nearly repeats code in CStyxFile.GetChildrenCallback.
258             // Can we refactor this?  Problem is that in this case we don't want
259             // the instances file to be closed after reading the children, or
260             // we won't get the desired async behaviour.
261             
262             // If this is the first read from this file, clear the cache of
263             // children
264             if (tReadMsg.getOffset().asLong() == 0)
265             {
266                 this.instances.clear();
267             }
268             
269             // We have got a (possibly partial) list of instances for this SGS
270             long offset = tReadMsg.getOffset().asLong() + data.remaining();
271             if (data.remaining() > 0)
272             {
273                 // Wrap data as a StyxBuffer
274                 StyxBuffer styxBuf = new StyxBuffer(data);
275                 // Get all the DirEntries from this buffer
276                 while(data.hasRemaining())
277                 {
278                     DirEntry dirEntry = styxBuf.getDirEntry();
279                     CStyxFile instance = this.sgsRoot.getFile("instances/" +
280                         dirEntry.getFileName());
281                     instance.setDirEntry(dirEntry);
282                     this.instances.add(instance);
283                 }
284                 // Read from this file again
285                 this.instancesFile.readAsync(offset);
286             }
287             else
288             {
289                 // We've read all the data from the file
290                 CStyxFile[] instancesRoots =
291                     (CStyxFile[])this.instances.toArray(new CStyxFile[0]);
292                 this.fireGotInstances(instancesRoots);
293                 // Start reading again from the start of the file
294                 this.instancesFile.readAsync(0);
295             }
296         }
297     }
298     
299     /***
300      * This callback is called if there has been an error with one of the
301      * asynchronous methods
302      */
303     public void error(CStyxFile file, String message)
304     {
305         if (file == this.cloneFile)
306         {
307             this.fireError("Error creating new instance: " + message);
308         }
309         else if (file == this.instancesFile)
310         {
311             this.fireError("Error reading instances: " + message);
312         }
313     }
314     
315     /***
316      * Adds a new SGSChangeListener to the list of registered change listeners.
317      * If the given listener has already been registered, this method does nothing.
318      */
319     public void addChangeListener(SGSChangeListener listener)
320     {
321         synchronized(this.changeListeners)
322         {
323             if (!this.changeListeners.contains(listener))
324             {
325                 this.changeListeners.add(listener);
326             }
327         }
328     }
329     
330     /***
331      * Removes a SGSChangeListener to the list of registered change listeners.
332      * If the given listener has not been registered, this method does nothing.
333      */
334     public void removeChangeListener(SGSChangeListener listener)
335     {
336         synchronized(this.changeListeners)
337         {
338             this.changeListeners.remove(listener);
339         }
340     }
341     
342     /***
343      * Fires the gotInstances() event in all registered change listeners
344      * @param instances The instances belonging to this SGS
345      */
346     private void fireGotInstances(CStyxFile[] instances)
347     {
348         synchronized(this.changeListeners)
349         {
350             for (int i = 0; i < this.changeListeners.size(); i++)
351             {
352                 SGSChangeListener listener = (SGSChangeListener)this.changeListeners.get(i);
353                 listener.gotInstances(instances);
354             }
355         }
356     }
357     
358     /***
359      * Fires the error() event in all registered change listeners
360      * @param message the error message
361      */
362     private void fireError(String message)
363     {
364         synchronized(this.changeListeners)
365         {
366             for (int i = 0; i < this.changeListeners.size(); i++)
367             {
368                 SGSChangeListener listener = (SGSChangeListener)this.changeListeners.get(i);
369                 listener.error(message);
370             }
371         }
372     }
373 }