<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-7740886660615459010</id><updated>2011-07-08T06:25:06.503-07:00</updated><category term='Music Database Program'/><title type='text'>John's Programs</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://johnsprograms.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://johnsprograms.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>John</name><uri>http://www.blogger.com/profile/12302295115917633435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://bp3.blogger.com/_a57asMH1jkM/SAqQtFYnwDI/AAAAAAAAAGA/UXyYKtDsGnM/S220/myPicture.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>17</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-7740886660615459010.post-2406818644016032430</id><published>2010-02-06T21:44:00.000-08:00</published><updated>2010-02-06T21:52:46.948-08:00</updated><title type='text'>The Importance of Bug Lists</title><content type='html'>&lt;span style="font-weight:bold;"&gt;Status of the Music DB program&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The first phase of my Music Database Project is done. The program I have allows me to slip a CD into my computer and will determine a disc ID and track lengths from which it can go out and get the name of the album, principal artist(s), and track titles.&lt;br /&gt;&lt;br /&gt;This data is presented to the user along with empty boxes into which he can supply the names of the performers for each track, composers, lyricists, various URLs (such as the URL to a video of the performer performing the music on the track), and notes. &lt;br /&gt;&lt;br /&gt;When the user has completed the form, there are checks to try to make sure that the data is as clean as possible, namely, that there are no "typos" that might result in duplicate entries for the same artist or the same piece of music.&lt;br /&gt;&lt;br /&gt;Once the user accepts the data, it is placed in an object-oriented database from which it will be retrieved in the second phase of the program.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Lessons learned&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;There have been many lessons learned but in this blog entry, I want to champion the use of bug lists. &lt;br /&gt;&lt;br /&gt;As with any software project, once you get to the point where you have software that is working (although not complete), you find that there are many things that are still incorrect. Develop a "bug list". I use a simple text editor in which each bug I find is placed as another new line. For example:&lt;br /&gt;&lt;br /&gt;&lt;font color="brown"&gt;&lt;br /&gt;• Artist ambiguities not complete.&lt;br /&gt;&gt;&gt;Fixed.&lt;br /&gt;&lt;/font&gt;&lt;br /&gt;&lt;br /&gt;This particular bug was easily fixed and I just noted that it had been fixed.&lt;br /&gt;&lt;br /&gt;Other bugs are difficult. Here's one that proved to be tough to resolve:&lt;br /&gt;&lt;br /&gt;&lt;font color="brown"&gt;&lt;br /&gt;• The online search for track titles fails and produces no output for the user for the album: "Dr. T" with Billy Taylor.&lt;br /&gt;&lt;/font&gt;&lt;br /&gt;&lt;br /&gt;This particular bug involved the online search in the freedb web site for the particular album. In my program this involves a CGI Perl script that uses telnet to communicate with the freedb site. There are a number of pieces to the software that could contain the bug. &lt;br /&gt;&lt;br /&gt;Instead of just reporting that I had fixed the bug when that happened (eventually), it's really important to document what you did along the way. Someday there will be a similar bug but by then you will have forgotten how you resolved this one. So I keep good records of what things I do and their results. Here is a piece of that log (which I insert into the bug list as I go as additional lines after the original bug description):&lt;br /&gt;&lt;br /&gt;&lt;font color="brown"&gt;&lt;br /&gt;   (1) go back to the command line access to the online database to see what's going on.&lt;br /&gt;   At the terminal window, entered the command (after inserting the CD):&lt;br /&gt;cd-discid /dev/rdisk2&lt;br /&gt; &lt;br /&gt;   Here's the reply:&lt;br /&gt;730d480a 10 150 27962 75475 102750 120057 148635 168450 190332 202657 236085 3402&lt;br /&gt;   &lt;br /&gt;   Now I typed:&lt;br /&gt;telnet freedb.freedb.org 8880&lt;br /&gt;&lt;br /&gt;   Here's the reply:&lt;br /&gt;Trying 195.214.216.38...&lt;br /&gt;Connected to freedb.freedb.org.&lt;br /&gt;&lt;/font&gt;&lt;br /&gt;&lt;br /&gt;This also serves as a record of those things you try and the results. This documentation can be used in other bug fix situations where cutting and pasting may save you a lot of time.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Prioritize the bugs&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Not all bugs are equally important. Some can be ignored for a long time simply because they don't affect the program significantly. Others have the quality that if they are not fixed, the program simply will not work properly. &lt;br /&gt;&lt;br /&gt;I keep two sections in my bug list file: the bugs still not resolved (at the top) and the solved bugs (below the unsolved ones).&lt;br /&gt;&lt;br /&gt;I prioritize bugs in the bug list by placing the most important bug at the top. As I resolve one bug, I move the entry down into the solved bug area of the file. In the course of solving one bug, I may find that other bugs become more critical since they may impact the bug I'm working on.&lt;br /&gt;&lt;br /&gt;In the end, the bug list provides an important tool in the software development process.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7740886660615459010-2406818644016032430?l=johnsprograms.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://johnsprograms.blogspot.com/feeds/2406818644016032430/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7740886660615459010&amp;postID=2406818644016032430' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/2406818644016032430'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/2406818644016032430'/><link rel='alternate' type='text/html' href='http://johnsprograms.blogspot.com/2010/02/importance-of-bug-lists.html' title='The Importance of Bug Lists'/><author><name>John</name><uri>http://www.blogger.com/profile/12302295115917633435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://bp3.blogger.com/_a57asMH1jkM/SAqQtFYnwDI/AAAAAAAAAGA/UXyYKtDsGnM/S220/myPicture.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7740886660615459010.post-6532314948003054542</id><published>2010-01-20T14:28:00.000-08:00</published><updated>2010-01-20T14:40:15.363-08:00</updated><title type='text'>Data Validation</title><content type='html'>&lt;span style="font-weight:bold;"&gt;The purpose of validation&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The idea behind validation is to minimize the number of errors in the database. The blog entry on September 3rd lays out the plan for data validation. Implementing those plans required some changes. &lt;br /&gt;&lt;br /&gt;Suppose the user enters the name of a principal artist that is not currently in the database. Is this really a new artist or is there a typo or perhaps the user is using a different version of the name. For example suppose the artist is Johann Sebastian Bach but the user enters J S Bach. We would like to avoid having two artists in the database that should really just be one.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;What to do if we suspect that there is ambiguity about an entry&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;For each new artist name, we check the name against that of the existing artists in the database. Suppose we find that there is not an exact match. We then look for names that have a sufficient similarity that they could be the artist. Once we have this list of names we want to indicate that there is the possibility of a duplication and let the user decide which one to keep.&lt;br /&gt;&lt;br /&gt;I started by focusing on the mechanism for keeping the multiple names. Remember that the user is in the process of entering the data for an album and is presented with a screen such as the following. (Click on the image to see a larger version).&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_a57asMH1jkM/S1eEHA-9HKI/AAAAAAAAALs/EeYJqX2_4MY/s1600-h/DataEntryScreen.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 211px;" src="http://3.bp.blogspot.com/_a57asMH1jkM/S1eEHA-9HKI/AAAAAAAAALs/EeYJqX2_4MY/s400/DataEntryScreen.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5428953131876424866" /&gt;&lt;/a&gt;&lt;br /&gt;In the same space that contains an input field, we want to display the choices available to fill that field. The obvious choice is a pull-down menu containing the list of artists. But how would the user be quickly alerted to the fields where there were potential ambiguities? The choice here was to change the background color for that field (see the image below) &lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_a57asMH1jkM/S1eETevWmFI/AAAAAAAAAL0/BkldFCfVVkA/s1600-h/ambiguityDisplay.jpg"&gt;&lt;img style="float:center; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 400px; height: 32px;" src="http://1.bp.blogspot.com/_a57asMH1jkM/S1eETevWmFI/AAAAAAAAAL0/BkldFCfVVkA/s400/ambiguityDisplay.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5428953346022479954" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;and to have an alert message displayed when the page was first opened &lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_a57asMH1jkM/S1eEpdSuCkI/AAAAAAAAAL8/L9BTX4-WZJM/s1600-h/validationAlert.jpg"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 400px; height: 140px;" src="http://1.bp.blogspot.com/_a57asMH1jkM/S1eEpdSuCkI/AAAAAAAAAL8/L9BTX4-WZJM/s400/validationAlert.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5428953723591068226" /&gt;&lt;/a&gt;.&lt;br /&gt;Internally, how do we represent the ambiguity. An Artist object, for example, has only one name field yet we want to somehow convey the multiple artists that are in contention for a particular data field. One possibility was to create an abstract (Artist) class, for example, and then have one subclass for the "real" Artist class and another that carried the ambiguity in the name. This approach proved to be unwieldy and, in the end, I chose to have parallel classes to the existing data classes. These parallel classes offered the advantage that the data fields could be maintained as String objects which is convenient for working with the form data on the JSPs.&lt;br /&gt;&lt;br /&gt;At the top there is a PrelimAlbum class with data fields:&lt;br /&gt;&lt;font color="brown"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    private String title;&lt;br /&gt;    private String recordingLabel;&lt;br /&gt;    private String yearRecorded;&lt;br /&gt;    private String coverArtFile;&lt;br /&gt;    private String playTime;&lt;br /&gt;    private String discID;&lt;br /&gt;    private String physicalLocation;&lt;br /&gt;    private String notes;&lt;br /&gt;    private List&lt;PrelimArtist&gt; principalArtistList;&lt;br /&gt;    private List&lt;PrelimTrack&gt; trackList;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/font&gt;    &lt;br /&gt;Notice that all the data fields are Strings and that the principal artist field and the list of tracks are represented by lists of new classes PrelimArtist and PrelimTrack.&lt;br /&gt;&lt;br /&gt;The new PrelimArtist class has data fields:&lt;br /&gt;&lt;font color="brown"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    private List&lt;String&gt; names;&lt;br /&gt;    private String birthDate;&lt;br /&gt;    private String wikipediaArticle;&lt;br /&gt;    private List&lt;String&gt; URLs;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/font&gt;    &lt;br /&gt;Now we can accommodate any ambiguity in the list of names. If there is more than one name in the list, then there is a potential ambiguity. &lt;br /&gt;&lt;br /&gt;Likewise, the PrelimTrack class parallels the Track Class and has fields:&lt;br /&gt;&lt;font color="brown"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    private List&lt;String&gt; URLs;&lt;br /&gt;    private String audioFileName;&lt;br /&gt;    private String playTime;&lt;br /&gt;    private PrelimPieceOfMusic piece;&lt;br /&gt;    private List&lt;PrelimPerformer&gt; performerList;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/font&gt;    &lt;br /&gt;There is no ambiguity carried directly in these fields but indirectly the possibility of a track from another album having the same piece of music led to the creation of the PrelimPieceOfMusic class which parallels a corresponding (new) class PieceOfMusic.&lt;br /&gt;&lt;br /&gt;The Performer class has its parallel class PrelimPerformer with fields:&lt;br /&gt;&lt;font color="brown"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    private PrelimArtist artist;&lt;br /&gt;    private String instrument;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/font&gt;    &lt;br /&gt;Notice that any ambiguities are carried in the PrelimArtist class field. &lt;br /&gt;&lt;br /&gt;The PrelimPieceOfMusic class carries any potential ambiguities in a similar manner to the PrelimArtist class. Here are its fields:&lt;br /&gt;&lt;font color="brown"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    private List&lt;String&gt; titleList; &lt;br /&gt;    private List&lt;PrelimComposer&gt; composerList;&lt;br /&gt;    private List&lt;PrelimLyricist&gt; lyricistList;&lt;br /&gt;    private String leadSheet; &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/font&gt;    &lt;br /&gt;Ambiguities for composers and lyricists are carried in the PrelimComposer and PrelimLyricist classes. Note that the lists in the PrelimPieceOfMusic class represent the fact that a piece of music may have several composers or lyricists regardless of ambiguities. It's the PrelimCompser and PrelimLyricist classes that have a list of names. [The leadSheet field represents a new field that I added to the database to allow saving lead sheets (sometimes called "fake sheets") with the chord changes for many pieces of jazz music.]&lt;br /&gt;&lt;br /&gt;When we check the database for existing entries as we validate the information, we have a small problem. Suppose we find an exact match for an artist in the database. We place that artist's name in the JSP shown to the user with the validated data and then later when the user commits to placing the data in the database, we need to know which object was associated with that artist. Remember that all we have in the JSP is the name of the artist. We need to know, when we process the data for entry into the database, that this is associated with an existing object. &lt;br /&gt;&lt;br /&gt;The mechanism I chose was to assign a random number to each object and to make it a part of that object's instance variables. Now, when we pick a name from an existing Artist object to display in the validation page, we append the id of the object to the name (as in "Stan Getz[46265]"). In the post-processing of the validated data, I use that id to locate the appropriate Artist object. This is facilitated with the use of a hashtable for each of the object classes that can incorporate amibiguities (Artist, Composer, Lyricist, PieceOfMusic).&lt;br /&gt;&lt;br /&gt;There is a second small problem. What if, in the course of processing the data for an album, we locate a new artist not in the database and for which there are no "nearby" names that could signal an ambiguity? We create a new Artist object but it isn't in the database yet. Later in the processing of the data we encounter another artist not yet in the database. We must not only check the database but we must check any new Artist objects that have been created in previous processing but are not yet in the database. This is facilitated by another hashtable that uses the artist's name as the key to locate the corresponding Artist object.&lt;br /&gt;&lt;br /&gt;The same problems occur for composers, lyricists, and pieces of music. These are treated in the same manner.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Lessons Learned&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;1. Trying to prevent duplicate objects in the database is not trivial. I had to develop a method for detecting such duplicates and I had to develop a way to handle such potential ambiguities in a web environment. This involved algorithm development for locating potential duplicates, data structure utilization for marking and quickly accessing objects that are potential duplicates of objects associated with names in a form.&lt;br /&gt;&lt;br /&gt;2. Storing the information for an object (e.g. Artist) in the presence of ambiguities required developing parallel classes that allowed me to encapsulate the ambiguities in a list of things (e.g. names). Having parallel classes also allowed me to store the information in String form whenever possible which then made working with forms easier in other parts of the program.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7740886660615459010-6532314948003054542?l=johnsprograms.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://johnsprograms.blogspot.com/feeds/6532314948003054542/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7740886660615459010&amp;postID=6532314948003054542' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/6532314948003054542'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/6532314948003054542'/><link rel='alternate' type='text/html' href='http://johnsprograms.blogspot.com/2010/01/data-validation.html' title='Data Validation'/><author><name>John</name><uri>http://www.blogger.com/profile/12302295115917633435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://bp3.blogger.com/_a57asMH1jkM/SAqQtFYnwDI/AAAAAAAAAGA/UXyYKtDsGnM/S220/myPicture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_a57asMH1jkM/S1eEHA-9HKI/AAAAAAAAALs/EeYJqX2_4MY/s72-c/DataEntryScreen.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7740886660615459010.post-3717240435818307662</id><published>2010-01-13T07:18:00.000-08:00</published><updated>2010-01-13T07:31:18.039-08:00</updated><title type='text'>Progress and a review of purpose</title><content type='html'>&lt;span style="font-weight:bold;"&gt;Why the Blog&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The reason I started writing this blog was to document this project I was working on. Over time I've been re-thinking that purpose. My teaching roots push me in the direction of wanting to make this more than just a documentation. What if I were to record lessons learned and to focus on the larger picture rather than the nitty gritty details of one or more Java classes? So, this is the direction I hope to move. &lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Progress to-date&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Despite the fact that the last post was almost 4 months ago, I have been working on the project. I've reached a point where I can insert a CD into my computer and have the program fetch information about the CD such as its title, the principal artist, number of tracks, track titles, and track playtimes. The program will check to see if an artist or composer or lyricist is already in the database so that a duplicate entry is avoided. There have been changes to accommodate more fields as seemed appropriate and there are many more classes that resulted from these changes.&lt;br /&gt;&lt;br /&gt;Some of the changes that I made were the result of entering data into early versions of the program. These exposed some problems that I could correct at an early stage rather than trying to make modifications after the program was completely written.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Changes &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The Album class was modified so that instead of just one principal artist, I could have a list of them. Sometimes an album has several artists that are equally prominent. The second change was to add a notes field. Sometimes, there are some interesting facts that the database user should know but that don't fall into the category of another field. For example, in the liner notes for one album, I found out that there were two piano players on the tracks but that the particular one on a particular track was not listed. This is something I would like to know if I'm looking at a particular track on this album.&lt;br /&gt;&lt;br /&gt;The Track class was modified more extensively. I was thinking of how a user may want to look for other occurrences of a particular piece of music and I realized that the structure that I had would probably require that I search through all tracks on all albums. The problem, I decided, was that I needed to separate a piece of music from a track. In particular, a piece of music has a title, a composer and perhaps a lyricist. This is separate from a track in which a particular piece of music is played by an artist using some instrument. The result was the introduction of a new class: PieceOfMusic with a title, a list of composers (after all there may be more than one), and a list of lyricists. There are a couple of other fields I'll talk about later.&lt;br /&gt;&lt;br /&gt;This simplified the Track class so that it now looks like:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;   Track&lt;br /&gt;       piece           PieceOfMusic&lt;br /&gt;       URLs            List&lt;String&gt;&lt;br /&gt;       audiFileName    File&lt;br /&gt;       performerList   List&lt;Performer&gt;&lt;br /&gt;       playTime        PlayTime&lt;br /&gt;&lt;/pre&gt; &lt;br /&gt;The other classes (Artist, Performer, Instrument, PlayTime) remain essentially the same.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Things learned&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The modifications that I have made were the result of several things that represent things learned. The first thing is to maintain flexibility as long as possible. Once we commit to the fields in the database and begin placing data in the database, we soon have a very strong incentive to avoid making changes. Changes, however are often necessary and as long as things are fluid, changes are relatively easy to make.&lt;br /&gt;&lt;br /&gt;The second thing that I learned is that we should simulate the data entry process. This may be done in a "dry run" way or it may involve actual execution of preliminary versions of the program. Many of the changes I made were made as a result of entering data and realizing that there were cases that didn't fit the design. For example I found that some albums had more than one principal artist. Another example was that I found (as mentioned above) that sometimes one wants to place a note with the album to account for some issue that a data field itself can't resolve.&lt;br /&gt;&lt;br /&gt;The third thing I learned was that its a lot easier to debug a program that's mostly working rather than a program that isn't yet working. So I started by getting a program that just displayed the entries I was making but didn't yet place them in the database. Then I added more fields as necessary or I modified the layout on the screen to accomodate what I had so far. Most IDEs let you step through the execution of the program when you are debugging. For a web-based application like this part of the project, I have not found a way to do this. That may be partly due to the fact that I have some Perl (the CGI component at the beginning) as well as a Servlet and JSPs. Fortunately, the old-fashioned insertion of print statments in strategic locations results in output showing up in the Glassfish output which is nicely integrated with the NetBeans IDE.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7740886660615459010-3717240435818307662?l=johnsprograms.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://johnsprograms.blogspot.com/feeds/3717240435818307662/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7740886660615459010&amp;postID=3717240435818307662' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/3717240435818307662'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/3717240435818307662'/><link rel='alternate' type='text/html' href='http://johnsprograms.blogspot.com/2010/01/progress-and-review-of-purpose.html' title='Progress and a review of purpose'/><author><name>John</name><uri>http://www.blogger.com/profile/12302295115917633435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://bp3.blogger.com/_a57asMH1jkM/SAqQtFYnwDI/AAAAAAAAAGA/UXyYKtDsGnM/S220/myPicture.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7740886660615459010.post-6338637312089212582</id><published>2009-09-18T07:18:00.000-07:00</published><updated>2009-09-18T07:26:55.723-07:00</updated><title type='text'>One more thing</title><content type='html'>As the user enters data in the CD entry mode, he adds more rows of track data as necessary. This is done by clicking on a button at the bottom of the block of data corresponding to the track. What happens as soon as the user clicks on the button is that a new page is presented with the new row of blank input boxes but the window was positioned at the top and the user had to scroll down to the track for which he was entering data. A friend pointed out that it would be much nicer if the new page would be positioned on the track for which the new row had been created. This was a lot harder than it seems.&lt;br /&gt;&lt;br /&gt;The first thought is to use the HTML &amp;lt;a&amp;gt; tag with the name attribute. You attach a name (a bookmark, really) to a location on the page with the &amp;lt;a&amp;gt; tag (example: &amp;lt;a name="track3"&amp;gt;) and then reference that name in the &amp;lt;a&amp;gt; tag in another document (example: &amp;lt;a href="dataUpdate.html#track3"&amp;gt;). Unfortunately, these are not static HTML documents but JSPs which are assembled on the fly to produce the HTML pages delivered to the browser. In other words, there is no &amp;lt;a href="..."&amp;gt; tag to work with that will allow you to use the bookmark mechanism.&lt;br /&gt;&lt;br /&gt;I spent some time following links on the web to see if I could find an answer to this puzzle. The answer is to use Javascript. In Javascript one can issue commands to tell the browser to scroll the page to a particular point (e.g. y pixels from the top of the page). To do this you have to determine the number of pixels from the top of the page to the location you want. Here's what you do. Use the fact that you can attach an id to an HTML element. In particular, the table row element that starts each track section is given an id:&lt;br /&gt;&lt;font color="brown"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;tr id="row_&amp;lt;% out.print(j); %&amp;gt;"&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/font&gt;&lt;br /&gt;The index j is a loop index that cycles through the tracks. The result is that in the generated HTML document you get (for j = 5 here):&lt;br /&gt;&lt;font color="brown"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;tr id="row_5"&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/font&gt;&lt;br /&gt;This id associated with this tag can now be used with a Javascript function to find the offset from the top of the window so that we can scroll the window in such a way that the displayed portion starts at the row corresponding to the track of interest.&lt;br /&gt;&lt;br /&gt;Here is the appropriate Javascript code:&lt;br /&gt;&lt;font color="brown"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;script type="text/javascript"&amp;gt;&lt;br /&gt;    function scroller(obj)&lt;br /&gt;    {&lt;br /&gt;        var curleft = curtop = 0;&lt;br /&gt;        curleft += obj.offsetLeft;&lt;br /&gt;        curtop += obj.offsetTop;&lt;br /&gt;        obj = obj.offsetParent;&lt;br /&gt;        curleft += obj.offsetLeft;&lt;br /&gt;        curtop += obj.offsetTop;&lt;br /&gt;        window.scrollTo(curleft, curtop);&lt;br /&gt;    }&lt;br /&gt;&amp;lt;/script&amp;gt;&lt;/pre&gt;&lt;/font&gt;&lt;br /&gt;This is placed in the &amp;lt;head&amp;gt; portion of the HTML document. Then we modify the &amp;lt;body&amp;gt; tag using the following code:&lt;br /&gt;&lt;font color="brown"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;%! int numberOfTracks; %&amp;gt;&lt;br /&gt;&amp;lt;% numberOfTracks = Integer.parseInt(request.getParameter("numberOfTracks")); &lt;br /&gt;    String addFields = null, trackId = "row_1";&lt;br /&gt;    for (int t = 0; t &amp;lt; numberOfTracks; t++)&lt;br /&gt;       {&lt;br /&gt;           addFields = request.getParameter("addInputDataFields"+t);&lt;br /&gt;           if (addFields != null)&lt;br /&gt;           {&lt;br /&gt;               trackId="'row_"+t+"'";&lt;br /&gt;               break;&lt;br /&gt;           }&lt;br /&gt;       } %&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;body onload="scroller(document.getElementById(&amp;lt;% out.print(trackId); %&amp;gt;))"&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/font&gt;&lt;br /&gt;Remember, the Java portions of this code are executed at the time the JSP engine is creating the HTML document. The user has just clicked on one of the "More input fields" buttons. The first thing we need to do is to find which of those buttons was clicked. When we emerge from the for loop, the trackId string will be (in the case of track 5 as an example again) 'row_5'. (The single quotes are necessary because in the generated HTML, this string is embedded in an outer double quoted string).&lt;br /&gt;&lt;br /&gt;The "onload" piece of the &amp;lt;body&amp;gt; tag refers to a Javascript "event". In this case, the event is the loading of the page by the browser. We are telling the browser to execute the Javascript function scroller when this event occurs. A good place to start to learn about this stuff is the &lt;a href="http://www.w3schools.com/"&gt;w3schools website&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;Briefly, the scroller function is a modification of a more comprehensive version that is available from &lt;a href="http://www.quirksmode.org/js/findpos.html"&gt;QuirksMode.org&lt;/a&gt;. For some reason, I was never able to get the more comprehensive version to work for my application. In the function we use the offsetLeft and offsetRight properties associated with HTML elements. Because the table row is embedded in a table and the offset properties only give you the offset relative to the parent, we have to walk up the chain of nestings to get the total offset. When we have the total offset (both from the left and the top of the window), we call the scrollTo function to position the page right where we want it.&lt;br /&gt;&lt;br /&gt;Now, we are ready to go on to the validation of the CD data.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7740886660615459010-6338637312089212582?l=johnsprograms.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://johnsprograms.blogspot.com/feeds/6338637312089212582/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7740886660615459010&amp;postID=6338637312089212582' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/6338637312089212582'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/6338637312089212582'/><link rel='alternate' type='text/html' href='http://johnsprograms.blogspot.com/2009/09/one-more-thing.html' title='One more thing'/><author><name>John</name><uri>http://www.blogger.com/profile/12302295115917633435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://bp3.blogger.com/_a57asMH1jkM/SAqQtFYnwDI/AAAAAAAAAGA/UXyYKtDsGnM/S220/myPicture.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7740886660615459010.post-3037938011699093219</id><published>2009-09-03T20:39:00.000-07:00</published><updated>2009-09-03T20:43:04.977-07:00</updated><title type='text'>Plan for Data Validation Phase</title><content type='html'>Once the user is satisfied with the data for an album, the next step is to check the data against what is currently in the database. &lt;br /&gt;&lt;br /&gt;First of all, we can check if the album has previously been entered. This is an easy check by comparing the disc ID. The next question is what to check and how to check it. Here's what we are going to check:&lt;br /&gt;   • the name of the principal artist or artists;&lt;br /&gt;   • the title of each track;&lt;br /&gt;   • the name of each artist on each track.&lt;br /&gt;   &lt;br /&gt;The way we will check is to use string comparison with n-grams. There's a &lt;a href="http://www.python.org/~jeremy/pubs/thesis/"&gt;paper describing this algorithm&lt;/a&gt; by Jeremy A. Hylton in the context of bibliographic data. &lt;br /&gt;&lt;br /&gt;Each item that we're checking in the album data will be compared to all the current entries in the database. For example, each principal artist will be compared to all the artists currently in the database and a list of very similar names will be generated (if such a list exists). Similarly, a list of very similar track title names will be collected by comparing each track title against those in the database.&lt;br /&gt;&lt;br /&gt;What happens if we find very similar names of titles? The user is presented with the data that was entered in the same layout. For those fields for which there are very similar (but different) entries already in the database, instead of the actual entry the user will see a SELECT HTML element with the users entry displayed but with the alternative entries showing in the drop-down selection. The user can choose to leave things as they are or to change the entry to one of those available.&lt;br /&gt;&lt;br /&gt;The mechanics will be as follows. We will make the Album class, the Artist class, and the Track class into JavaBeans. This will allow us to use JSP technology. JavaBeans are described in the &lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/JavaBean"&gt;wikipedia article&lt;/a&gt;. When we collect the data from the user we will create an appropriate Album object and fill in the appropriate fields (including creating any necessary new objects). In particular here is the code to build the Album object:&lt;br /&gt;&lt;font color="brown"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;private Album makeAlbum(HttpServletRequest request)&lt;br /&gt;{&lt;br /&gt;    Album newAlbum = new Album(request.getParameter("albumTitle"));&lt;br /&gt;&lt;br /&gt;    //--Pick up the number of principal artist entry fields (which&lt;br /&gt;    //--may be more than the number of principal artists).&lt;br /&gt;    int numberOfPrincipalArtistFields =&lt;br /&gt;            Integer.parseInt(request.getParameter("numberOfPrincipalArtistFields"));&lt;br /&gt;&lt;br /&gt;    //--Determine the principal artists for this album and add them&lt;br /&gt;    //--to the list.&lt;br /&gt;    for (int i = 1; i &lt;= numberOfPrincipalArtistFields; i++)&lt;br /&gt;    {&lt;br /&gt;        String principalArtist = request.getParameter("principalArtist_"+i);&lt;br /&gt;        //--Add the artist if the field is not null or blank.&lt;br /&gt;        if (!(principalArtist == null || principalArtist.equals("")))&lt;br /&gt;        {&lt;br /&gt;            newAlbum.addPrincipalArtist(new Artist(principalArtist));&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    //--Fill in the other non-track fields.&lt;br /&gt;    String recordingLabel = request.getParameter("recordingLabel");&lt;br /&gt;    newAlbum.setRecordingLabel(recordingLabel);&lt;br /&gt;    String yearRecorded = request.getParameter("year");&lt;br /&gt;    newAlbum.setYearRecorded(yearRecorded);&lt;br /&gt;    String coverArtFileName = request.getParameter("coverArtFileName");&lt;br /&gt;    File coverArtFile = new File(MusicDBPreferences.coverArtFolder+coverArtFileName);&lt;br /&gt;    newAlbum.setCoverArtFile(coverArtFile);&lt;br /&gt;    String totalPlayTime = request.getParameter("totalPlayTime");&lt;br /&gt;    PlayTime playTime = PlayTime.parsePlayTime(totalPlayTime);&lt;br /&gt;    newAlbum.setPlayTime(playTime);&lt;br /&gt;    String discID = request.getParameter("discID");&lt;br /&gt;    newAlbum.setDiscID(discID);&lt;br /&gt;    String location = request.getParameter("location");&lt;br /&gt;    newAlbum.setPhysicalLocation(location);&lt;br /&gt;&lt;br /&gt;    //--Now take care of the tracks.&lt;br /&gt;    int numberOfTracks = Integer.parseInt(request.getParameter("numberOfTracks"));&lt;br /&gt;    System.out.println("makeAlbum-- numberOfTracks: " + numberOfTracks);&lt;br /&gt;    newAlbum.setNumberOfTracks(numberOfTracks);&lt;br /&gt;    for (int j = 1; j &lt;= numberOfTracks; j++)&lt;br /&gt;    {&lt;br /&gt;        Track track = makeTrack(request, j);&lt;br /&gt;        newAlbum.addTrack(track);&lt;br /&gt;    }&lt;br /&gt;    return newAlbum;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;    private Track makeTrack(HttpServletRequest request, int j) &lt;br /&gt;    {&lt;br /&gt;        String title = request.getParameter("trackTitle"+j);&lt;br /&gt;        Track newTrack = new Track(title);&lt;br /&gt;        &lt;br /&gt;        int numberOfInputDataFields = &lt;br /&gt;                Integer.parseInt(request.getParameter("numberOfInputDataFields"+j));&lt;br /&gt;&lt;br /&gt;        //--Pick up the performers.&lt;br /&gt;        for (int i = 1; i &lt;= numberOfInputDataFields; i++)&lt;br /&gt;        {&lt;br /&gt;            String performerName = request.getParameter("performer"+j+"_"+i);&lt;br /&gt;            String instrument = request.getParameter("instrument"+j+"_"+i);&lt;br /&gt;            if (!(performerName == null || performerName.equals("")))&lt;br /&gt;            {&lt;br /&gt;                Performer performer = new Performer(new Artist(performerName),&lt;br /&gt;                        Instrument.getInstance(instrument));&lt;br /&gt;                newTrack.addPerformer(performer);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        //--Pick up the links associated with this track.&lt;br /&gt;        for (int i = 1; i &lt;= numberOfInputDataFields; i++)&lt;br /&gt;        {&lt;br /&gt;            String linkName = request.getParameter("links"+j+"_"+i);&lt;br /&gt;            if (!(linkName == null || linkName.equals("")))&lt;br /&gt;            {&lt;br /&gt;                newTrack.addURI(linkName);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        //--Pick up the composers associated with this track.&lt;br /&gt;        for (int i = 1; i &lt;= numberOfInputDataFields; i++)&lt;br /&gt;        {&lt;br /&gt;            String composer = request.getParameter("composer"+j+"_"+i);&lt;br /&gt;            if (!(composer == null || composer.equals("")))&lt;br /&gt;            {&lt;br /&gt;                newTrack.addComposer(composer);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        //--Pick up the lyricists associated with this track.&lt;br /&gt;        for (int i = 1; i &lt;= numberOfInputDataFields; i++)&lt;br /&gt;        {&lt;br /&gt;            String lyricist = request.getParameter("lyricist"+j+"_"+i);&lt;br /&gt;            if (!(lyricist == null || lyricist.equals("")))&lt;br /&gt;            {&lt;br /&gt;                newTrack.addLyricist(lyricist);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;       //--Pick up the play time for this track.&lt;br /&gt;        String playTimeString = request.getParameter("playtime"+j);&lt;br /&gt;        PlayTime playTime = PlayTime.parsePlayTime(playTimeString);&lt;br /&gt;        newTrack.setPlayTime(playTime);&lt;br /&gt;        return newTrack;&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;/font&gt;&lt;br /&gt;This code is found in the servlet that gets called by each of the genereated web pages (either the Perl scrip, or the update data entry jsp, or the validation jsp). The servlet is the traffic cop, directing traffic to the appropriate JSP.&lt;br /&gt;&lt;br /&gt;The first JSP (updateDataEntryForm.jsp) allows the user to fill in the data missing after the Perl script fills in what it can. This JSP will allow the user to add more fields as necessary by clicking on an add button.&lt;br /&gt;&lt;br /&gt;The second JSP (dataValidation.jsp) takes care of the case when the user is satisfied that all the information for the CD album has been entered. This JSP is the one that will communicate with the database (through the JavaBeans) to identify possible mis-spellings and errors that we mentioned above. For the appropriate fields, the JSP will call on the appropriate JavaBean to check the database. If there are near-misses, then they are collected into an array of Strings. These will be placed in a SELECT HTML data construct that will display the value supplied by the user with "nearby" values listed as pull-down items.&lt;br /&gt;&lt;br /&gt;We will describe this in some detail on the next blog entry.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7740886660615459010-3037938011699093219?l=johnsprograms.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://johnsprograms.blogspot.com/feeds/3037938011699093219/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7740886660615459010&amp;postID=3037938011699093219' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/3037938011699093219'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/3037938011699093219'/><link rel='alternate' type='text/html' href='http://johnsprograms.blogspot.com/2009/09/plan-for-data-validation-phase.html' title='Plan for Data Validation Phase'/><author><name>John</name><uri>http://www.blogger.com/profile/12302295115917633435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://bp3.blogger.com/_a57asMH1jkM/SAqQtFYnwDI/AAAAAAAAAGA/UXyYKtDsGnM/S220/myPicture.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7740886660615459010.post-8137561078292070185</id><published>2009-07-10T20:11:00.000-07:00</published><updated>2009-07-10T20:20:59.559-07:00</updated><title type='text'>Data Classes</title><content type='html'>I have put off the writing of the data classes for as long as I could. This was not a matter of procrastenation. As I've been working on the data entry and trying things out on CDs that I currently own, I find that I need to consider new fields or revise the ones that I had decided on earlier. Once the data classes have been established and I begin to enter data, it will be much more difficult to change things. For example, how will I convert the old data already entered into the new data with additions and modifications in the structure? &lt;br /&gt;&lt;br /&gt;So here is the latest version of the data classes:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Classes fields            type&lt;br /&gt;   Album&lt;br /&gt;        title             String&lt;br /&gt;        principalArtist   Artist&lt;br /&gt;        recordingLabel    String&lt;br /&gt;        yearRecorded      int&lt;br /&gt;        coverArtFile      File&lt;br /&gt;        playTime          PlayTime&lt;br /&gt;        discID            String&lt;br /&gt;        physicalLocation  String&lt;br /&gt;        trackList         List&lt;Track&gt;&lt;br /&gt;   Track&lt;br /&gt;        title             String&lt;br /&gt;        performerList     List&lt;Performer&gt;&lt;br /&gt;        URLs              List&lt;URL&gt;&lt;br /&gt;        composerList      List&lt;String&gt;&lt;br /&gt;        lyricistList      List&lt;String&gt;&lt;br /&gt;        audioFileName     File&lt;br /&gt;        playTime          PlayTime&lt;br /&gt;   Artist&lt;br /&gt;        name              String&lt;br /&gt;        birthDate         Calendar&lt;br /&gt;        wikipediaArticle  URL&lt;br /&gt;        URLs              List&lt;URL&gt;&lt;br /&gt;   Performer&lt;br /&gt;        artist            Artist&lt;br /&gt;        instrument        Instrument&lt;br /&gt;   Instrument&lt;br /&gt;        name              String&lt;br /&gt;   PlayTime&lt;br /&gt;        min               int&lt;br /&gt;        sec               int&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;A few remarks: The PlayTime class allows me to get at the minutes and seconds component without having to parse a String object to get those elements any time I want them. I overrode the &lt;tt&gt;toSting&lt;/tt&gt; method to provide the String version of the PlayTime object. Notice that I have a number of fields that contain objects of the URL class. This will allow my program to let the user view certain videos (on youtube, for example) or articles (such as wikipedia). These articles or videos will enhance the database.&lt;br /&gt;&lt;br /&gt;There are other classes, of course, that will manage the logic flow of the program. There is the data to save to the database, there is validation of the data to try to catch typos or minor variations on the title of a tune. For now this will give me a blueprint with which to work.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7740886660615459010-8137561078292070185?l=johnsprograms.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://johnsprograms.blogspot.com/feeds/8137561078292070185/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7740886660615459010&amp;postID=8137561078292070185' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/8137561078292070185'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/8137561078292070185'/><link rel='alternate' type='text/html' href='http://johnsprograms.blogspot.com/2009/07/data-classes.html' title='Data Classes'/><author><name>John</name><uri>http://www.blogger.com/profile/12302295115917633435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://bp3.blogger.com/_a57asMH1jkM/SAqQtFYnwDI/AAAAAAAAAGA/UXyYKtDsGnM/S220/myPicture.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7740886660615459010.post-805723145462789910</id><published>2009-07-05T08:58:00.000-07:00</published><updated>2009-07-05T09:10:20.468-07:00</updated><title type='text'>From Tomcat and Eclipse to Glassfish and NetBeans and the Overall Plan</title><content type='html'>This is a process of programmer development as well as program development. After spending a significant amount of time trying to integrate Tomcat 5.5 and Eclipse 3.4 (see &lt;a href="http://www.vogella.de/articles/EclipseWTP/article.html"&gt;this article&lt;/a&gt;), I gave up. The problem probably was related to the fact that I wanted to have an independent Tomcat installation running outside of the Eclipse "box" and not just withing the IDE environment. I was resigned to letting Textmate manage the code components from diverse locations in the file system and then manually starting and stopping Tomcat during debugging. This is not an unacceptable way to go except that an IDE can save so much effort during the development of a project like this.&lt;br /&gt;&lt;br /&gt;Then I decided to revisit some of the technologies such as JavaServer Faces (JSF). As I worked through some of the tutorials (such as &lt;a href="http://www.exadel.com/tutorial/jsf/jsftutorial-kickstart.html"&gt;kickstart&lt;/a&gt;). Again I had problems getting Tomcat to work (this time with JSF). Finally I came to the thought that the Java technologies come from Sun Microsystems and they use NetBeans for an IDE and Glassfish for the application server (that Tomcat provides). So why am I trying to get Tomcat and Eclipse working together for this project? &lt;br /&gt;&lt;br /&gt;I started by downloading GlassFish, JavaEE, Tools (incl. Netbeans) from &lt;a href="http://java.sun.com/javaee/downloads"&gt;downloads (Mac OS X and PPC version)&lt;/a&gt;. I customized the install to leave out Portlet stuff and worked through the tutorial: &lt;a href="http://www.netbeans.org/kb/docs/java/quickstart.html"&gt;quickstart&lt;/a&gt;. Not all is exactly as it says but it seemed much easier to fix minor inconsistencies than it had with the Eclipse IDE. NetBeans handles the launching of Glassfish easily. One issue is that NetBeans is quite a memory hog. I decided to invest in a memory upgrade from 512MB to 1GB ($60) and things loosened up considerably.&lt;br /&gt;&lt;br /&gt;I worked through another tutorial (&lt;a href="http://www.netbeans.org/kb/61/web/jastrologer-intro.html"&gt;jAstrologer intro&lt;/a&gt;) to learn about the special tags that are the characteristic of JSF. Since the graphical elements of the user interface will have to be more extensive than just the form field stuff, I decided to work through a tutorial on Applets and NetBeans (&lt;a href="http://www.netbeans.org/kb/61/web/applets.html"&gt;web and applets&lt;/a&gt;). Now I'm seeing the utility of NetBeans, especially in the tools that let you build a form page graphically and then go back and supply the code that provides the appropriate response to events. &lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_a57asMH1jkM/SlDPWkgnvLI/AAAAAAAAAKM/KtUgH0bmiXk/s1600-h/MusicDBOverview.jpg"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 360px; height: 400px;" src="http://4.bp.blogspot.com/_a57asMH1jkM/SlDPWkgnvLI/AAAAAAAAAKM/KtUgH0bmiXk/s400/MusicDBOverview.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5355007943608089778" /&gt;&lt;/a&gt;I'm sold. Here's the current plan––the access of the online CD database (freeDB) and the ability to manually add information to the album data is done (the boxes in blue in the diagram). Validating the data involves placing the data into the database and trying to make sure there are very few errors. In particular, I want to check existing track titles against the current track titles to discover and eliminate inconsequential variants. The same applies to artists, instruments, etc.&lt;br /&gt;&lt;br /&gt;Another part of the program involves collecting and placing cover art into the database. This may involve just incorporating images that the user has scanned or actually searching the web for cover art images.&lt;br /&gt;&lt;br /&gt;Still another part of the program will convert from aiff format files (existing CDs) to MP3 format that allows storing (and playing) these files in compressed format. And another part is an editor that will allow the user to modify an existing record or to add information to the database.&lt;br /&gt;&lt;br /&gt;Rounding out the program will be the browser itself. This browser is where the user can ask to see music with particular characteristics involving any of the data fields. This part of the program will involve a more substantial use of graphics in with some animation involving the cover art images.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7740886660615459010-805723145462789910?l=johnsprograms.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://johnsprograms.blogspot.com/feeds/805723145462789910/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7740886660615459010&amp;postID=805723145462789910' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/805723145462789910'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/805723145462789910'/><link rel='alternate' type='text/html' href='http://johnsprograms.blogspot.com/2009/07/from-tomcat-and-eclipse-to-glassfish.html' title='From Tomcat and Eclipse to Glassfish and NetBeans and the Overall Plan'/><author><name>John</name><uri>http://www.blogger.com/profile/12302295115917633435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://bp3.blogger.com/_a57asMH1jkM/SAqQtFYnwDI/AAAAAAAAAGA/UXyYKtDsGnM/S220/myPicture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_a57asMH1jkM/SlDPWkgnvLI/AAAAAAAAAKM/KtUgH0bmiXk/s72-c/MusicDBOverview.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7740886660615459010.post-5383027105596129824</id><published>2009-06-15T10:02:00.000-07:00</published><updated>2009-06-15T10:08:53.280-07:00</updated><title type='text'>Music Database Program: Tomcat</title><content type='html'>&lt;b&gt;&lt;i&gt;From Apache to Tomcat&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;Last time, I showed the initial part of the program to extract the data from a CD and present it to the user so that additional data can be added for a particular CD and eventually placed in the database. In the previous blog entry, I described how to use the Apache web server that comes as part of Mac OS X to provide the underlying structure for our application to obtain title and track information for an audio CD.&lt;br /&gt;&lt;br /&gt;This information will become the start of the information that we collect for each CD in building up our Music Database. The information that we collect from the FreeDB web site (freedb.org) contains CD album titles, track titles and some artist information. That's only a portion of the data I want to have for each CD in our collection. Much of that information will have to be entered in by the owner of the database. The next phase of the project addresses this issue.&lt;br /&gt;&lt;br /&gt;JavaServer Pages (JSP) builds on top of Java Servlet technology to provide for full Java applications on the server side of the client-server internet connection. Apache-Tomcat (usually just known as tomcat) is the server software that manages the connection and provides the bridge to the Java software that supplies the content that the user sees. Once I have the FreeDB data I can work completely in the Java language instead of having to revert to Perl to provide the Telnet service that I needed. This is the path I will take.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;i&gt;Installing Tomcat&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;First of all, you have to install Tomcat on your machine. Here are the steps I took.&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Downloaded apache-tomcat-5.5.27 from the &lt;br /&gt;&lt;a href="http://tomcat.apache.org/"&gt;tomcat-apache&lt;/a&gt; web site.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Placed the unzipped folder into /usr/local&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Created a symbolic link (from the terminal application)&lt;br&gt;&lt;br /&gt;&lt;tt&gt;ln -s /usr/local/apache-tomcat-5.5.27 /Library/Tomcat&lt;/tt&gt;&lt;br&gt;&lt;br /&gt;(this sets the path to the tomcat home directory to be known as /Library/Tomcat)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;There is a very helpful web site with &lt;a href="http://mamamusings.net/archives/2004/04/28/installing_tomcat_5_on_os_x.php"&gt;&lt;br /&gt;contnued directions&lt;/a&gt;. Following the directions in that web site I then issued (in the terminal application):&lt;br&gt;&lt;br /&gt;&lt;tt&gt;cd /Library/Tomcat/bin&lt;/tt&gt;&lt;br&gt;&lt;br /&gt;and issued the following (to set the appropriate files to be executable):&lt;br&gt;&lt;br /&gt;&lt;tt&gt;chmod 755 *.sh&lt;/tt&gt;&lt;br&gt;&lt;br /&gt;Next I placed the following in my &lt;tt&gt;.tcshrc&lt;/tt&gt; file (which is located in your home folder):&lt;br&gt;&lt;br /&gt;&lt;tt&gt;# Tomcat stuff&lt;/tt&gt;&lt;br&gt;&lt;br /&gt;&lt;tt&gt;setenv TOMCAT_HOME /Library/Tomcat&lt;/tt&gt;&lt;br&gt;&lt;br /&gt;and issued (this will make the information you just place into the &lt;tt&gt;.tcshrc&lt;/tt&gt; file immediately available in yout terminal window. Since each time you start up a terminal window this file is read, this information will be available to your terminal sessions from now on):&lt;br&gt;&lt;br /&gt;&lt;tt&gt;source .tcshrc&lt;/tt&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Finally, I started up Tomcat by issuing (again from the terminal window):&lt;br&gt;&lt;br /&gt;&lt;tt&gt;/Library/Tomcat/bin/startup.sh&lt;/tt&gt;&lt;br&gt;&lt;br /&gt;Here was the response:&lt;pre&gt;&lt;br /&gt;Using CATALINA_BASE:   /Library/Tomcat&lt;br /&gt;Using CATALINA_HOME:   /Library/Tomcat&lt;br /&gt;Using CATALINA_TMPDIR: /Library/Tomcat/temp&lt;br /&gt;Using JRE_HOME:       /Library/Java/Home&lt;/pre&gt;&lt;br /&gt;In the browser, I entered the url:&lt;br&gt;&lt;br /&gt;&lt;tt&gt;http://localhost:8080&lt;/tt&gt;&lt;br&gt;&lt;br /&gt;and I got the Tomcat home page. This meant that I now had the tomcat software installed and that it was listennng on port 8080 for any incoming requests.&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;As an aid in the development process, I added the following three lines to&lt;br /&gt;the &lt;tt&gt;.tcshrc&lt;/tt&gt; file in my home folder:&lt;pre&gt;&lt;br /&gt;alias startTomcat '${TOMCAT_HOME}/bin/startup.sh'&lt;br /&gt;alias stopTomcat '${TOMCAT_HOME}/bin/shutdown.sh'&lt;br /&gt;alias restartTomcat 'startTomcat; stopTomcat'&lt;/pre&gt;&lt;br /&gt;Now, when I want to start, stop, or restart tomcat, I can just enter the commands&lt;pre&gt;&lt;br /&gt;startTomcat&lt;br /&gt;stopTomcat&lt;br /&gt;restartTomcat&lt;/pre&gt;&lt;br /&gt;in the terminal window which I keep open during development.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;i&gt;JSP, Servlets, and the MusicDB program&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;The plan is to pick up from the flow diagram of April 10. We need to let the user expand the number of copies of certain fields as necessary. In particular, there may be more than one principal artist for an album or more than the three possible artist/instrument pairs boxes that I supply in the first view that the user sees (see the May 4th entry). Next, I must validate the data to try to remove typos, discrepancies, and other errors. All this is done with Java.&lt;br /&gt;&lt;br /&gt;I used the Model-View-Controller pattern in which a controller servlet gets the incoming data and decides which view to send back the user based on the (data entry) model I have put together. The servlet is just Java code that ties in with the tomcat software. The views are supplied by Java Server Pages (JSPs) that consist of a mixture of Java statements and HTML statements. I think that it would be taking this too far afield to try to explain the elements of JSP and servlets. The book I found most useful is &lt;i&gt;Core Servlets &amp;amp; JavaServer Pages&lt;/i&gt; by Hall and Brown.&lt;br /&gt;&lt;br /&gt;In this blog entry I'm just looking at the box in the diagram here labeled "Process request for more copies of certain fields". As I built the JSP and the servlet, I uncovered some bugs in the original Perl script (the part that generated the HTML). I've fixed these bugs which generally had to do with neglecting to provide some hidden fields that really were necessary for processing the data (such as the number of artist/instrument fields for each track). The data is first sent to a servlet. If the servlet determines that a request for more fields is present (more principal artist fields or more track fields) then the JSP that is invoked is the one that adds more field entry boxes but otherwise just keeps the fields that have so far been entered.&lt;br /&gt;&lt;br /&gt;The mixture of HTML and Java code in the JSP makes for some difficult debugging. Here's an example of a piece of the JSP that places previous prinicipal artist fields into the proper position and keeps the field entry values that have already been filled. &lt;br /&gt;&lt;font color="brown"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;%! String moreArtists;&lt;br /&gt;    int i;&lt;br /&gt;       String bgColor = ""; %&amp;gt;&lt;br /&gt;&amp;lt;%-- Include all previous principal Artist fields (if more than one) --%&amp;gt;&lt;br /&gt;&amp;lt;% i = 2;&lt;br /&gt;   while (i &amp;lt;= numberOfPrincipalArtistFields)&lt;br /&gt;   { &lt;br /&gt;      if ((i-1)%3 == 0)&lt;br /&gt;         bgColor = "blue"; &lt;br /&gt;      if ((i-1)%3 == 1)&lt;br /&gt;         bgColor = "orange"; &lt;br /&gt;      if ((i-1)%3 == 2)&lt;br /&gt;         bgColor = "red";&lt;br /&gt;    %&amp;gt;&lt;br /&gt;      &amp;lt;tr&amp;gt;&lt;br /&gt;         &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;         &amp;lt;td bgcolor="&amp;lt;% out.print(bgColor); %&amp;gt;"&amp;gt;&lt;br /&gt;            &amp;lt;input name="principalArtist_&amp;lt;% out.print(i); %&amp;gt;" &lt;br /&gt;               value="&amp;lt;% out.print(request.getParameter("principalArtist_"+i));%&amp;gt;"  &lt;br /&gt;                  size="30" type="text"&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;         &amp;lt;td colspan="5"&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;      &amp;lt;/tr&amp;gt;&lt;br /&gt;&amp;lt;%   i++;&lt;br /&gt;   } %&amp;gt;&lt;br /&gt;&amp;lt;%-- i is now numberOfPrincipalArtistFields + 1 --%&amp;gt;&lt;br /&gt;&amp;lt;% moreArtists = request.getParameter("addPrincipalArtist");&lt;br /&gt;   if (moreArtists != null)  //--We are adding a new principal artist field.&lt;br /&gt;   {  &lt;br /&gt;      numberOfPrincipalArtistFields++;&lt;br /&gt;&lt;br /&gt;      if ((i-1)%3 == 0)&lt;br /&gt;         bgColor = "blue"; &lt;br /&gt;      if ((i-1)%3 == 1)&lt;br /&gt;         bgColor = "orange"; &lt;br /&gt;      if ((i-1)%3 == 2)&lt;br /&gt;         bgColor = "red";&lt;br /&gt; %&amp;gt;&lt;br /&gt;      &amp;lt;tr&amp;gt;&lt;br /&gt;         &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;         &amp;lt;td bgcolor="&amp;lt;% out.print(bgColor); %&amp;gt;"&amp;gt;&lt;br /&gt;            &amp;lt;input name="principalArtist_&amp;lt;% out.print(i); %&amp;gt;" &lt;br /&gt;               value="" size="30" type="text"&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;         &amp;lt;td colspan="5"&amp;gt;&amp;lt;/td&amp;gt;&lt;br /&gt;      &amp;lt;/tr&amp;gt;&lt;br /&gt;&amp;lt;% } %&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/font&gt;&lt;br /&gt;&lt;br /&gt;In the next entry I'll talk about replacing Apace with Tomcat altogether for this project.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7740886660615459010-5383027105596129824?l=johnsprograms.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://johnsprograms.blogspot.com/feeds/5383027105596129824/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7740886660615459010&amp;postID=5383027105596129824' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/5383027105596129824'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/5383027105596129824'/><link rel='alternate' type='text/html' href='http://johnsprograms.blogspot.com/2009/06/music-database-program-tomcat.html' title='Music Database Program: Tomcat'/><author><name>John</name><uri>http://www.blogger.com/profile/12302295115917633435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://bp3.blogger.com/_a57asMH1jkM/SAqQtFYnwDI/AAAAAAAAAGA/UXyYKtDsGnM/S220/myPicture.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7740886660615459010.post-5108659080905834465</id><published>2009-05-04T06:58:00.000-07:00</published><updated>2009-05-04T08:25:29.281-07:00</updated><title type='text'>Music Database Program: Using Apache</title><content type='html'>The program we have developed so far is a web applicaton. It uses telnet to communicate with the freeDB server. Instead of building a GUI front end to the perl script, we can use readily available tools, in particular the browser, to provide the front end. Mac OS X comes with the web server software known as Apache. This open source software is designed to deliver web pages and documents to clients who have requested them. It is also capable of communicating with other applications (such as our perl script). Our initial data entry program will utilize this technology.&lt;br /&gt;&lt;br /&gt;You have to make sure that the Apache web server is running. To check, select &lt;b&gt;System Preferences...&lt;/b&gt; from the Apple menu then click on the &lt;b&gt;Sharing&lt;/b&gt; icon. Select the &lt;b&gt;Services&lt;/b&gt; tab and make sure the &lt;b&gt;Personal Web Sharing&lt;/b&gt; box is checked. If not, then click the checkbox followed by the &lt;b&gt;Start&lt;/b&gt; button to the right. This should also insure that Apache will launch at startup whenever you reboot.&lt;br /&gt;&lt;br /&gt;Apache has a folder in which it first looks for documents that you request. Since we are doing everything locally, the url for the document we want is something like&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;http://localhost/MusicDB/CDdataEntry.html&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;On the Mac, the location of the file &lt;tt&gt;CDdataEntry.html&lt;/tt&gt; is located in a folder named &lt;tt&gt;MusicDB&lt;/tt&gt;. That folder, in turn, is located in the base folder for web documents that Apache can deliver. That folder is &lt;tt&gt;/Library/WebServer/Documents&lt;/tt&gt;. The base folder we can leave alone, the folder &lt;tt&gt;MusicDB&lt;/tt&gt; and any other folders we want to create are up to us. Generally, I like to keep each project in its own folder, perhaps creating other folders inside these project folders as I see fit.&lt;br /&gt;&lt;br /&gt;Here is the picture of the simple web page that serves as the front end of the data entry program (click on the picture to see it in full size). &lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_a57asMH1jkM/Sf71JRGavPI/AAAAAAAAAI4/FodbACdJkFQ/s1600-h/dataEntryView.png"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 400px; height: 241px;" src="http://4.bp.blogspot.com/_a57asMH1jkM/Sf71JRGavPI/AAAAAAAAAI4/FodbACdJkFQ/s400/dataEntryView.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5331968548411194610" /&gt;&lt;/a&gt;&lt;br /&gt;As the page shows, the user is asked to insert a CD and to click on the button. Clicking on the button has to cause the Apache web server software to launch the perl script we have constructed. This mechanism uses a protocol called CGI (for common gateway interface). Basically, it dessribes a mechanism that lets the application know how to get the data it needs from Apache and it tells Apache how to obtain the data from the cgi-application. &lt;br /&gt;&lt;br /&gt;Here is the text version of the HTML document:&lt;br /&gt;&lt;font color="#cc0000"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"&lt;br /&gt;   "http://www.w3.org/TR/html4/strict.dtd"&amp;gt;&lt;br /&gt;&amp;lt;html lang="en"&amp;gt;&lt;br /&gt;&amp;lt;head&amp;gt;&lt;br /&gt;   &amp;lt;meta http-equiv="content-type" content="text/html; charset=utf-8"&amp;gt;&lt;br /&gt;   &amp;lt;title&amp;gt;Music CD DB Data Entry&amp;lt;/title&amp;gt;&lt;br /&gt;   &amp;lt;meta name="generator" content="BBEdit 7.1.3"&amp;gt;&lt;br /&gt;   &amp;lt;style type="text/css"&amp;gt;&lt;br /&gt;      body&lt;br /&gt;      { background: url("Images/dataFetchBackground.JPG") }&lt;br /&gt;      table&lt;br /&gt;      { border: 0}&lt;br /&gt;      p.right&lt;br /&gt;      {&lt;br /&gt;         position:fixed;&lt;br /&gt;         left:280px;&lt;br /&gt;         top:60px;&lt;br /&gt;      }&lt;br /&gt;   &amp;lt;/style&amp;gt;&lt;br /&gt;&amp;lt;/head&amp;gt;&lt;br /&gt;&amp;lt;body&amp;gt;&lt;br /&gt;&amp;lt;h2&amp;gt;Music DB Data Entry&amp;lt;/h2&amp;gt;&lt;br /&gt;&amp;lt;form action="http://localhost/cgi-bin/musicDataFetch.pl" method="post"&amp;gt;&lt;br /&gt;&amp;lt;p&amp;gt;&lt;br /&gt;&amp;lt;b&amp;gt;Insert a CD into your drive and click the "Get Data" button.&amp;lt;/b&amp;gt;&lt;br /&gt;&amp;lt;/p&amp;gt;&lt;br /&gt;&amp;lt;p class="right"&amp;gt;&lt;br /&gt;&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;&amp;lt;input type="submit" value="Get Data"&amp;gt;&lt;br /&gt;&amp;lt;/p&amp;gt;&lt;br /&gt;&amp;lt;/form&amp;gt;&lt;br /&gt;&amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/font&gt;&lt;br /&gt;You can see that we use the form tag and that the &lt;tt&gt;action&lt;/tt&gt; attribute names a URL.&lt;br /&gt;&lt;br /&gt;In our case, clicking on the button sends information to the perl script which we've named &lt;tt&gt;musicDataFetch.pl&lt;/tt&gt;. That script must be placed in a specific folder just as in the case of web documents. Here that folder is &lt;tt&gt;/Library/WebServer/CGI-Executables&lt;/tt&gt;. Several points to make:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;The perl script file must have execute permissions. In the terminal applicaton you can do this by executing the commands&lt;br&gt;&lt;br /&gt;&lt;tt&gt;cd /Library/WebServer/CGI-Executables&lt;/tt&gt;&lt;br /&gt;followed by&lt;br&gt;&lt;br /&gt;&lt;tt&gt;chmod 755 musicDataFetch.pl&lt;/tt&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The telnet module used by the perl script: &lt;tt&gt;Telnet.pm&lt;/tt&gt; must be placed in a folder named &lt;tt&gt;Net&lt;/tt&gt; which is placed, in turn, in a folder named &lt;tt&gt;lib&lt;/tt&gt; placed in the &lt;tt&gt;CGI-Executables&lt;/tt&gt; folder.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;In an earlier blog I showed the major portion of the perl script. Here, now is the reamining &lt;tt&gt;generateHTML&lt;/tt&gt; subroutine that generates the returning HTML document that Apace will deliver. The CGI protocol dictates that the output of the CGI application must include two leading lines in the output followed by whatever else is being delivered. In our case, because we are returning an HTML document, the first line must be&lt;br /&gt;&lt;tt&gt;Content-type: text/html&lt;/tt&gt;&lt;br /&gt;The second line must be blank.&lt;br /&gt;&lt;br /&gt;Here is the &lt;tt&gt;generateHTML&lt;/tt&gt; subroutine:&lt;br /&gt;&lt;font color="#cc0000"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;sub generateHTML&lt;br /&gt;{&lt;br /&gt;##--Front end stuff&lt;br /&gt;print("Content-type: text/html\n");&lt;br /&gt;print("\n");&lt;br /&gt;print("&amp;lt;!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n");&lt;br /&gt;print("   \"http://www.w3.org/TR/html4/loose.dtd\"&amp;gt;\n");&lt;br /&gt;print("&amp;lt;html lang=\"en\"&amp;gt;\n");&lt;br /&gt;print("&amp;lt;head&amp;gt;\n");&lt;br /&gt;print("   &amp;lt;meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"&amp;gt;\n");&lt;br /&gt;print("   &amp;lt;title&amp;gt;Music DB Data Entry&amp;lt;/title&amp;gt;\n");&lt;br /&gt;print("   &amp;lt;meta name=\"generator\" content=\"BBEdit 7.1.3\"&amp;gt;\n");&lt;br /&gt;print("   &amp;lt;style type=\"text/css\"&amp;gt;\n");&lt;br /&gt;print("      body\n");&lt;br /&gt;print("      {   background: url(\"/MusicDB/Images/dataFetchBackground.JPG\") }\n");&lt;br /&gt;print("      table\n");&lt;br /&gt;print("      { border: 0}\n");&lt;br /&gt;print("   &amp;lt;/style&amp;gt;\n");&lt;br /&gt;print("&amp;lt;/head&amp;gt;\n");&lt;br /&gt;print("&amp;lt;body&amp;gt;\n");&lt;br /&gt;print("&amp;lt;h2&amp;gt;Music DB Data Entry&amp;lt;/h2&amp;gt;\n");&lt;br /&gt;print("&amp;lt;form action=\"cgi-bin/dataEntryScript.pl\" method=\"post\"&amp;gt;\n");&lt;br /&gt;&lt;br /&gt;##--First table with the CD-specific elements&lt;br /&gt;print("&amp;lt;table&amp;gt;\n");&lt;br /&gt;print("   &amp;lt;tr&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;th&amp;gt;Album Title&amp;lt;/th&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;th&amp;gt;Principal Artist&amp;lt;/th&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;th&amp;gt;[+]&amp;lt;/th&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;th&amp;gt;Recording Label&amp;lt;/th&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;th&amp;gt;Year&amp;lt;/th&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;th&amp;gt;Cover Art file name&amp;lt;/th&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;th&amp;gt;Play time&amp;lt;/th&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;th&amp;gt;disc ID&amp;lt;/th&amp;gt;\n");&lt;br /&gt;print("   &amp;lt;/tr&amp;gt;\n");&lt;br /&gt;print("   &amp;lt;tr&amp;gt;\n");&lt;br /&gt;if ($codeMessage ne "OK") &lt;br /&gt;{   &lt;br /&gt;   $cdTitleField = $codeMessage; &lt;br /&gt;   $principalArtistField = "";&lt;br /&gt;   $coverArtFileName = ""; &lt;br /&gt;}&lt;br /&gt;print("      &amp;lt;td bgcolor=\"blue\"&amp;gt;&amp;lt;input type=\"text\" name=\"albumTitle\" \n");&lt;br /&gt;print("         value=\"",$cdTitleField,"\" size=\"35\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td bgcolor=\"blue\"&amp;gt;&amp;lt;input type=\"text\" name=\"principalArtist\" \n");&lt;br /&gt;print("         value=\"",$principalArtistField,"\" size=\"30\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td&amp;gt;&amp;lt;input type=\"checkbox\" name=\"addPrincipalArtist\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td bgcolor=\"blue\"&amp;gt;&amp;lt;input type=\"text\" name=\"recordingLabel\" \n");&lt;br /&gt;print("         value=\"\" size=\"15\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td bgcolor=\"blue\"&amp;gt;&amp;lt;input type=\"text\" name=\"year\" \n");&lt;br /&gt;print("         value=\"\" size=\"6\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td bgcolor=\"blue\"&amp;gt;&amp;lt;input type=\"text\" name=\"coverArtFileName\" \n");&lt;br /&gt;print("         value=\"",$coverArtFileName,"\" size=\"30\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td bgcolor=\"blue\"&amp;gt;&amp;lt;input type=\"text\" name=\"TotalPlayTime\" \n");&lt;br /&gt;print("         value=\"",$playingTime,"\" size=\"10\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td bgcolor=\"blue\"&amp;gt;&amp;lt;input type=\"text\" name=\"discID\" \n");&lt;br /&gt;print("         value=\"",$discid,"\" size=\"11\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("   &amp;lt;/tr&amp;gt;\n");&lt;br /&gt;print("   &amp;lt;tr&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td align=\"center\"&amp;gt;&amp;lt;b&amp;gt;Physical location&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td colspan=\"7\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("   &amp;lt;/tr&amp;gt;\n");&lt;br /&gt;print("   &amp;lt;tr&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td bgcolor=\"blue\"&amp;gt;&amp;lt;input type=\"text\" name=\"location\" size=\"35\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td colspan=\"7\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("   &amp;lt;/tr&amp;gt;\n");&lt;br /&gt;print("&amp;lt;table&amp;gt;\n");&lt;br /&gt;print("&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;\n");&lt;br /&gt;&lt;br /&gt;##--Now the second table with the track elements&lt;br /&gt;print("&amp;lt;table&amp;gt;\n");&lt;br /&gt;print("   &amp;lt;tr&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;th&amp;gt;Track&amp;lt;/th&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;th&amp;gt;Track title&amp;lt;/th&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;th&amp;gt;Artist/Instrument&amp;lt;/th&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;th&amp;gt;[+]&amp;lt;/th&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;th&amp;gt;Comment&amp;lt;/th&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;th&amp;gt;Composer&amp;lt;/th&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;th&amp;gt;Lyricist&amp;lt;/th&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;th&amp;gt;Play time&amp;lt;/th&amp;gt;\n");&lt;br /&gt;print("   &amp;lt;/tr&amp;gt;\n");&lt;br /&gt;for ($index = 1; $index &amp;lt;= $noTracks; $index++)&lt;br /&gt;{&lt;br /&gt;   if ($codeMessage ne "OK")&lt;br /&gt;   {&lt;br /&gt;      $trackTitle[$index] = "";&lt;br /&gt;   }&lt;br /&gt;print("   &amp;lt;tr&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td align=\"center\"&amp;gt;&amp;lt;b&amp;gt;",$index,"&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td bgcolor=\"blue\"&amp;gt;&amp;lt;input type=\"text\" name=\"trackTitle",$index,"\" size=\"40\"\n");&lt;br /&gt;print("         value=\"",$trackTitle[$index],"\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td bgcolor=\"blue\"&amp;gt;&amp;lt;input type=\"text\" name=\"artistInstrument",$index,"a\" size=\"30\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td bgcolor=\"blue\"&amp;gt;&amp;lt;input type=\"text\" name=\"comment",$index,"\" size=\"20\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td bgcolor=\"blue\"&amp;gt;&amp;lt;input type=\"text\" name=\"composer",$index,"\" size=\"20\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td bgcolor=\"blue\"&amp;gt;&amp;lt;input type=\"text\" name=\"lyricist",$index,"\" size=\"20\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td bgcolor=\"blue\"&amp;gt;&amp;lt;input type=\"text\" name=\"playtime",$index,"\" size=\"8\"\n");&lt;br /&gt;print("         value=\"",$trackTime[$index],"\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("   &amp;lt;/tr&amp;gt;\n");&lt;br /&gt;print("   &amp;lt;tr&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td colspan=\"2\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td bgcolor=\"orange\"&amp;gt;&amp;lt;input type=\"text\" name=\"artistInstrument",$index,"b\" size=\"30\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td colspan=\"5\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("   &amp;lt;/tr&amp;gt;\n");&lt;br /&gt;print("   &amp;lt;tr&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td colspan=\"2\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td bgcolor=\"red\"&amp;gt;&amp;lt;input type=\"text\" name=\"artistInstrument",$index,"c\" size=\"30\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td&amp;gt;&amp;lt;input type=\"checkbox\" name=\"addArtistInstrument",$index,"\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("      &amp;lt;td colspan=\"4\"&amp;gt;&amp;lt;/td&amp;gt;\n");&lt;br /&gt;print("   &amp;lt;/tr&amp;gt;\n");&lt;br /&gt;}&lt;br /&gt;print("&amp;lt;/table&amp;gt;\n");&lt;br /&gt;print("&amp;lt;/form&amp;gt;\n");&lt;br /&gt;print("&amp;lt;/body&amp;gt;\n");&lt;br /&gt;print("&amp;lt;/html&amp;gt;\n");&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/font&gt;&lt;br /&gt;As an example of the resulting web document we have the following image showing a portion of the document delivered by Apache (click the image for full size view).&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_a57asMH1jkM/Sf71eP3rfcI/AAAAAAAAAJA/GZ1HbOZ-U3A/s1600-h/sampleApacheOutput.png"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 400px; height: 216px;" src="http://4.bp.blogspot.com/_a57asMH1jkM/Sf71eP3rfcI/AAAAAAAAAJA/GZ1HbOZ-U3A/s400/sampleApacheOutput.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5331968908858195394" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7740886660615459010-5108659080905834465?l=johnsprograms.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://johnsprograms.blogspot.com/feeds/5108659080905834465/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7740886660615459010&amp;postID=5108659080905834465' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/5108659080905834465'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/5108659080905834465'/><link rel='alternate' type='text/html' href='http://johnsprograms.blogspot.com/2009/05/music-database-program-using-apache-and.html' title='Music Database Program: Using Apache'/><author><name>John</name><uri>http://www.blogger.com/profile/12302295115917633435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://bp3.blogger.com/_a57asMH1jkM/SAqQtFYnwDI/AAAAAAAAAGA/UXyYKtDsGnM/S220/myPicture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_a57asMH1jkM/Sf71JRGavPI/AAAAAAAAAI4/FodbACdJkFQ/s72-c/dataEntryView.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7740886660615459010.post-135293863719699430</id><published>2009-04-29T08:32:00.000-07:00</published><updated>2009-04-29T08:33:14.692-07:00</updated><title type='text'>Music Database Program: A bug-fix</title><content type='html'>In the previous entry, I had made an assumption that proved to be false. The program needs to read the CD in order to compute the disc-id. Based on the experience with my computer, I had assumed this would always be identified with the device file /dev/rdisk1. Well, that's not quite true. The device file name that is assigned to the CD drive depends on the order in which the system discovers the drive. If you happen to have connected an external drive (as I had) and then ran this program. The system will have already given out /dev/rdisk1.&lt;br /&gt;&lt;br /&gt;The device will be /dev/rdisk- where - will be replaced by a number. There are a number of issues that arise when you try to find out which rdisk number corresponds to the CD ROM drive. The clue is the acroynm: ROM (read-only-memory). I look for a drive that is not writeable. If you have more than one such drive, this will only work if the CD ROM drive is the first one discovered.&lt;br /&gt;&lt;br /&gt;The program starts with rdisk1, then rdisk2, up to rdisk5 looking for a drive that is not writeable. If none is found, an error condition exists. If one is found, it is assumed to be the CD drive. Perl has file test operators that will determine if a file is writeable. Unfortunately, writeable or not-writeable means "by the person running the program". If the perl script is executing as a cgi application in a web server environment, then the account under which the execution is taking place is usually a special account set up in the system. This account, for security purposes, has little or no privileges. In particular, it may not have write privilege to a disk even if the disk is writeable. So, here's what I did. I look to see if the drive &lt;tt&gt;/dev/rdisk1&lt;/tt&gt; (or whichever number I'm checking) exists. If it does I then execute a system command &lt;tt&gt;ls -l /dev/rdisk1&lt;/tt&gt; and capture the output to a temporary file. &lt;br /&gt;&lt;br /&gt;Here's an example of what the output should look like:&lt;br /&gt;&lt;pre&gt;cr--r-----   1 avila  operator   14,   6 Apr 29 08:11 /dev/rdisk1&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The important part are the first four characters. The c identifies this "device file" as a character special file (that's not particularly important here). The next three characters identify the permissions set for the owner of the file. The first (r) says the owner has read permissions. The second and third characters identify the write and execute permissions. In this case both are the character - which means that even the owner does not have write or execute permissions. This is what we are looking for. If even the owner cannot write to the file, then the device file is not writeable and we will take that to mean this is identifies a CD drive. &lt;br /&gt;&lt;br /&gt;Here's the code: &lt;br /&gt;&lt;font color="#cc0000"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;##--Look for a device that is not writeable. It is assumed that &lt;br /&gt;##--is the CD drive&lt;br /&gt;$diskfile = '/dev/rdisk';&lt;br /&gt;$done = 0;&lt;br /&gt;$found = 0;&lt;br /&gt;$diskNumber = 1;&lt;br /&gt;while ($done != 1)&lt;br /&gt;{&lt;br /&gt; $CDdiskFile = $diskfile.$diskNumber;&lt;br /&gt;&lt;br /&gt; ##--Check if we have found a non-writeable drive&lt;br /&gt; ##--Actually it must be non-writeable by the owner. Since &lt;br /&gt; ##--the owner is not the id of the web server, we have to be&lt;br /&gt; ##--more clever to find out if the owner can write or not.&lt;br /&gt; sleep 5 while !-e $CDdiskFile;&lt;br /&gt; &lt;br /&gt; $command = 'ls -l '.$CDdiskFile." &gt;".$listOutput;&lt;br /&gt; system($command);&lt;br /&gt; open (LISTFILE, $listOutput) or die "Cannot open ls output\n";&lt;br /&gt; $permissionString = &lt;LISTFILE&gt;;&lt;br /&gt; &lt;br /&gt; if (substr($permissionString, 0, 3) eq "cr-") &lt;br /&gt; { &lt;br /&gt;  $found = 1; $done = 1;&lt;br /&gt; }&lt;br /&gt; else &lt;br /&gt; {&lt;br /&gt;  $diskNumber++;&lt;br /&gt;  ##--Only look for 5 drive numbers&lt;br /&gt;  if ($diskNumber &gt; 5){$done = 1;}&lt;br /&gt; }&lt;br /&gt; ##--In either case, remove the list file output.&lt;br /&gt; $command = 'rm -f '.$listOutput;&lt;br /&gt; system($command);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;##--As we exit the while loop check if we actually found the CD drive&lt;br /&gt;##--if not, exit.&lt;br /&gt;if (! $found)&lt;br /&gt;{&lt;br /&gt; print "Cannot locate the CD drive\n";&lt;br /&gt; exit 1;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/font&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;A second "bug"&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;If you are behind a firewall then you have to worry about a second issue. Some firewalls have been set to prevent the use of telnet to access non-local sites. If this is the case, then the program will not be able to access the database from which to extract the album title, track titles, etc.&lt;br /&gt;&lt;br /&gt;At least for the moment, I have chosen to ignore that problem.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7740886660615459010-135293863719699430?l=johnsprograms.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://johnsprograms.blogspot.com/feeds/135293863719699430/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7740886660615459010&amp;postID=135293863719699430' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/135293863719699430'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/135293863719699430'/><link rel='alternate' type='text/html' href='http://johnsprograms.blogspot.com/2009/04/music-database-program-bug-fix.html' title='Music Database Program: A bug-fix'/><author><name>John</name><uri>http://www.blogger.com/profile/12302295115917633435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://bp3.blogger.com/_a57asMH1jkM/SAqQtFYnwDI/AAAAAAAAAGA/UXyYKtDsGnM/S220/myPicture.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7740886660615459010.post-9071635355994851430</id><published>2009-04-10T10:33:00.000-07:00</published><updated>2009-04-10T10:57:22.222-07:00</updated><title type='text'>Music Database Program: The first big step toward a data entry program</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_a57asMH1jkM/Sd-FBdx3xqI/AAAAAAAAAIo/A1gLY4atvgw/s1600-h/dataAcquisitionFlow.jpg"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 400px; height: 197px;" src="http://2.bp.blogspot.com/_a57asMH1jkM/Sd-FBdx3xqI/AAAAAAAAAIo/A1gLY4atvgw/s400/dataAcquisitionFlow.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5323119544795383458" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;It took a number of things to get to the first step of the data entry program. The idea was to create a web application that would read an audio CD, determine appropriate information about that CD, connect to the freeDB web server to get the album title and track title information, and create a form that the user can use to add data to the music database--with the available data already filled in.&lt;br /&gt;&lt;br /&gt;The task of obtaining the necessary information from which we can get the track titles, playtimes, etc. is handed off to the C program cd-discid. In the previous blog entry, I indicated how one can download and install the program using the information on the Darwin Ports web site. The cd-discid program must be able to read the CD once it is inserted into the CD drive. The information that is extracted is the byte offsets into the CD for each track. With this information the disc id can be computed and with the disc id, the freeDB server can be queried to obtain an album title, principal artist, and track title information. This information is then inserted into an HTML document that is the start of the data entry program. The user will be able to add information and/or modify existing information. At the point where the user is satisfied with the data, he clicks on a "submit data" button which will then take the data and begin the process of adding it to the database. I'll have more to say about this later.&lt;br /&gt;&lt;br /&gt;The diagram shows the flow of the process described in the previous paragraph. The connector at the end represents the next stage where the user modifies or adds to the data in preparation to placing it in the database.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Walking through the script&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Using cd-discid to read frame offsets from the CD&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;The beginning of the Perl script has to check to make sure a CD has been inserted into the disk drive. On the Mac, once a disc is inserted, the file /dev/rdisk1 is created and is associated with the CD. If you have used the Darwin Ports procedure to install cd-discid, you will find it located in the directory /opt/local/bin. The command shown will take the output of the cd-discid program and place it in a temporary file which we place in the /tmp directory. One critical item that took me some time to debug is that cd-discid needs to have the uid bit set. I'll show you how to do that in a minute. What this does is allow the program to read the CD. By default, the CD "belongs" to the user who inserted it into the drive. The uid issue has to do with permissions. By setting what's called the uid bit, we give permission to the cd-discid program to read anyone's CD.&lt;br /&gt;&lt;br /&gt;Here's how to do it. Open a terminal window and issue the command:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;chmod u=rws,g=rs,o=rx /opt/local/bin/cd-discid&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;then issue the command:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;ls -l /opt/local/bin/cd-discid&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The permission elements at the beginning of the line that is displayed should agree with the corresponding elements in the following line:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;-rwSr-Sr-x   2 root  admin  13432 Mar 16 11:38 cd-discid&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here's the script so far:&lt;br /&gt;&lt;font color="#cc0000"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$diskfile = '/dev/rdisk1';&lt;br /&gt;$cdDiscIdOutput = "/tmp/discidOutput";&lt;br /&gt;&lt;br /&gt;##--Wait until the disk has been inserted.&lt;br /&gt;sleep 5 while !-e $diskfile;&lt;br /&gt;&lt;br /&gt;##--Execute the cd-discid program to compute the discid and &lt;br /&gt;##--obtain frame offsets.&lt;br /&gt;$command = '/opt/local/bin/cd-discid /dev/rdisk1 1&amp;gt; '. $cdDiscIdOutput;&lt;br /&gt;system ($command);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/font&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Using telnet to communicate with the freeDB server&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;Now that we have the track offsets and the disc id, we have the information we need to request the available information from the freeDB server. The Telnet package has the Perl software necessary to establish a connection to the freeDB server using telnet. It requires that we create an object which represents the connection. We need to specify two temporary files to collect the telnet commands that we generate as well as the responses. These we create in the /tmp directory as above.&lt;br /&gt;&lt;br /&gt;We open a telnet connection to the freeDB telnet server on port 8880 and wait until we receive a reply from the server that contains the numerical code "201". This tells us that the connection is now made. Next we issue a command to the server that starts with "cddb". This is the first command and represents the "handshake" protocol. If you look at the Perl code you see "userMe mySite.com". This needs to be replaced by your e-mail account at the location where your e-mail account is located (e.g. smith yahoo.com). If all is well, the response will start with the code "200". (So far, I have not decided what to do if something goes wrong with establishing the connection.)&lt;br /&gt;&lt;br /&gt;If all is well to this point then I have to read the file with the file that I got from cd-discid. There is some fairly standard Perl to pull apart the data string from the temporary file and to grab the portions that we need. Even before we query the freeDB server, we can already compute the playtime for each track. If we know the number of frames on the track, we can use the fact that there are 75 frames per second to determine the length of each track. The subroutine &lt;tt&gt;sec2minSec&lt;/tt&gt; converts the track length data into a formatted version of the form [mm:ss]. The final piece of this part of the script is to form the command to the freeDB server to seek a match for a CD with the given disc id and track offsets that we have extracted from the given CD.&lt;br /&gt;&lt;font color="#cc0000"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$telnetDumpFile = "/tmp/telnetDumpFile";&lt;br /&gt;$telnetInputLogFile = "/tmp/telnetInputLogFile";&lt;br /&gt;&lt;br /&gt;##--Establish an object to communicate via telnet&lt;br /&gt;use Net::Telnet ();&lt;br /&gt;$t = new Net::Telnet (Dump_Log =&amp;gt; $telnetDumpFile, &lt;br /&gt; Input_log =&amp;gt; $telnetInputLogFile);&lt;br /&gt;$t-&amp;gt;open(Host =&amp;gt; "freedb.freedb.org", Port =&amp;gt; "8880");&lt;br /&gt;&lt;br /&gt;##--Wait for the connection to be established.&lt;br /&gt;$t-&amp;gt;waitfor('/201.*$/');&lt;br /&gt;&lt;br /&gt;##--Send the first command to establish a connection to the &lt;br /&gt;##--database server.&lt;br /&gt;$t-&amp;gt;cmd(String =&amp;gt; "cddb hello userMe mySite.com telnet v0.0",&lt;br /&gt; Prompt =&amp;gt; '/200.*$/');&lt;br /&gt;&lt;br /&gt;##--Read the file into which cd-discid placed the discid and &lt;br /&gt;##--offsets&lt;br /&gt;open(discidAndOffsets, $cdDiscIdOutput) || &lt;br /&gt; die "Can't open discidAndOffsets";&lt;br /&gt;$discidPlus = &amp;lt;discidAndOffsets&amp;gt;;&lt;br /&gt;&lt;br /&gt;## Grab the discid. This will be used to query the database.&lt;br /&gt;@discData = split /\s+/, $discidPlus;&lt;br /&gt;$discid = $discData[0];&lt;br /&gt;&lt;br /&gt;$noTracks = $discData[1];&lt;br /&gt;&lt;br /&gt;##--Compute the total number of minutes, seconds for the album&lt;br /&gt;$timeTotal = $discData[$noTracks + 2];&lt;br /&gt;@minSec = sec2minSec($timeTotal);&lt;br /&gt;$playTimeTotalMin = $minSec[0];&lt;br /&gt;$playTimeTotalSec = $minSec[1];&lt;br /&gt;&lt;br /&gt;##--Now compute the play time for each track.&lt;br /&gt;$playTime[$noTracks] = $timeTotal - $discData[$noTracks + 1]/75;&lt;br /&gt;@minSec = sec2minSec($playTime[$noTracks]);&lt;br /&gt;$playTimeMin[$noTracks] = $minSec[0];&lt;br /&gt;$playTimeSec[$noTracks] = $minSec[1];&lt;br /&gt;&lt;br /&gt;for ($index = 1; $index &amp;lt; $noTracks; $index++)&lt;br /&gt;{&lt;br /&gt; $playTime[$index] = ($discData[$index+2] - $discData[$index+1])/75;&lt;br /&gt; @minSec = sec2minSec($playTime[$index]);&lt;br /&gt; $playTimeMin[$index] = $minSec[0];&lt;br /&gt; $playTimeSec[$index] = $minSec[1];&lt;br /&gt; if ($debug) {print $index, ": [", $minSec[0],":",$minSec[1],"]\n";}&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;##--Query the database with the discid and frame offsets.&lt;br /&gt;$cddbQuery = "cddb query ".$discidPlus;&lt;br /&gt;&lt;br /&gt;$ok = $t-&amp;gt;cmd(String =&amp;gt; "$cddbQuery", Prompt =&amp;gt; '/2[0,1][0-2].*$/');&lt;br /&gt;## Since there is no prompt character issued by the host&lt;br /&gt;## this is all we can do to capture the returning output&lt;br /&gt;$cddbOutput1 = $t-&amp;gt;last_prompt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/font&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Extracting the data from the query reply&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;The output from the last command shown at the end of the previous segment of the script will contain all the information that we want. Our job is to extract the information using a lot of string manipulation to do it. The code is self-explanatory. There are some possibilities in the response to the query that might happen that I haven't yet dealt with as you can see. Also, the last thing we do is generate an HTML document to contain the information that the user will see.&lt;br /&gt;&lt;br /&gt;I'll start here next time and describe how the data entry program will function.&lt;br /&gt;&lt;font color="#cc0000"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;@discInfo = split /\s+/, $cddbOutput1;&lt;br /&gt;$code = $discInfo[0];&lt;br /&gt;$genre = $discInfo[1];&lt;br /&gt;&lt;br /&gt;if ($code eq "202")  ## no match&lt;br /&gt;{&lt;br /&gt; $t-&gt;close;&lt;br /&gt; print "No match\n";&lt;br /&gt; exit 202;&lt;br /&gt;}&lt;br /&gt;elsif ($code eq "211")  ## fuzzy match &lt;br /&gt;{&lt;br /&gt; $t-&gt;close; &lt;br /&gt; print "Fuzzy match\n";&lt;br /&gt; exit 211;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;## If we fall through, then there is at least one exact match.&lt;br /&gt;## Knowing the genre and discid, read the data for this disc&lt;br /&gt;$cddbQuery = "cddb read ".$genre." ".$discid;&lt;br /&gt;@cddbOutput2 = $t-&gt;cmd(String =&gt; $cddbQuery, Errmode =&gt; "return", &lt;br /&gt; Prompt =&gt; '/\.$/');&lt;br /&gt;&lt;br /&gt;##--Gather the principal artist and album title&lt;br /&gt;$trackNumber = 0;&lt;br /&gt;foreach $line (@cddbOutput2)&lt;br /&gt;{&lt;br /&gt; chop $line;&lt;br /&gt; if (index($line, "TTITLE") != -1)  #--Found a track title line&lt;br /&gt; {&lt;br /&gt;  $trackNumber++;&lt;br /&gt;  $marker = index($line, "=");&lt;br /&gt;  $trackTitle[$trackNumber] = substr($line, $marker+1);&lt;br /&gt;  $trackTime[$trackNumber] = " [".$playTimeMin[$trackNumber].&lt;br /&gt;   ":".$playTimeSec[$trackNumber]."]";&lt;br /&gt; }&lt;br /&gt; elsif (index($line, "DTITLE") != -1) #--Found the disc title line&lt;br /&gt; {&lt;br /&gt;  $marker = index($line, "=");&lt;br /&gt;  $fields = substr($line, $marker+1);&lt;br /&gt;  ($principalArtistField, $cdTitleField) = split /\//, $fields;&lt;br /&gt;  $playingTime = "[".$playTimeTotalMin.":".$playTimeTotalSec."]";&lt;br /&gt;  ##--remove white space and certain other characters &lt;br /&gt;  ##  from the title string&lt;br /&gt;  @cdTitleWords = split /[ .,:;()]/,$cdTitleField;  #/&lt;br /&gt;  $compactTitle = join '', @cdTitleWords;&lt;br /&gt;  $coverArtFileName = $compactTitle."_".$discid.".png";&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;$t-&gt;close;&lt;br /&gt;&lt;br /&gt;##--Cleanup&lt;br /&gt;if (! $debug)&lt;br /&gt;{&lt;br /&gt; $command = 'rm -f '.$cdDiscIdOutput;&lt;br /&gt; system($command);&lt;br /&gt; $command = 'rm -f '.$telnetInputLogFile;&lt;br /&gt; system($command);&lt;br /&gt; $command = 'rm -f '.$telnetDumpFile;&lt;br /&gt; system($command);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;generateHTML();&lt;br /&gt;exit 0;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/font&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7740886660615459010-9071635355994851430?l=johnsprograms.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://johnsprograms.blogspot.com/feeds/9071635355994851430/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7740886660615459010&amp;postID=9071635355994851430' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/9071635355994851430'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/9071635355994851430'/><link rel='alternate' type='text/html' href='http://johnsprograms.blogspot.com/2009/04/music-database-program-first-big-step.html' title='Music Database Program: The first big step toward a data entry program'/><author><name>John</name><uri>http://www.blogger.com/profile/12302295115917633435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://bp3.blogger.com/_a57asMH1jkM/SAqQtFYnwDI/AAAAAAAAAGA/UXyYKtDsGnM/S220/myPicture.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_a57asMH1jkM/Sd-FBdx3xqI/AAAAAAAAAIo/A1gLY4atvgw/s72-c/dataAcquisitionFlow.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7740886660615459010.post-8852779920341679209</id><published>2009-03-28T21:18:00.000-07:00</published><updated>2009-03-29T14:19:12.815-07:00</updated><title type='text'>Music Database Program: The Elephant in the Room</title><content type='html'>Even though we are still working on the design and attempting to determine how to utilize existing available software (such as NeoDatis), the large object in the middle of the room  is the data entry element of the database. The utility of the database will require that we have the most comprehensive set of music data that we can supply. Yet, if we have to enter every field for every musical element, no one will want to use this software.&lt;br /&gt;&lt;br /&gt;So, in this entry, we want to look at how to automate, as much as possible, the task of entering data into the database. First of all, there are already existing (and free) online CD databases. This is where we will start. Generally, we have in our database many more elements than just the track titles, times, and some artist information for each album. Nevertheless, if we can start with the data that is available online, we will only have to enter those elements that extend that data.&lt;br /&gt;&lt;br /&gt;There is a database called &lt;a href="http://www.freedb.org/" TARGET="_blank"&gt;freeDB&lt;/a&gt; which contains a large repository of information on music CDs. In particular, the data available consists of album titles and principal artists, track titles and playing times, and musical genre. In order to identify the CD, you need some information from the disc itself.&lt;br /&gt;&lt;br /&gt;The basic identifier for a CD is something called the disc id. This number is obtained by an algorithm that utilizes the information available on the CD. This consists primarily of the numbers that represent the frame offsets to the individual tracks on the CD. You can find out more about this at the &lt;a href= "http://wiki.musicbrainz.org/Disc_ID_Calculation" TARGET="_blank"&gt;MusicBrainz&lt;/a&gt; web site. There's even a C program called cd-discid that you can download and install that will read an audio CD and generate the disc id. If you are on a Mac, you can find what you need to install it at the &lt;a href="http://cd-discid.darwinports.com/" TARGET="_blank"&gt;Darwin Ports&lt;/a&gt; web site.&lt;br /&gt;&lt;br /&gt;There are a number of ways of obtaining the data from the freedb database. freedb.org even provides the ability to obtain the information using a &lt;a href="http://www.freedb.org/freedb_discid_check.php" TARGET="_blank"&gt;simple front end&lt;/a&gt;. I want to automate the search so that I can insert a CD, the software will automatically pick off the data on the CD that I need to query the freedb database, make the query, collect the results and place the results in appropriate fields, leaving me to fill in missing fields or to modify the pre-filled data if necessary. The manual interaction with the database using the freedb.org front end won't suffice.&lt;br /&gt;&lt;br /&gt;There is another mechanism to access the freedb database and that is to use telnet to query the database. One establishes a connection to the freedb database server and interacts with the server using the cddb protocol. There is a very useful document that you can get from the freedb website by clicking on the &lt;b&gt;freedb howto v1.07&lt;/b&gt; link on the &lt;a href="http://www.freedb.org/en/download__miscellaneous.11.html" TARGET="_blank"&gt;freedb website&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;This is the mechanism that seemed the most reasonable; utilize available tools and software as much as possible and choose methods that can be automated. It would be my preference to be able to provide a Java program to do this but for starters cd-discid is in C and the mechanisms in Java to communicate with C are arcane. The most sensible is to use a scripting language. Perl is what I chose. In a Perl script, I can call the cd-discid program. Using telnet from within a Perl script requires some tricky programming involving sockets. Fortunately, there is a &lt;a href="http://search.cpan.org/~jrogers/Net-Telnet-3.03/lib/Net/Telnet.pm" TARGET="_blank"&gt;telnet library&lt;/a&gt; which I was able to use. &lt;br /&gt;&lt;br /&gt;So now I have a script that first waits until the CD has been inserted into the drive, then it calls the cd-discid program and captures the output of that program. At this point telnet is used to connect to the freedb database server and a series of queries yields the desired data.&lt;br /&gt;&lt;br /&gt;There is still work to do on the script. What if there is no match with the discid? What if there are multiple matches? What if there are no matches but there are "close misses"?&lt;br /&gt;&lt;br /&gt;Next, I want to turn the Perl script into a cgi application that will allow me to interact with a browser to let me view the existing data, add missing fields, modify any of the data items that are incorrect and then save the resulting data for the CD into my object-oriented database.&lt;br /&gt;&lt;br /&gt;I'll post more when I've put these elements together.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7740886660615459010-8852779920341679209?l=johnsprograms.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://johnsprograms.blogspot.com/feeds/8852779920341679209/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7740886660615459010&amp;postID=8852779920341679209' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/8852779920341679209'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/8852779920341679209'/><link rel='alternate' type='text/html' href='http://johnsprograms.blogspot.com/2009/03/elephant-in-room.html' title='Music Database Program: The Elephant in the Room'/><author><name>John</name><uri>http://www.blogger.com/profile/12302295115917633435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://bp3.blogger.com/_a57asMH1jkM/SAqQtFYnwDI/AAAAAAAAAGA/UXyYKtDsGnM/S220/myPicture.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7740886660615459010.post-1988043845164736258</id><published>2009-03-09T13:16:00.000-07:00</published><updated>2009-03-29T14:15:25.793-07:00</updated><title type='text'>Music Database Program: A NeoDatis Test</title><content type='html'>We will need to be able to store and retrieve data from our database. Moreover, we want to be able to store the data as objects and not have to convert the object data into tables as in a relational database model. The &lt;a href="http://www.neodatis.org" TARGET="_blank"&gt;NeoDatis object database engine&lt;/a&gt; appears to be a good candidate in our quest for support for this music database program. In reality, our plan is to use the software more as a "persistence" engine. We will be able to store the objects of our program in a way that allows us to restore them at a later time while preserving the references between objects in our model. But first, we had to be able to see how this might work and if, in fact, it worked as advertized.&lt;br /&gt;&lt;br /&gt;In our tests we had to create a number of objects, store them, come back after exiting the program to see if the data had, in fact, been stored in a persistent manner. Moreover, we had to test to make sure the connection (references) between objects was preserved.&lt;br /&gt;&lt;br /&gt;As part of this test we added another class to those described earlier. This class is the Album class. It's similar to the earlier classes in that it represents one of the data objects that make up our model. We've supplied a number of fields for this class but there may be more later. We have also gone back and made some changes to the classes we previously defined. This, too, will be described later. Generally, this is part of the process of discovery as we design and build the program.&lt;br /&gt;&lt;br /&gt;There were two Neodatis database software tests. In the first test, we created and stored a number of objects. In the second test, we retrieved the objects created in the first test and demonstrated that they were in fact whole; that is, that they behave as they did before they were stored and later reconstituted. One of the great features of the NeoDatis software is that when it stores an object, all objects that are referenced by that object (and all those referenced by those, recursively) will be stored as well. This greatly simplifies the job of saving the data.&lt;br /&gt;&lt;br /&gt;One of the modifications to the Artist class was to add a field of type LinkedList&lt;Album&gt; that contains a list of all the albums on which that particular artist appears. When we retrieve the Artist objects, we should be able to display the information about the albums associated with any artist and to see the tracks on that album. All this should be possible without any additional work on our part in storing the objects.&lt;br /&gt;&lt;br /&gt;In the first test we created two Album objects. The Albums contain references to Artists. The Artist objects contain lists that reference the albums. Since NeoDatis will traverse an object that it has been asked to store and automatically store all the objects referenced within the object (recursively), we should be able to store the album objects and get everything else stored along the way. This is because the Album objects contain references to track objects which contain references the performer objects which contain references to the Artist and Instrument objects. All this by just storing two Album objects!&lt;br /&gt;&lt;br /&gt;The second test required that we ask NeoDatis to recover the Album objects. NeoDatis makes this easy by allowing us to ask for all objects of a particular class. Just to make sure the Artist objects have been retrieved, we display the name of each artist. Next we retrieved the two Album objects. From this collection of albums, we chose one and then displayed the titles of each of the tracks on that album. &lt;br /&gt;&lt;br /&gt;The good news is that once we discovered a basic error in the NeoDatis documentation, we were able to carry out the tests successfully.&lt;br /&gt;&lt;br /&gt;Here are some of the details: &lt;br /&gt;&lt;br /&gt;This is now the set of data fields for the Artist class:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    private String name = null;&lt;br /&gt;    private LinkedList&amp;lt;Album&amp;gt; albums = null;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The new Album class has the fields:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    private String name = null;    //--the name of the album&lt;br /&gt;    private Artist[] principalArtist = null;&lt;br /&gt;    private String location = null;    //--Where to find the physical album&lt;br /&gt;    private int numberOfTracks = 0;&lt;br /&gt;    private String label = null;&lt;br /&gt;    private List&amp;lt;Track&amp;gt; tracks = null;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;There are the usual constructors, getter and setter methods.&lt;br /&gt;&lt;br /&gt;Here is the code that stores the data into the NeoDatis database...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    //--Open the database&lt;br /&gt;    ODB odb = ODBFactory.open("MyMusicDB"); &lt;br /&gt;&lt;br /&gt;    //--Store the two albums in this test&lt;br /&gt;    odb.store(album1);&lt;br /&gt;    odb.store(album2);&lt;br /&gt;&lt;br /&gt;    //--Close the database &lt;br /&gt;    odb.close();        &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This is it! That's all that's required to store everything!&lt;br /&gt;&lt;br /&gt;And here is the retrieval of the data elements we want to check from the second test:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;//--Open the database.&lt;br /&gt;ODB odb = null; &lt;br /&gt;try&lt;br /&gt;{ &lt;br /&gt;    //--Open the database &lt;br /&gt;    odb = ODBFactory.open("MyMusicDB"); &lt;br /&gt;        &lt;br /&gt;    //--Get all object of type Artist &lt;br /&gt;    Objects&amp;lt;Artist&amp;gt; objects = odb.getObjects(Artist.class); &lt;br /&gt;    System.out.println(objects.size() + "  artist(s)"); &lt;br /&gt;        &lt;br /&gt;    //--display each object &lt;br /&gt;    while(objects.hasNext())&lt;br /&gt;    { &lt;br /&gt;        Artist a = (Artist)objects.next();&lt;br /&gt;        System.out.println("    " + a.getName()); &lt;br /&gt;    } &lt;br /&gt;    &lt;br /&gt;    //--Get the albums, pick out album2 and display its tracks.&lt;br /&gt;    Objects&amp;lt;Album&amp;gt; albumObj = odb.getObjects(Album.class);&lt;br /&gt;    &lt;br /&gt;    while (albumObj.hasNext())&lt;br /&gt;    {&lt;br /&gt;        Album album = (Album)albumObj.next();&lt;br /&gt;        if (album.getName().equals("The Essential Stan Getz"))&lt;br /&gt;        {&lt;br /&gt;            System.out.println("Album/Tracks");&lt;br /&gt;            List&amp;lt;Track&amp;gt; tracks = album.getTracks();&lt;br /&gt;            ListIterator&amp;lt;Track&amp;gt; iter = tracks.listIterator();&lt;br /&gt;            while(iter.hasNext())&lt;br /&gt;            {&lt;br /&gt;                System.out.println("    " + iter.next().getTitle());&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}finally&lt;br /&gt;{ &lt;br /&gt;    //--Closes the database &lt;br /&gt;    if(odb!=null)&lt;br /&gt;    { &lt;br /&gt;        odb.close(); &lt;br /&gt;    } &lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7740886660615459010-1988043845164736258?l=johnsprograms.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://johnsprograms.blogspot.com/feeds/1988043845164736258/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7740886660615459010&amp;postID=1988043845164736258' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/1988043845164736258'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/1988043845164736258'/><link rel='alternate' type='text/html' href='http://johnsprograms.blogspot.com/2009/03/neodatis-test.html' title='Music Database Program: A NeoDatis Test'/><author><name>John</name><uri>http://www.blogger.com/profile/12302295115917633435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://bp3.blogger.com/_a57asMH1jkM/SAqQtFYnwDI/AAAAAAAAAGA/UXyYKtDsGnM/S220/myPicture.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7740886660615459010.post-5941396307988150990</id><published>2009-02-25T20:14:00.000-08:00</published><updated>2009-03-29T14:15:09.790-07:00</updated><title type='text'>Music Database Program: Four Data Classes</title><content type='html'>It's been too long since I have had a chance to work on this project. Too many other projects. The "friction losses" are always larger than you think they will be when you have been away from something for this long. In this entry we are looking to create enough of the classes with enough of the detail so that we can check out the Neodatis database program. &lt;br /&gt;&lt;br /&gt;Here are the first few data classes: the Instrument class, the Artist class, the Performer class, and the Track class. If you compare this post to the previous one, you will see that there have already been some changes reflecting more thought about the program. For example we decided to keep an artist and the instrument separate so that an artist could appear on a track with one instrument and on another track with a different instrument.&lt;br /&gt;&lt;br /&gt;The Instrument class represents an instrument such as a trumpet or piano. If we had the names of all the instrument types we would ever use, then we would probably use an enumerated type. This would make it less likely that we would have two classes associated with a single instrument type. Unfortunately, we don't know that there won't be some instrument that we had not anticipated. Doing things this way lets us add instruments at run-time if we need to.&lt;br /&gt;&lt;br /&gt;The Artist class has to represent the elements of an artist that we want to preserve in our database. It is tempting to associate an artist with a particular instrument but unfortunately, many artists use multiple instruments. It's easier to have an artist consist of the name of the artist and some background information (such as a picture, a biography, discography). Perhaps we will have a link to the Wikepedia article about an artist (if one exists). The Performer class represents the pair of artist and the instrument that are used on a particular track. It would be tempting to make the Performer class a subclass of Artist. The reason for NOT doing that is that we want to have a single Artist object for each artist regardless of how many instrument he or she plays&lt;br /&gt;&lt;br /&gt;Finally, the Track class represents one track on a CD or record. It will contain several pieces of information related to the track such as the name of the tune on the track, the running time in minutes and seconds, and a list of performers on the track. In the final version of this class, we may add other fields such as the composer and any other pieces of information we want to store related to a particular track. For now we will just keep it simple.&lt;br /&gt;&lt;br /&gt;In each of these classes we have included a static main method which gives us the ability to test each class as we develop the program.&lt;br /&gt;&lt;br /&gt;The Instrument class...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;public class Instrument&lt;br /&gt;{&lt;br /&gt;    private String instrumentName;&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * &lt;br /&gt;     * @param name is the name of the instrument (e.g. Piano)&lt;br /&gt;     */&lt;br /&gt;    public Instrument(String name)&lt;br /&gt;    {&lt;br /&gt;        instrumentName = name;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public String getInstrumentName()&lt;br /&gt;    {&lt;br /&gt;        return instrumentName;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * @param args&lt;br /&gt;     */&lt;br /&gt;    public static void main(String[] args)&lt;br /&gt;    {&lt;br /&gt;        //--Test the Instrument class&lt;br /&gt;        &lt;br /&gt;        Instrument piano = new Instrument("Piano");&lt;br /&gt;        Instrument trumpet = new Instrument("Trumpet");&lt;br /&gt;        &lt;br /&gt;        System.out.println("piano: " + piano.getInstrumentName());&lt;br /&gt;        System.out.println("trumpet: " + trumpet.getInstrumentName());&lt;br /&gt;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The Artist class...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;public class Artist&lt;br /&gt;{&lt;br /&gt;        &lt;br /&gt;    private String artistName = null;&lt;br /&gt;    &lt;br /&gt;    public Artist(String name)&lt;br /&gt;    {&lt;br /&gt;        artistName = name;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * Get method&lt;br /&gt;     * @return the name of the artist&lt;br /&gt;     */&lt;br /&gt;    public String getName()&lt;br /&gt;    {&lt;br /&gt;        return artistName;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * @param args&lt;br /&gt;     */&lt;br /&gt;    public static void main(String[] args)&lt;br /&gt;    {&lt;br /&gt;        /*&lt;br /&gt;         * Create an object of type Artist&lt;br /&gt;         */&lt;br /&gt;        Artist bill = new Artist("Bill Evans");&lt;br /&gt;        &lt;br /&gt;        /*&lt;br /&gt;         * Print the contents of the new object using the GET methods&lt;br /&gt;         */&lt;br /&gt;        System.out.println("Artist name = " + bill.getName());&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The Performer class...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;/**&lt;br /&gt; * A performer will consist of an artist and an instrument.&lt;br /&gt; * &lt;br /&gt; * @author avila&lt;br /&gt; *&lt;br /&gt; */&lt;br /&gt;public class Performer&lt;br /&gt;{&lt;br /&gt;    private Artist artist = null;&lt;br /&gt;    private Instrument instrument = null;&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * &lt;br /&gt;     * @param artist &lt;br /&gt;     * @param instrument&lt;br /&gt;     */&lt;br /&gt;    public Performer(Artist artist, Instrument instrument)&lt;br /&gt;    {&lt;br /&gt;        this.artist = artist;&lt;br /&gt;        this.instrument = instrument;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * &lt;br /&gt;     * @return the artist&lt;br /&gt;     */&lt;br /&gt;    public Artist getArtist()&lt;br /&gt;    {&lt;br /&gt;        return artist;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * Getter method&lt;br /&gt;     * @return the instrument associated with this performer&lt;br /&gt;     */&lt;br /&gt;    public Instrument getInstrument()&lt;br /&gt;    {&lt;br /&gt;        return instrument;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * @param args&lt;br /&gt;     */&lt;br /&gt;    public static void main(String[] args)&lt;br /&gt;    {&lt;br /&gt;        //--Create a few Instrument objects that will be used for the Music DB&lt;br /&gt;        Instrument piano = new Instrument("Piano");&lt;br /&gt;        Instrument bass = new Instrument("Bass");&lt;br /&gt;        Instrument drums = new Instrument("Drums");&lt;br /&gt;        &lt;br /&gt;        //--Create some Artist objects&lt;br /&gt;        Artist billEvans = new Artist("Bill Evans");&lt;br /&gt;        Artist scottLaFaro = new Artist("Scott La Faro");&lt;br /&gt;        Artist paulMotion = new Artist("Paul Motion");&lt;br /&gt;   &lt;br /&gt;        //--Some Performer objects&lt;br /&gt;        Performer performer1 = new Performer(billEvans, piano);&lt;br /&gt;        Performer performer2 = new Performer(scottLaFaro, bass);&lt;br /&gt;        Performer performer3 = new Performer(paulMotion, drums);&lt;br /&gt;        &lt;br /&gt;        //--Display the contents of the generated Performer objects&lt;br /&gt;        System.out.println("Performer performer1: " + performer1.getArtist().getName() +&lt;br /&gt;                performer1.getInstrument().getInstrumentName() );&lt;br /&gt;        System.out.println("Performer performer2: " + performer2.getArtist().getName() +&lt;br /&gt;                performer2.getInstrument().getInstrumentName() );&lt;br /&gt;        System.out.println("Performer performer3: " + performer3.getArtist().getName() +&lt;br /&gt;                performer3.getInstrument().getInstrumentName() );&lt;br /&gt;        &lt;br /&gt;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The Track class...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import java.util.*;&lt;br /&gt;&lt;br /&gt;public class Track&lt;br /&gt;{&lt;br /&gt;    private String title;&lt;br /&gt;    private String length;&lt;br /&gt;    private List&lt;Performer&gt; performers;&lt;br /&gt;    &lt;br /&gt;    public Track(String title, String length, &lt;br /&gt;            List&amp;lt;Performer&amp;gt; performers)&lt;br /&gt;    {&lt;br /&gt;        this.title = title;&lt;br /&gt;        this.length = length;&lt;br /&gt;        this.performers = performers;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * @return track title&lt;br /&gt;     */&lt;br /&gt;    public String getTitle()&lt;br /&gt;    {&lt;br /&gt;        return title;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * @return track length (min:sec)&lt;br /&gt;     */&lt;br /&gt;    public String getLength()&lt;br /&gt;    {&lt;br /&gt;        return length;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * @return list of performers on this track&lt;br /&gt;     */&lt;br /&gt;    public List&amp;lt;Performer&amp;gt; getPerformers()&lt;br /&gt;    {&lt;br /&gt;        return performers;&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /**&lt;br /&gt;     * @param args&lt;br /&gt;     */&lt;br /&gt;    public static void main(String[] args)&lt;br /&gt;    {&lt;br /&gt;        /*&lt;br /&gt;         * Test the Track class&lt;br /&gt;         */&lt;br /&gt;        &lt;br /&gt;        /**&lt;br /&gt;         * and the Instrument objects we need&lt;br /&gt;         */&lt;br /&gt;        Instrument piano = new Instrument("Piano");&lt;br /&gt;        Instrument bass = new Instrument("Bass");&lt;br /&gt;        Instrument drums = new Instrument("Drums");&lt;br /&gt;        &lt;br /&gt;        /**&lt;br /&gt;         * Create some artists&lt;br /&gt;         */&lt;br /&gt;        Artist billEvans = new Artist("Bill Evans");&lt;br /&gt;        Artist scottLaFaro = new Artist("Scott La Faro");&lt;br /&gt;        Artist paulMotion = new Artist("Paul Motion");&lt;br /&gt;        &lt;br /&gt;        /*&lt;br /&gt;         * Create the list of performers for this track&lt;br /&gt;         */&lt;br /&gt;        List&lt;Performer&gt; performers = new LinkedList&lt;Performer&gt;();&lt;br /&gt;        performers.add(new Performer(billEvans, piano));&lt;br /&gt;        performers.add(new Performer(scottLaFaro, bass));&lt;br /&gt;        performers.add(new Performer(paulMotion, drums));&lt;br /&gt;        &lt;br /&gt;        /*&lt;br /&gt;         * Create the Track object&lt;br /&gt;         */&lt;br /&gt;        Track one = new Track("Israel", "6:08", performers);&lt;br /&gt;        &lt;br /&gt;        /*&lt;br /&gt;         * Now test the get methods&lt;br /&gt;         */&lt;br /&gt;        String trackTitle = one.getTitle();&lt;br /&gt;        System.out.println("Track title: " + trackTitle);&lt;br /&gt;        &lt;br /&gt;        String trackLength = one.getLength();&lt;br /&gt;        System.out.println("Track length: " + trackLength);&lt;br /&gt;        &lt;br /&gt;        List&lt;Performer&gt; trackPerformers = one.getPerformers();&lt;br /&gt;        System.out.println("Performers/Instruments: ");&lt;br /&gt;        ListIterator&amp;lt;Performer&amp;gt; iter = trackPerformers.listIterator();&lt;br /&gt;        while (iter.hasNext())&lt;br /&gt;        {&lt;br /&gt;            Performer performer = iter.next();&lt;br /&gt;            System.out.println(performer.getArtist().getName() + " : " +&lt;br /&gt;                    performer.getInstrument().getInstrumentName());&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7740886660615459010-5941396307988150990?l=johnsprograms.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://johnsprograms.blogspot.com/feeds/5941396307988150990/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7740886660615459010&amp;postID=5941396307988150990' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/5941396307988150990'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/5941396307988150990'/><link rel='alternate' type='text/html' href='http://johnsprograms.blogspot.com/2009/02/four-data-classes.html' title='Music Database Program: Four Data Classes'/><author><name>John</name><uri>http://www.blogger.com/profile/12302295115917633435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://bp3.blogger.com/_a57asMH1jkM/SAqQtFYnwDI/AAAAAAAAAGA/UXyYKtDsGnM/S220/myPicture.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7740886660615459010.post-499577049865441325</id><published>2008-11-22T22:12:00.000-08:00</published><updated>2009-03-29T14:14:43.577-07:00</updated><title type='text'>Music Database Program: Setting up an early design test</title><content type='html'>Let’s imagine the database elements. We have albums. They have a title, a principal artist or group, a list of tracks and additional information such as recording dates and where the album is located (so that we can actually find it physically). We may discover additional items of information that we need as we develop the design. The tracks each have a title, the length of the track, a list of artists, the instruments they play, the composer, and perhaps additional information we will find we want. &lt;br /&gt;&lt;br /&gt;At this point we are trying to develop a design. That is, we want to determine the objects in our program and their relationships with each other. As we develop the design, we will build some software elements that we will use to test ideas and perhaps to form the start of the program itself. &lt;br /&gt;&lt;br /&gt;Let’s try to identify some of the classes of objects we will be working with. The idea here is not to nail down all the detail but rather to discover as many classes as we can and to think carefully about the inter-relationship of these classes.&lt;br /&gt;&lt;br /&gt;We will need an Album object. That object contains a list of (references to) Track objects. Track objects will carry the information for a single track on a particular album. An Album will have a reference to an Artist object (the principal artist). Here we will have to think about how to handle groups of artists as a single object (e.g. the New York Philharmonic). Each Track object will have a reference to a list of artists that perform on that particular track. &lt;br /&gt;&lt;br /&gt;The query: “find all occurrences of the title: ‘Waltz for Debby’” will result in searching the titles of each track on each album for that string. We should get a list of Album, Track pairs as the response to the query so that we can not only display each track but display the album information including the physical location of the album. I can see a screen showing the cover art for one album with a list of the tracks with the relevant one highlighted. In addition, there are Next and Previous buttons that allow us to move forward or backward in the list. If we want information about the album (such as its physical location) we double-click the cover art and a separate window will let us choose what we want to see about the album.&lt;br /&gt;&lt;br /&gt;A first step in sorting some of this out is to build a very small set of objects in a preliminary version of our music database and test to see how we would use &lt;a href="http://www.neodatis.org/" target="_blank"&gt;NeoDatis&lt;/a&gt; as our search engine. We’ll start with a description of a few classes. Then in the next blog entry we will describe the testing of these objects in the &lt;a href="http://www.neodatis.org/" target="_blank"&gt;NeoDatis&lt;/a&gt; context.&lt;br /&gt;&lt;br /&gt;The &lt;span style="font-weight:bold;"&gt;Track&lt;/span&gt; class:&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Data&lt;/span&gt;:&lt;br /&gt; Title (String)&lt;br /&gt; Length (String -- min:sec)&lt;br /&gt; Performers (List of Artist objects)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Methods&lt;/span&gt;:&lt;br /&gt; Get/Set methods for the data&lt;br /&gt; &lt;br /&gt;&lt;br /&gt;The &lt;span style="font-weight:bold;"&gt;Artist&lt;/span&gt; class:&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Data&lt;/span&gt;:&lt;br /&gt; Name (String)&lt;br /&gt; Instruments (List of instruments (enum type) played on this track)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Methods&lt;/span&gt;:&lt;br /&gt; Get/Set methods for the data&lt;br /&gt;&lt;br /&gt; &lt;br /&gt;The &lt;span style="font-weight:bold;"&gt;Album&lt;/span&gt; class:&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Data&lt;/span&gt;:&lt;br /&gt; Album name (String)&lt;br /&gt; Principal Artist (Artist object)&lt;br /&gt; List of tracks (List of Track objects)&lt;br /&gt; Length of Album (String -- min:sec (computed from track info))&lt;br /&gt; Physical location of album (String)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Methods&lt;/span&gt;:&lt;br /&gt; Get/Set methods for the data&lt;br /&gt;&lt;br /&gt;A couple of final notes: the Set methods will be used to initialize database elements. Also, the fact that this is a database application means there is no need of methods (at least at this point in the design) other than the Get/Set methods.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7740886660615459010-499577049865441325?l=johnsprograms.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://johnsprograms.blogspot.com/feeds/499577049865441325/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7740886660615459010&amp;postID=499577049865441325' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/499577049865441325'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/499577049865441325'/><link rel='alternate' type='text/html' href='http://johnsprograms.blogspot.com/2008/11/setting-up-early-design-test.html' title='Music Database Program: Setting up an early design test'/><author><name>John</name><uri>http://www.blogger.com/profile/12302295115917633435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://bp3.blogger.com/_a57asMH1jkM/SAqQtFYnwDI/AAAAAAAAAGA/UXyYKtDsGnM/S220/myPicture.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7740886660615459010.post-8124923522269835823</id><published>2008-11-19T17:08:00.000-08:00</published><updated>2009-03-29T14:14:13.289-07:00</updated><title type='text'>Music Database Program: An Object Oriented Database</title><content type='html'>Before we commit to a design, however, we have a lot more work to do. If I build the object-oriented database myself, I will be relying on object serialization to store the objects on disk in between uses of the program. The reading of serialized data each time the program is run will be time consuming. There are OODBs already out there that are open source. Maybe it would be better to build on top of such software where many of these issues have been dealt with.&lt;br /&gt;&lt;br /&gt;I did some online research and found what may be an answer: &lt;a href="http://www.neodatis.org/" target="_blank"&gt;NeoDatis&lt;/a&gt; - a native object database. The fact that this is an opensource project makes it attractive. One can learn a lot from the source code. Presumably, some of the issues have been resolved already about accessing the data base in an efficient manner. &lt;br /&gt;&lt;br /&gt;A first look at NeoDatis (the &lt;a href="http://wiki.neodatis.org/5m-tutorial" target="_blank"&gt;five minute tutorial&lt;/a&gt; on their web site) makes it appear that the OODB has been written to look a lot like a relational database. In particular the queries appear as method calls that look like SQL queries. I need to think about this and see if I can map the music DB program into a form for which this works.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7740886660615459010-8124923522269835823?l=johnsprograms.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://johnsprograms.blogspot.com/feeds/8124923522269835823/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7740886660615459010&amp;postID=8124923522269835823' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/8124923522269835823'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/8124923522269835823'/><link rel='alternate' type='text/html' href='http://johnsprograms.blogspot.com/2008/11/object-oriented-database.html' title='Music Database Program: An Object Oriented Database'/><author><name>John</name><uri>http://www.blogger.com/profile/12302295115917633435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://bp3.blogger.com/_a57asMH1jkM/SAqQtFYnwDI/AAAAAAAAAGA/UXyYKtDsGnM/S220/myPicture.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7740886660615459010.post-786898334416380623</id><published>2008-11-17T17:54:00.000-08:00</published><updated>2009-03-29T14:13:23.248-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Music Database Program'/><title type='text'>Music Database Program: Introduction</title><content type='html'>Over the years I’ve accumulated many CDs. In fact, the collection has grown large enough that I’ve lost track of exactly what I have. Often I’ll want to find something that I remember having but I don’t remember where the CD is located. Sometimes I may remember something about the track that I want to hear but can’t remember the artist or the title of the CD. Well, isn’t that what databases are supposed to be for?&lt;br /&gt;&lt;br /&gt;Of course, the easy answer to the “problem” is to rip the music collection to MP3 files and use iTunes (or a similar application) to handle the data (and the playing of it). That is an option, but then I wouldn’t have the fun of putting the software together and learning some things along the way. There is also the fact that some of the queries don’t map easily into the choices I have in iTunes.&lt;br /&gt;&lt;br /&gt;The usual approach for the use of databases is to use one of the freely available high-quality relational database systems (such as mySQL) and then write a front-end to the database system with a nice graphical user interface. I thought it would more interesting to develop instead an object-oriented database (OODB). The idea is to tie the data together in a set of inter-related objects in such a way as to be able to locate the information you want in a few steps.&lt;br /&gt;&lt;br /&gt;For example, suppose I want to find a track that has Bill Evans playing “Waltz for Debby”. One object will contain a list of all CDs in which Bill Evans plays one or more tracks. Another object contains a list of all CDs on which “Waltz for Debby” is played. Assuming these lists are relatively small (how many versions of “Waltz for Debby” could I have?), the intersection of the elements in these two lists can be computed in O(n&lt;sup&gt;2&lt;/sup&gt;) for a small value of n.&lt;br /&gt;&lt;br /&gt;Well, that seems like a good start but let’s explore the kinds of queries I want to make of this database. Suppose I want to find anything written by George Gershwin. According to the model, we have an object that contains a list of all CDs with at least one track composed by Gershwin. Suppose I want something recorded in the last 5 years originally written by Gershwin. Now the problem is a little more difficult because the value of 5 years is arbitrary and perhaps tomorrow I will want that number to be 3 years or 10 years. I’ll need a way to get a list of recordings based on the date they were recorded. The idea is the same (an object that contains a list of CDs that meet the criterion) but with the additional aspect that the list is ordered by the recording date. Now we take the intersection of the Gershwin list with the list of CDs recorded in the last 5 years.&lt;br /&gt;&lt;br /&gt;The concept is to associate each property (some aspect of the database that provides a criterion for searching) with an object that contains a list of those CDs with that property.&lt;br /&gt;&lt;br /&gt;One choice that I’ve made that deserves more scrutiny is the use of the CD as the atomic element of the database and not the track. It seems that most of the tracks on a CD will share the same properties (artists, instrument, time of recording, etc.) so that keeping the CD together avoids scattering the more fine-grained data (the tracks) into an assortment of objects. The price of extracting the relevant tracks once we have narrowed the search down to CDs seems small. Besides, ultimately, I will need to locate the CD if I want to play any of its tracks.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7740886660615459010-786898334416380623?l=johnsprograms.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://johnsprograms.blogspot.com/feeds/786898334416380623/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=7740886660615459010&amp;postID=786898334416380623' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/786898334416380623'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7740886660615459010/posts/default/786898334416380623'/><link rel='alternate' type='text/html' href='http://johnsprograms.blogspot.com/2008/11/introduction.html' title='Music Database Program: Introduction'/><author><name>John</name><uri>http://www.blogger.com/profile/12302295115917633435</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='http://bp3.blogger.com/_a57asMH1jkM/SAqQtFYnwDI/AAAAAAAAAGA/UXyYKtDsGnM/S220/myPicture.jpg'/></author><thr:total>0</thr:total></entry></feed>
