I want to disallow a client from being granted locks when they already have a lock (in order to avoid deadlocks), but does this actually meet the requirements? I don't want my code to fail some automated test because the behaviour of the method is different to what the test code expects.
Also I want to add a public lock(int recordNos) method to allow a client to lock multiple records at once. Can I add this to the DBMain interface, or are we supposed to implement the interface exactly, with no changes?
Create a new interface, say Database. Have Data class implement DB and Database. Have DBRemote interface extend Remote and Database. Have DBRemoteAdapter class extend UnicastRemoteObject and implement DBRemote.
Database declares all of the methods in DB plus any new ones, such as your multiunlock method. It should throw the lowest common denominator Exception class, which you will probably have as IOException (RemoteException extends this).
Any class wishing to talk to a database, whether it is remote or local, calls methods on an object of class Database. This solves many problems, such as Adapter pattern use of local / remote Data object through a unified interface, plus allows you to extend DB to include other much needed functionality.
Do NOT extend RuntimeException as you may have read elsewhere. Checked exceptions to the client all the way. LockManager should be a singleton, personally I also have a RMIRegistryService and a LogManager as singletons too.
Hmm personally I think that there may be a case for throwing RuntimeExceptions. I had a read method in the supplied interface which only threw a RecordNotFoundException. I felt that, IOExceptions also needed to be thrown, and considered it better to wrap the IOException in a RuntimeException rather than subvert the RecordNotFoundException to a different purpose.
That was what I chose to do anyway. At the end of the day, the point of the exercise is to make you think and to justify your own choices.