Distributed Systems Lab/06


Lab 4 / DSGFileMan - A simple versioning server (RMI)

Introduction

In Lab3, you had your first hands-on experience with a distributed object system (i.e., Java Remote Method Invocation -- RMI). If you skipped Lab3, read the Lab3 description first; especially the short introduction to versioning systems.

In Lab4, more exciting things are waiting to be discovered. Your objective is to implement the server part of the DSGFileMan versioning system. The DSGFileMan server stores documents in a repository, and provides its versioning functionality as a remote object.

Detailed Description

Your objective is to develop the server for the DSGFileMan versioning system (a "downsized" version of Subversion ;-)) with Java RMI. The DSGFileMan server allows users to create and delete repositories. In addition, the server provides the operations checkout, import, commit, delete, and update of repository documents. In addition, the DSGFileMan server provides a "watch dog", i.e. a notification mechanism that informs about modifications of repository documents.

Implementation Details

In this lab, you have to implement three interfaces for your server. The first one is IFileManager which implements the server logic. Those of you who did Lab3 are already familiar with this interface. All the others should read the Lab3 description first.

The second interface you should implement is IRepository. This interface provides the basic functionality of the repository, which is responsible for creating, deleting, and accessing the repositories physically on disk. It therefore provides methods to checkout, import, commit, update, and delete repository documents (represented by IDocument).

Finally, you have to implement IFileManServer. This interface is mainly used for starting the IFileManager and shutting it down in a clean way.

Before you start implementing your server, you should download the DSGFileMan-lab4.jar that contains all the classes and interfaces you need for implementing this lab. Note that this JAR file differs from the one you used for implementing Lab3. For example, it additionally contains IFileManServer and IRepository. For solving Lab4, include this JAR file to your CLASSPATH.

Implementing IRepository

You may want to start by implementing the functionality for reading/writing repositories (and repository documents) physically on disk. Therefore, you should implement the IRepository interface. Read the JavaDoc documentation of this interface to get more details about all methods. Note that this interface is only a recommendation by us. As it does not appear as parameter or return value in the IFileManager interface, you do not have to implement it. Thus, use it as guidance or feel free to implement your own repository ;-)
However, note that we can only provide little support if you do not use the IRepository interface for implementing your repository.

On the client implemented in Lab3, you have only stored the most recent document version. In contrast to this, the server has to store all versions of the repository documents, to be able to revert to older versions of a document. However, it is up to you how you store the documents. In any case, you have to assure that name, version, and content of the documents are preserved.

One way to store the repositories and repository documents on the server is to use a separate directory for each repository. For instance, repository dsgfm999_repo1 is stored in $REPO_ROOT/dsgfm999_repo1, while repository dsgfm888_testrepository is stored in $REPO_ROOT/dsgfm888_testrepository.
In these directories, you will store the repository configuration (owner, allowed users, etc.), and all versions of the repository documents. You may distinguish between different versions of a document by appending the version number to the filename of the document. For instance, if you have three versions of document document.txt, you may store them as document.txt.1, document.txt.2, document.txt.3.

Implementing IFileManager

After implementing the mechanism to physically store repositories and repository documents on disk, you are ready to implement the server logic.
Therefore, create a new public class called FileManagerImpl and place it in the package at.ac.tuwien.dslab.rmi.server. This class has to be a remote object (the server), and has to implement the IFileManager interface. You find a short description of each method below. The JavaDoc comments give you a more detailed method specification (method parameters, which exceptions to throw, etc.). We recommend that you carefully read the JavaDoc documentation of this interface...
...so does the grading robot ;-)

Interface methods

Most of the methods take the name of the repository, and the user who wants to access the repository as arguments. Each method has to check if the given repository really exists (otherwise throw a RepositoryNotExistsException), and if the given user is valid and allowed to access the repository (otherwise throw a UserAccessException).

The createRepository() method creates a repository on the server, After creating the repository, the owner and all allowed users are able to access the repository.

The removeRepository() method removes a repository from the server, if the given user matches to the owner of the repository (i.e., only the owner is allowed to delete the repository).

The checkout() method performs an initial checkout of the repository (i.e., it returns the most recent versions of all repository documents).

The importDocument() method imports a new document to the repository (i.e., the document must not already exist in the repository). The version number of the document is set to 1.

The commitDocument() method commits a new version of a document to the repository (i.e., the document must already exists in the repository). The new version of the document is only committed, if the version number is correct. You therefore have to implement a simple versioning logic (see below).

The removeDocument() method deletes (all versions of) a document from the repository.

The updateAllDocuments() method gets all documents from the repository. Note that this method only returns the most recent versions of all documents.

The updateDocument() method gets a specific document version from the repository. The version number of the requested document is taken as argument. If the version number is -1, the method returns the most recent version of the document. If the requested document does not exist in the repository, you should return null.

As described above, the server provides a "watch dog" mechanism that sends notifications to all registered listeners. Thus, your server implementation needs a way to register and remove repository listeners; see the methods addRepositoryListener() and removeRespositoryListeners() (we are sorry for this typo introduced in Lab3 :), respectively. The repository listener is represented by the IRepositoryListener interface. If certain events occur, you have to invoke the appropriate methods of all registered listeners:

  • documentImported: if a document was imported to the repository
  • documentCommitted: if a document was committed to the repository
  • documentRemoved: if a document was removed from to the repository
Please make sure that you read the reference on RMI callbacks from the reading suggestions below, in order to understand remote events, callbacks, etc.

Constructor

In the constructor of FileManagerImpl, initialize all required information (e.g., a list of registered repository listeners). In addition, you have to read the user account information from the user file located on the CLASSPATH. The name of the user file can be found in the property file. More details about this property file are explained in the IFileManServer section below.
For testing purposes, you find a sample user account file test-accounts.txt in your home directory. In addition, the file can also be found here. The user account file contains all users that are allowed to access the DSGFileMan system. Each line of this file contains one pair of account name and account password, which are separated by a colon (":").
Don't forget to add the sample user account file to your classpath! Moreover, add your own account information (dsgfmXXX:<yourpassword>) to this file, to be able to test your server with your dsgfm-account.

Versioning logic

Your server has to implement the following simple versioning logic. If a client wants to commit a document, there are three possibilities: The version number of the document to commit can be

  1. smaller than the version on the server: That means, the client does not have the most recent version of the document and is therefore not allowed to commit. In that case, throw a FileManException with a meaningful exception message.
  2. equal to the version on the server: The document can be committed. Therefore, the server stores the new document version, increments the version number by 1, and returns the new version number to the client.
  3. greater than the version on the server: In fact, this should never happen (i.e., the client is not working correctly). In that case, throw a FileManException with a meaningful exception message.
We are aware, that our versioning logic is quite simplified. In contrast to more sophisticated tools such as Subversion or CVS, we do not track the actual changes of document contents, but only concentrate on the version number of the documents. However, the main goal of Lab3 and Lab4 is to learn the basics of RMI, and not how to resolve version conflicts.

Synchronization and null values

Furthermore, you have to consider proper synchronization mechanisms because multiple clients can connect concurrently. Synchronize only the critical and necessary code blocks.

Please also make sure that your server implementation is robust against remote method calls with "null" values. If you encounter null values, throw appropriate exceptions (e.g., InvalidArgumentException, etc).

Implementing IFileManServer

Finally, you have to provide a way to start and stop your server. Therefore, implement the IFileManServer interface:

01 package at.ac.tuwien.dslab.rmi.common.interfaces;
02 
03 import at.ac.tuwien.dslab.rmi.common.impl.FileManException;
04 
05 /**
06  * The <code>IFileManServer</code> defines an interface
07  * for starting and stopping the RMI server.
08  
09  @author Florian Rosenberg
10  @author Anton Michlmayr
11  */
12 public interface IFileManServer {
13   
14   /**
15    * Starts the RMI server. Everything which is necessary to handle 
16    * the correct startup of the server has to be done here.
17    @throws FileManException If an error occurs while starting up the RMI server.
18    */
19   public void start() throws FileManException;
20   
21   /**
22    * Stops the RMI server. Every cleanup which may be necessary has to be done here.
23    @throws FileManException If an error occurs while stopping the RMI server.
24    */
25   public void stop() throws FileManException;
26 
27 }

Your public implementation class of this interface must be called FileManServerImpl, and must be placed in the package at.ac.tuwien.dslab.rmi.server. It has to provide a public constructor that takes an java.util.Properies class as the only argument and throws no exceptions. In the constructor, you should build the appropriate RMI URL from the property file given as argument. You will need this URL for binding the file manager to the registry.

In the start() method, create the IFileManager instance with the necessary parameters and bind it to the registry. If you do not implement the start() method correctly, all of your tests will fail since the grading robot cannot lookup the file manager from the registry.
The stop() method should unbind the RMI server object, and unexport it from the registry. For both operations, use the RMI URL you built in the constructor (from the property file). If you do not use this RMI URL, the grading robot will not be able to lookup your file manager, and your tests will fail!

Furthermore, the FileManServerImpl class should provide a main() method for starting up the server. You have to take the property file name (i.e., file name and path) as the first command-line argument. In the method body, instantiate your server with the given property file. Then, start the server by invoking the start() method.

Property file

The sample property file DSGFileMan.properties defines the following properties:

  • RMI_HOSTNAME=pizza.dslab.tuwien.ac.at
  • RMI_PORT=1099
  • RMI_OBJECT=DSGFileMan789
  • DOCUMENT_ROOT=doc-root/
  • USER_FILE=test-accounts.txt
The first three properties are used for binding the IFileManager to the RMI registry. The property DOCUMENT_ROOT specifies the document root of IFileManager. The last property USER_FILE defines the name of the file where the user accounts are stored on the server. The keys of the property files can be found in the IPropertyKeys interface.

You can assume that these properties you get in your constructor are correct, so you do not have to check them. The USER_FILE property represents a filename which you *have* to load from the CLASSPATH. Do not load it as a normal file with FileReader or FileInputStream. For loading a file from the classpath, check out java.lang.ClassLoader.

Printing output to System.out and System.err

In this assignment, we use JUnit again to automatically test your application. You are free to print any debug messages you like to System.out or System.err, but you will not see your debug output produced during the execution of the grading robot in your grading mail (this is because your debug output would probably cause confusion and would not help as there might be dependencies between our tests).

System.exit

In this assignment, you should not use System.exit in your code. This might cause unexpected problems during the automated testing. You should use return instead.

Testing issues

Note that when testing your solution, we try to start an instance of your server and lookup the file manager from the registry. Therefore, it does not make sense to use our grading robot before your server can start up, and bind the IFileManager to the registry, because all your tests will fail.

How to compile and run your code

As in Lab3, we provide a simple Ant file to compile your clients (download here). We assume that you have the following directory structure in your $HOME/lab4 directory:

	dslab150@pizza:~/lab4$ ll
	total 24
	drwx--x--x 3 dslab150 dslab150 4096 2006-11-14 10:01 build
	-rw------- 1 dslab150 dslab150  620 2006-11-14 18:17 build.xml
	-rw------- 1 dslab150 dslab150  130 2006-11-14 14:03 DSGFileMan.properties
	drwx--x--x 2 dslab150 dslab150 4096 2006-11-14 17:25 lib
	drwx--x--x 3 dslab150 dslab150 4096 2006-11-03 17:31 src
	-rw------- 1 dslab150 dslab150  178 2006-11-14 09:54 test-accounts.txt
	
Simply store the Ant script in a file called build.xml in $HOME/lab4 and execute it by issuing the command ant compile on the bash. Delete the compiled classes by issuing the command ant clean. Your compiled Java classes will be stored in the build directory.

In contrast to Lab3, we do not provide a script for starting the server. To start your implementation, simply enter the following command:

dslab150@pizza:~/lab4$ java -cp build/:.:`echo lib/*.jar | tr ' ' ':'`
	-Djava.rmi.server.hostname=pizza.dslab.tuwien.ac.at
	-Djava.rmi.server.codebase=http://www.dslab.tuwien.ac.at/lab4/DSGFileMan-lab4.jar
	at.ac.tuwien.dslab.rmi.server.FileManServerImpl DSGFileMan.properties
	
Please note that the JVM property -Djava.rmi.server.hostname=pizza.dslab.tuwien.ac.at has to be set to the hostname where the server should be bound. Furthermore, the property -Djava.rmi.server.codebase=http://www.dslab.tuwien.ac.at/lab4/DSGFileMan-lab4.jar has to be set to the jar-file that contains the remote interface.

Hints for Solving the Lab

  1. If you are new to Java RMI programming, please check out the reading suggestions below to get familiar with RMI.
  2. The interface IRepository is only a suggestion by us. Since this interface does not appear in the IFileManager interface, you can implement the repository functionality as you prefer.
  3. In your home directory, you find the file test-accounts.txt which contains test users for the DSGFileMan system. Note that you have to put this file to your CLASSPATH so that your server is able to read the user accounts. Moreover, add your own dsgfm account to this file, if you want to test your server with your account.
  4. There are two RMI registries which you can use to bind your server. These registries are running on rmi://pizza.dslab.tuwien.ac.at:1099 and rmi://pasta.dslab.tuwien.ac.at:1099, respectively. To avoid confusion with other students, use a unique name for your server (for instance, user dslab789 should use DSGFileMan789).
    Moreover, you can also start your own registry using the command rmiregistry <port number>. To avoid confusion with other students, use a unique portname for your registry. We suggest that user dslabXXX uses port number 40+XXX (for instance, user dslab789 uses port number 40789).
  5. If our grading robot tells you that your submission does not pass all the tests, always start to correct the first test that fails because the other errors could be consecutive failures.

Deliverables

To submit your solution, you need to follow these steps:

  1. Develop your solution in Java (version 1.5), and implement the IFileManager and the IFileManServer interface as described above. Put all your files in the $HOME/lab4/src/ folder and use the provided package name (at.ac.tuwien.dslab.rmi.server). Make sure that your Java program compiles and runs in the lab environment. You can implement and use as much classes as you need. The only constraint is that your implement the required interfaces and use the package name at.ac.tuwien.dslab.rmi.server for all you implementation classes. It is not allowed to use external libraries (e.g., XML parser), they won't be submitted to the server.
  2. If you have created your files on some other machine, copy them into the lab environment using scp (secure copy).
  3. In your $HOME/lab4/src directory call submit4 to submit all your files.
  4. Read any error or success messages.
  5. It might take up to an hour for the grading robot to check your program. Don't forget to read your e-mail to check the results of the grading robot. If you have errors, correct them and try again!

Reading Suggestions

The following reading suggestions should help you in developing your solution.

  1. Lab4 JavaDoc Documentation
  2. Java RMI Tutorial (from Sun Microsystems Inc)
  3. Java RMI Tutorial (from JGuru)
  4. Notes on RMI Callbacks (from David Grimshaw)
  5. CORBA and RMI comparison (from Gopalan Suresh Raj)

Deadline

You need to submit your solution until December the 14th, 17:59.

Closing words

Have fun, without sockets ;-)


Last Modified: Die Jän 2 14:41:31 CET 2007


Distributed Systems Group, Technical University of Vienna, Argentinierstrasse 8 / 184-1, 1040 Vienna, Austria, www.infosys.tuwien.ac.at