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