Step 3: Adding hot tracks

You can alter existing tables to add new columns.

Now you will enhance Playlist to allow users to select a particular track as a hot track. This will involve creating a new table, track_by_id, and altering the existing track_by_artist and track_by_genre tables with a new column. The TracksDAO will be altered to store whether a track is a hot track.

Changes to the Playlist schema 

The track_by_artist and track_by_genre tables need a new boolean column to indicate whether the track has been selected as a hot track. You can add a new column to existing tables by using the ALTER TABLE syntax in cqlsh.

ALTER TABLE track_by_artist ADD starred boolean;
ALTER TABLE track_by_genre ADD starred boolean;
track_by_artist
track text PK
artist text PK
track_id UUID PK
track_length_in_seconds int  
genre text  
music_file text  
starred boolean  
track_by_genre
track text PK
artist text PK
track_id UUID PK
track_length_in_seconds int  
genre text PK
music_file text  
starred boolean  

You will use an additional query to find tracks by their ID, which means you need a new table, track_by_id. This table is similar to the other track tables but uses a simple primary key of the track ID.

create table track_by_id (track text, artist text, track_id UUID, track_length_in_seconds int, genre text, music_file text, primary key (track_id));
track_by_id
track text  
artist text  
track_id UUID PK
track_length_in_seconds int  
genre text  
music_file text  

Changes to the model 

The TracksDAO must be modified for the new starred column in the track tables. Add a new field of type Boolean:

private Boolean starred;

The constructors need to be modified. If the object is being retrieved from a row from one of the tracks table, check the value of the starred column to set the starred field in TracksDAO:

try {
  starred = row.getBool("starred");
} catch (Exception e) {
  starred = false;  // If the field doesn't exist or is null we set it to false
}

For a brand new instance of TracksDAO, set the starred field to false by default:

this.starred = false;

Now you'll add a method to set starred to true when a user selects it as a hot track. You want the track to be starred for only a minute, so you will use the USING TTL time in seconds CQL keyword to set the expiration time for the updated column. In this case, you will set the starred column to true for 60 seconds.

public void star() {

    PreparedStatement preparedStatement = getSession().prepare("UPDATE track_by_artist  USING TTL 60 SET starred = true where artist = ? and track = ? and track_id = ?");
    BoundStatement boundStatement = preparedStatement.bind(artist, track, track_id);
    getSession().execute(boundStatement);

    preparedStatement = getSession().prepare("UPDATE track_by_genre  USING TTL 60 SET starred = true where genre = ? and artist = ? and track = ? and track_id = ?");
    boundStatement = preparedStatement.bind(genre, artist, track, track_id);
    getSession().execute(boundStatement);

}

You need a new finder method for returning a track based on its ID. This finder method queries the new track_by_id table with specified track ID.

public static TracksDAO getTrackById(UUID track_id) {
  PreparedStatement preparedStatement = getSession().prepare("SELECT * FROM track_by_id WHERE track_id = ?");
  BoundStatement boundStatement = preparedStatement.bind(track_id);
  ResultSet resultSet = getSession().execute(boundStatement);

  // Return null if there is no track found

  if (resultSet.isExhausted()) {
    return null;
  }

  return new TracksDAO(resultSet.one());
}

Finally, the add method needs to be updated to add any new tracks to the track_by_id table. The code is almost identical to the code that inserts the track to the other track tables.

preparedStatement = getSession().prepare("INSERT INTO track_by_id (genre, track_id, artist, track, track_length_in_seconds) VALUES (?, ?, ?, ?, ?)");
boundStatement = preparedStatement.bind(this.genre, this.track_id, this.artist, this.track, this.track_length_in_seconds);
getSession().execute(boundStatement);

Changes to the controller 

The TrackServlet needs to be enhanced to work with starred tracks. Users will click on tracks that they want starred. In the doPost method, you extract out the star parameter from the request, and check if the user starred the track. If so, call TracksDAO.getTrackById() to retrieve the TracksDAO instance, then call the star() method on that instance so it will update the track in the Cassandra tables.

String star = request.getParameter("star");

if (star != null) {
  TracksDAO.getTrackById(UUID.fromString(star)).star();

  response.sendRedirect("tracks?howmany=" + howmany
          + (artist == null ? "" : "&artist=" + URLEncoder.encode(artist, "UTF-8"))
          + (genre  == null ? "" : "&genre="  + URLEncoder.encode(genre,  "UTF-8")));

}

Changes to the view 

The tracks.jsp file handles the view of the tracks. You need to add a button to each track so users can star the track, and display the correct image if the track is a hot track.

<table class="table">
    <c:forEach var="track" items="${tracks}">
        <tr>
            <c:set var="startype" value="${track.starred ? 'yellowstar.png' : 'emptystar.png'}"/>
            <c:if test="${empty frame}">
                <td class="field_start">
                    <button name="star" value="${track.track_id}"><img src="images/${startype}"/></button>
                </td>
            </c:if>
            <c:if test="${frame == 'true'}">
                <td class="field_plus"><input type="button" name="add" value="+" onclick="addTrack('${track.track_id}')"/></td>
            </c:if>
            <td class="field_track">${track.track}</td>
            <td class="field_genre">${track.genre}</td>
            <td class="field_sec">
                <fmt:formatNumber value="${track.track_length_in_seconds div 60}" minIntegerDigits="1" maxFractionDigits="0"/>:
                <fmt:formatNumber value="${track.track_length_in_seconds % 60}" minIntegerDigits="2"/>
            </td>
        </tr>
    </c:forEach>
</table>

The <c:set var="startype" value="${track.starred ? 'yellowstar.png' : 'emptystar.png'}"/> tag checks whether the track is a hot track, and if so, adds a yellow star image. If the track is not a hot track, the image is an empty star.

The <button name="star" value="${track.track_id}"><img src="images/${startype}"/></button> tags add the track ID to the request headers if the user presses the star button to make the track a hot track.

Changes from Step 2 

To see all code changes from the step2 branch, enter the following command in a terminal in the playlist directory:

git diff step2..step3