At the end of last year I finally decided to register for Oracle Certified Master. Java Developer certification, do the assignment and hopefully pass it. I was "lucky" at that time that projects at my work position got stalled, so there was nothing to do, and I had a lot of time to do my own things. I tried to not lose that time but to use it to quickly developed OCMJD project and I am satisfied with my results, since I passed the certification in relatively short time. Here is my short timeline:
- 15th of September 2010 - Registered for assignment
- 4th of November 2010 - Sat for an essay.
- 3rd of December 2010 - Submitted my assignment.
- 7th of January 2011 - Received an email telling I passed.
Preparations included going through Head First Design Patterns and Monkhouse's book for SCJD. I have also had a LOT of help from people in JavaRanch forum, one of my favorite forums with most helpful people.
My project was URLyBird 1.1.1 and I would like to talk about how I implemented it. It was a no-brainer to me that overall architecture will be "thin client", since I am used to developing web-based applications, thus I prefer to follow that model.
I decided to split functions of Data class into two parts, one being data access part, and another being record locking part. Data access part dealt with functions of creating, deleting, updating, reading and searching records, and locking layer deals with locking and unlocking records. Both of these parts have interfaces and implementing classes, in order to be able to switch implementations. There classes are not expected to be synchronized, and Data class has its own record locking mechanism (with Map of ReadWriteLocks, and master ReadWriteLock to read/write to this map). Note that this is a separate locking mechanism and has nothing to do with locking done in implementation of data access class.
This class is composed of an interface that defines methods necessary for data access, and an implementation class. An implementation class implements the data access interface with file-based database, and has its own locking mechanism to read/write/update/delete records. Since I used RandomAccessFile to read and write to the file, I needed seek point to be stable, thus whenever I needed to perform file operations, I synchronized over the RandomAccessFile instance that I used. File-based implementation of data access also had a map that would map record numbers to record positions in the file. That would allow us to reuse file positions, as it was required for me, without having to reuse record numbers (which can be dangerous if versions are not handled.). To map file positions to position in actual file, I used a static function, since length of every records was the same, and header size was known. I also had a separate Set for deleted positions, and when a request to create a new record was received, the code was first looking whether there is a deleted record that can be reused. During application start-up, the Map of record numbers to positions and Set of deleted records were filled in. Access to the Map of record numbers to positions and the Set of deleted records was synchronized with ReadWriteLock. Find method was special that it was expected to throw UnsupportedOperationException if underlying data store did not support easy search of data. File data store was such a case, thus its find method was throwing UnsupportedOperationException, which was handled by Data class, and Data class was using its simple search algorithm. Find method was left in such way in order to enable SQL-based data stores to be effectively used to search for records.
Record locking sub-system consisted of an interface that defined methods for locking, unlocking and checking for locks, and the implementation class, which used a simple HashMap to store cookies for locked records. Access to this map was synchronized with ReentrantLock and Condition, in order to enable a given thread to wait for another record to be unlocked. For generating cookies I used java.util.Random class, and gave it System.currentTimeMillis() as a seed.
This layer had methods that client layer would call, and also high-level business objects, such as Record with all the named fields, instead of array of Strings. I also made factory classes in order to convert from Record object to String array and record number. To achieve that I first made an abstract record factory class and used generics in order to denote the type to which I will be converting my Record class. My concrete implementation of this class in business layer was converting to and from Pair of String and integer. Pair was a custom class I wrote for this project that would contain two elements, left and right, similar to Pair from C++ STL. My main business class was called URLyBird, and contained methods for booking, searching and retrieving all records.
RMI, easier and cleaner. I also have never used it before, so I was keen to learn the new technology. I had a class to start and stop the server, which was called by server GUI. This class had an instance of URLyBird class to which the calls from client side were propagated. The method to start the server programmaticaly started RMI registry and binded my service to it. Stop method however only unbinded the service from RMI registry, but did not shut it down. I could not find any simple and straightforward way to programmatically shut down the RMI registry.
I decided NOT to implement MVC model, since I believed that it would be an over-complication for my project. GUI in my case made only two requests to back-end, and there wasn't much of a logic for controller part, so I decided to merge controller and view. Other than that I tried to keep my GUI as simple as possible, required JTable (with custom TableModel), fields for searching, buttons for searching and booking, no icons, and only tested it in Windows 7 OS. To connect to back-end part of my application I had connector classes which would extend my Connector interface. This interface had all the methods that GUI needed from back-end, such as bookRecord and listAllRecords, and it was the bridge between back-and-front ends. I also had dialogs for connection in the beginning, which would return instances of usable Connectors upon completion of user input (details about connection). Having a separate connection dialog would allow me in future enable re-connections to another servers or files easier, without having to restart client.
This layer also had an implementation of abstract record factory I mentioned in business layer section, with only String array. The first element of array denoted the record number in String representation.
4 questions, but it was the only Sun exam so far that I only finished 2 minutes before the end of exam. Questions themselves were not really hard, however I took my time to correct my wording, and I did get stuck in one questions which asked me about my table model. I did not know what to write, since writing "I did it this way because it was done so in Monkhouse book" would definitely not give me a credit. I wrote general things which by my intuition, and I guess it seemed to work out. At the end of an exam I got 0% as report, which is completely normal with the way Prometric software is designed, so there is nothing to worry about it. You are filling "comments" section of each question, and none of the questions have answers, thus you have 0 points from each of them. The person in Prometric center will probably know nothing about it, but you can ask for verification if you get a strange look from the person about you getting 0% :)
My package documentation went to package-info.java. Even thought it's only supported in Java 6, its preferred way over having an html file. The find method in Data class was using prefix-based search, and in business layer it is searching based on exact match. I have generated RMI stubs since my project explicitly stated that "You must provide all classes pre-installed so that no dynamic class downloading occurs". And it would for sure not hurt, so why to take a risk of not generating them. I have changed the indentation and formatting of DB.java interface to match Java Coding Convention, and I documented it in my choices.txt. I do not handle misuse of Data class, i.e. deadlock could occur if for example two threads A and B try to obtain locks and update records 1 and 2 in reverse order. I documented it in my choices file that handling such a care would over-complicate the design, and may not be reliable.