This report shows the total 1.4 SCJD points that could have been awarded in each section, and the actual number of points you were awarded. This is provided to give you per-section feedback on your strengths. The maximum possible score is 400; the minimum to pass is 320.
General Considerations (maximum = 100): 100
Documentation (maximum = 70): 70
O-O Design (maximum = 30): 30
GUI (maximum = 40): 28
Locking (maximum = 80): 80
Data store (maximum = 40): 40
Network server (maximum = 40): 40
For the Server GUI, I have allowed the user to input the Server Port number and
the location of the database file. Buttons are provided for the user to load
settings from an existing suncertify.properties file in the current working
directory, or to load default settings that are hardcoded. Once these settings
are input, the user may select the Start Server button to start the Server, and
once this is successful, the current settings are written into the properties
file. Once the Server has been started, the user may choose to stop the Server
entirely. I chose to implement the Server using RMI, as it allowed me easily
to add the networking features that were required for the application.
All the records are read from the database file upon the Server startup, and are
kept in an ArrayList in the DataAccess class, which I chose to make a singleton
class, thereby ensuring that there is only one instance of the class and
preventing corruption of the ArrayList of the records. Each object in the
ArrayList is of type RecordData, which holds all the data from a record. The
implementation of the Server is fairly "dynamic" in that if the database schema
is to change in the database file, the Server would still function correctly
with minimal, if any, changes. Once the records have been read from the database
file, they are not read again and are maintained solely in the ArrayList. When
a record has been updated or created, I use a RandomAccessFile to write to the
database file using all the information retrieved initially from the database
file schema. This RandomAccessFile is not kept open and is closed everytime the
specific writing operation has been completed, and because the methods that call
the methods that do the writing are synchronized, corruption of the database
file is not possible. The single instance of the DataAccess class is then used
by the Data class to ensure that there is only a single instance of the records
For the locking mechanism, I chose to allow the Client to choose when to lock a
record for updating, but prevent Clients that do not have a lock from updating a
record with an additional lock check in the update() method in the ServerImpl
class. There is a LockManager class (a singleton class), which holds a HashMap
with the record number as a key and the specific Client connection as the value
in the HashMap. The LockManager keeps track of what Client owns what lock, and
the lock() method uses the LockManager class to determine when a certain Client
has to wait for a record to be unlocked. All of these locking functions are
only run in the ServerImpl class implementations of the lock() and unlock()
methods (which also call their respective methods in the Data class) and kept
out of the Data class because threading functions are not necessary in all cases
of the URLyBird application (ie: standalone mode). Also, if a Client has been
shut down (from closing the Client or a crashing situation) while it owned a
lock for a record, the lock is released automatically by the Server after a
short duration (in my case, I set the lease duration to 10 seconds) using the
Unreferenced interface and the LockManager.
One of the problems that I was faces was that the Data class was constrained by
the DBMain interface, thereby not allowing the throwing of additional
exceptions. Because many of the File I/O methods throw possible IOExceptions, I
chose to catch the IOExceptions and to throw RuntimeExceptions instead. This
allowed me to not have to change the method definitions in the DBMain interface
while also not losing the exceptions that might be thrown.
For the Client GUI, I have allowed the user to input the Server IP and Port
Number upon startup. If there is a suncertify.properties file in the current
working directory, then the values taken from that file are automatically
populated in the prompts for the Server IP and Port number. Once the Client has
been successfully connected to the Server, the values that were entered in are
then written to the suncertify.properties file, or the file is created if it did
not exist in the current working directory.
I have elected to use a simple 3 panel solution, where one (button) panel holds
all the main buttons for the user to navigate between the Search and Booking
screens, another (table) panel holds the JTable that displays all the data
currently being viewed (which can be filtered from the Search screen), and
another (main) panel to allow the user to either Search on specific criteria or
to Modify/Book a selected record. I have used CardLayout for the main panel to
allow easy switching between the Search and Booking screens using the buttons in
the button panel. I decided to use a only allow the User to search upon the
required fields (Hotel Name and City/Location). And because the requirements
state that the User should only be able to search upon records where the name
and/or location fields exactly match the values specified, I provided the User
with pre-populated JComboBoxes for the search, as well as an additional button
to view all of the records without needing to "empty" out the JComboBoxes.
As for updating/booking a record, I decided to only allow one User to access the
booking screen for a single record at one time, and providing any additional
Users who wish to access the same record a prompt to allow them to wait until
the record becomes unlocked (ie: the first User finishes booking the record or
cancels the operation), or to continue on with another record. This assures
that only one User can book a record at any one moment.
In order to display the most recent changes made by the User, or any other
Users, the JTable that displays the records is refreshed everytime any action is
taken on the Client.
One of the choices that I made was not to make the Client "dynamic", and rather
hardcoding all field names and fields into the Client code. I didn't feel there
was any benefit in taking the extra time to make the Client change based on what
is in the database schema. However, all JLabel text and messages, etc. are all
held in a single URLyBirdConstants class to allow easy access to changing things
such as field names. Another choice that I made was to exclude a menu bar for
the Client GUI even though the majority of GUIs contain a menubar. I felt since
I include all the functions that are necessary in the panel that contains the
main buttons, there was no reason to add a menubar, and the ButtonPanel behaves
as a menubar might, if the Users requested one, it would be fairly simple to
The following Design Patterns were the main Patterns used in developing the
Singleton Design Pattern -
The Singleton Design Pattern is used in the DataAccess and
LockManager classes to ensure there is only one instance of each of
these classes at a single time and to allow any class to access the
public methods of the class.
MVC Design Pattern -
The Model View Controller Design Pattern's use is inherent in using
Swing components in designing the GUI.
Adapter Design Pattern -
The Adapter Design Pattern was used when the methods provided in the
DBMain interface were not sufficient when those methods were to be
used in the RMI implementation, so a ServerImpl Adapter class was
created to allow for correct implementation.
Of course, many other Patterns were used (Proxy, Factory, etc.), but I felt tit
would be overkill to list all the Patterns used, when the main ones of interest
were noted above.
I attempted to make the code are clear as possible, with comments where I felt
were appropriate. I also provided basic javadoc information which I believe
would be helpful for any "junior programmer" to understand the classes and
methods and what they are intended to do.
Also, one of the statements that was in the instructions, but was not a "must"
was the following: "They take bookings only within 48 hours of the start of room
occupancy." While this was not an implicit "must", I chose to implement it, and
the method checkDate() determines if the current date is at most 48 hours (2
days) before the Date Available of the record. If the current date is more than
2 days before or after the Date of Occupancy, the room cannot be booked (things
such as Time Zones were not considered for this application).
For future enhancements, because the create() and delete() methods in the Data
class are fully implemented, the only thing that would need to be done is
provide the Client GUI components to access these methods. The current
BookingPanel class can also be easily modified to allow for dual use for the
creation of records as well.
Originally posted by Eric Chang:
After going through the resubmission process where I changed absolutely nothing besides making my Data class a non-singleton and putting the locking mechanism in there instead of in the RMI implementation, and adding a singleton class to hold the ArrayList of records, I foudn out today that I passed.
I think I missed the 12 points on the GUI because I didn't include a JMenuBar...oh well, at least I didn't get a 0 on locking again.
Thanks to all that wished me luck on my resubmitall as well as everyone that posted tips that I've read.
Originally posted by Eric Chang:
Sure, I had a singleton LockManager class which contained a HashMap of current locks. The key for the object in the HashMap was the record number that is locked, and the Object is the instance of the Data class (as each connection has a separate instance of the Data class). So in my lock method in my Data class, I attempt to acquire a lock by calling the createLock method in the LockManager and sending in the record number and "this" (the instance of the Data class). If the lock is acquired successfully, "this" is returned, otherwise the instance of the Data class which holds the lock is returned. Back in the lock method, if the instance returned from the createLock method is NOT equal to "this", then I wait on the instance of the Data class returned (ie: the owner of the lock). In the unlock method, I call the LockManager again to remove the lock, which fires off a notifyAll on the owner of the lock.
I also implement Unreferenced to take care of orphaned locks.