Hi all, is the following solution an overkill? Sounds a little bit complex but keeps the DB-server very generic and reusable for other projects. Any comments welcome. Thanks Rainer
The client uses a DataInterface to talk to the DB. This interface is implemented by the Data class for local mode (direct file acccess) and defines all public methods of Data. For network mode the client uses a client side RMIDataAdapter class which implements the same interface and handles the RMI communication transparently. The RMIDataAdapter initially looks up a RMIDataServerFactory which it uses to get a handle to a RMIDataServer object on the server. The RMIDataServer uses an instance of Data to access the DB file and exposes all public methods of Data to the client through the RMIDataAdapter. There is only one RMIDataSeverFactory permanently running on the server. All clients acessing any DB on the server share this common object. There may be multiple RMIDataServer, one for each different DB file that is accessed. For the booking application there is currently only one, but there may be other DB files and thus RMIDataServer in the future for other applications (customer management or so). All clients accessing a single DB file share this object, which is weakly cached in the RMIDataServerFactory. As soon as no more clients use this object it's garbage collected by the DGC. That way we have a generic data server that can be used to access multiple data files on the same server. The RMIDataServer objects are reused by other clients they are likely to survive iondividual client connections. Therefore it is necessary to ensure that the locks created by a client are removed, even if the client dies after locking a record. This is done by one RMISessionMonitor per connection and RMIDataServre that is garbage collected by the DGC after the session died. Upon garbage collection this monitor removes any existing locks. Thats it :-)
Sounds very similar to my approach. I have a DataServer that produces remote Connection objects. These Connection objects implement the same public interface as the Data object. The client looks up the DataServer and then gets a Connection to a Data object. It may be a little overkil, but it keeps things need and allows easy modification. One problem I am stuck with is how to make the DataServer multi threaded. Any ideas??
Making all methods that may cause a modification to the data (also locks and filepointer!) synchronized should be sufficient for synch issues. The other point is to use wait() & notify() to implement the locking. I am currently using the wait() on the DataObject itself and then notifyAll(). It would be a little bit more efficient to wait() on the individual locks the notify would only wake up threads that wait for a lock on the specific record. But since concurrent updates won't happen that often I think it's OK - otherwise they should use a DB and not that silly stuff they provided us ;-)
I like your implementation. However, how will a client specefiy which database he wants to connect to? The server can only start using datafile names. So it would not be realistic for the client to know the location of the server databse would it? What I mean is while connecting from the client, I wouldn't want to know the remote database path would I?
Never be satisfied with anything less than the best and you will surely pass the test...
Hi, Personally, I'm against naming classes in such a way that suggests/implies how they are implemented. Why do you need to use 'RMI' in the class name? For instance, RMIDataAdapter? Why not just call it DataAdapter? This is particularly the case when the object is on the client-side. The whole point of RMI is that once you have a reference to a remote object you should use it as other objects. I suspect that your RMIDataAdpater is more of a proxy than an adapter. Although you haven't mentioned it explicitly, I hope that your RMIDataServerFactory implements the Singleton pattern so that only one instance of the factory can be instantiated. I'm not sure why you have coupled the RMIDataServer class to the RMIDataServerFactory? Surely the factory should just return a references to an instance of the object it is creating. The fact that it returns a reference is the key here - the factory should not know or care whether it is a new instance or a reference to an existing instance that is returned. In this case it may be better to have the RMIDataServer implement the Singleton pattern and it can control how many instances of itself can be created. If you need to create a new object for a separate database then the singleton could use a mapping so that only one RMIDataServer is created for each database. I hope this helps, Steve
Kalichar, Steve, quite some time since my post... In the meantime I changed the implementation quite a lot. I was busy with my job and had no time to work on the assignmend for a couple of months. When I re-started I had a fresh look at the project and found my design flaws pretty soon. I changed a lot of stuff before submitting, all points Steve mentions were changed. I also introduced the concept of a FlightBean and a FlightService which helped me a lot to cleanup the project. Re-Factoring is the key!
When I submitted, I had one remote DataSession per client instance which holds a reference to a shared Data instance that performs the file related work, a remote DataSessionFactory (singleton) which was used to construct a DataSession and pass the reference back. That factory was looked up using the naming stuff. On the client side there was a DataFactory which created either a local Data instance or looked up the DataSessionFactory to build a DataProxy which was connected to the DataSession to access remote data. BTW: In my case the client has to know what DB-file it is talking to. yu may use conventions here, e.g. the server knows the path and the client only knows the filename or so. It's the same as with a typical C/S system where the client knows which DB to talk to. In the real world you would be required to build a bullet-proof security file to ensure that clients only see what they are allowed to see.
I guess that my stuff was somewhat useful. I got 152 points which makes 98% ;-) Rainer
Kalichar, the locking is implemented in the Data class via modification. I did not add an additional layer although I now think I should have done so. It makes the whole thing clearer.
I have one Data instance per DB-File which is shared amongst all clients using this particular file. The Data class implements a DataInterface interface which extracts the public interface of the Data manipulation methods. Each client has a DataProxy instance on the client side that implemets the same DataInterface so that the client does not need to know whether it is dealing with a local or remote DB file. The DataProxy itself is connected to a server side DataSession instance. This DataSession belongs to one single DataProxy and has a reference to the shared Data instance and performs the real work using this Data instance.
The Data class has the following lock method: public void lock(Object session, int record) This method puts the lock into a hashmap where the key is an Integer with the record id and the value is the session object. It checks for an existing record or DB lock before, of course. For multi-user mode the client executes the lock(int record) method on the local DataProxy which executes lock(int record) on the remote DataSession which in turn executes lock(Object session, int record) on the shared Data instance. The DataSession passes itself a the session parameter to the lock method. For single user mode the client directly executes the method lock(int record) on the Data instance which does nothing but call lock(Object session, int record) with a String constant as the session object.
That way the DataSession holding any lock can be identified and vice versa. That means that as soon as the DGC collects the DataSession of a dead client you are able to remove the associated locks. I synchronized on the Data instance, so notify() always wakes up all threads waiting for a lock, no matter what lock it is. I did not implement a lock queue either. From what I can see this is a perfect fit. Not too easy, not too complex (it is mentioned in my assignment description that it should be understandable by junior programmers). Good luck! Rainer
Hi Kalichar, I started with a simple design and came to the conclusion that a more robust solution would not be much more effort. The first full-featured but quick-and-dirty solution took me about 20-30 hours without any docs. I than had to stop working on it for about half a year because of job demands. Afterwards I had a fresh look at it, identified a lot of design weaknesses and started refactoring the complete system until it looked like I wanted it to be (again around 20 hours). Plus two weekends for complete documentation including full JavaDoc. Looking back it seems to be a good way to go if you have the time. Complete it, try to forget everything about it and restart with a fresh mind. You quickly identify areas which are not as clearly designed as they could be. I had the time as this was just fun stuff for me (Java developer and consultant since 1997). Regards Rainer