Week 6
Movie Project DAO
Students will demonstrate an understanding multitier software design
Students will understand the use of standard collections and generics in the Java programming language
Students will understand elementary Thread programming in Java
Students will create software that performs standard data operations with Java and a relational database
Students will use streams to communicate with sockets and files
Students will apply elementary XML functionality in Java
Students will understand the importance of using standard Object Oriented Programming (OOP) design tools such as the Unified Modeling Language (UML)
Students will demonstrate a preference for messages and Exceptions for communicating between components
Students will systematize Test Driven Development
package edu.kirkwood.model;
import java.util.Objects;
// A simple Plain Old Java Object (POJO)
public class Movie implements Comparable<Movie> {
private String id;
private String title;
private int releaseYear;
public Movie() {
}
public Movie(String id, String title, int releaseYear) {
this.id = id;
this.title = title;
this.releaseYear = releaseYear;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getReleaseYear() {
return releaseYear;
}
public void setReleaseYear(int releaseYear) {
this.releaseYear = releaseYear;
}
@Override
public String toString() {
return "Movie{" +
"id='" + id + '\'' +
", title='" + title + '\'' +
", releaseYear=" + releaseYear +
'}';
}
/**
* Compares two Movie objects by their release year (0-9), then title (A-Z)
* @param o The other Movie being compared
* @return an integer, in order if less than or equal to 0, not in order if greater than 0
*/
@Override
public int compareTo(Movie o) {
int result = Integer.compare(this.releaseYear, o.getReleaseYear());
if(result == 0) {
result = this.title.compareTo(o.title);
}
return result;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Movie movie = (Movie) o;
return releaseYear == movie.releaseYear && Objects.equals(title, movie.title);
}
@Override
public int hashCode() {
return Objects.hash(title, releaseYear);
}
}<root totalResults="503" response="True">
<result title="Batman Begins" year="2005" imdbID="tt0372784" type="movie" poster="https://m.media-amazon.com/images/M/MV5BODIyMDdhNTgtNDlmOC00MjUxLWE2NDItODA5MTdkNzY3ZTdhXkEyXkFqcGc@._V1_SX300.jpg"/>
<result title="The Batman" year="2022" imdbID="tt1877830" type="movie" poster="https://m.media-amazon.com/images/M/MV5BMmU5NGJlMzAtMGNmOC00YjJjLTgyMzUtNjAyYmE4Njg5YWMyXkEyXkFqcGc@._V1_SX300.jpg"/>
<result title="Batman v Superman: Dawn of Justice" year="2016" imdbID="tt2975590" type="movie" poster="https://m.media-amazon.com/images/M/MV5BZTJkYjdmYjYtOGMyNC00ZGU1LThkY2ItYTc1OTVlMmE2YWY1XkEyXkFqcGc@._V1_SX300.jpg"/>
<result title="Batman v Superman: Dawn of Justice" year="2016" imdbID="tt2975590" type="movie" poster="https://m.media-amazon.com/images/M/MV5BZTJkYjdmYjYtOGMyNC00ZGU1LThkY2ItYTc1OTVlMmE2YWY1XkEyXkFqcGc@._V1_SX300.jpg"/>
<result title="Batman" year="1989" imdbID="tt0096895" type="movie" poster="https://m.media-amazon.com/images/M/MV5BYzZmZWViM2EtNzhlMi00NzBlLWE0MWEtZDFjMjk3YjIyNTBhXkEyXkFqcGc@._V1_SX300.jpg"/>
<result title="Batman Returns" year="1992" imdbID="tt0103776" type="movie" poster="https://m.media-amazon.com/images/M/MV5BZTliMDVkYTktZDdlMS00NTAwLWJhNzYtMWIwMDZjN2ViMGFiXkEyXkFqcGc@._V1_SX300.jpg"/>
<result title="Batman & Robin" year="1997" imdbID="tt0118688" type="movie" poster="https://m.media-amazon.com/images/M/MV5BYzU3ZjE3M2UtM2E4Ni00MDI5LTkyZGUtOTFkMGIyYjNjZGU3XkEyXkFqcGc@._V1_SX300.jpg"/>
<result title="Batman Forever" year="1995" imdbID="tt0112462" type="movie" poster="https://m.media-amazon.com/images/M/MV5BMTUyNjJhZWItMTZkNS00NDc4LTllNjUtYTg3NjczMzA5ZTViXkEyXkFqcGc@._V1_SX300.jpg"/>
<result title="The Lego Batman Movie" year="2017" imdbID="tt4116284" type="movie" poster="https://m.media-amazon.com/images/M/MV5BMTcyNTEyOTY0M15BMl5BanBnXkFtZTgwOTAyNzU3MDI@._V1_SX300.jpg"/>
<result title="Batman v Superman: Dawn of Justice (Ultimate Edition)" year="2016" imdbID="tt18689424" type="movie" poster="https://m.media-amazon.com/images/M/MV5BOTRlNWQwM2ItNjkyZC00MGI3LThkYjktZmE5N2FlMzcyNTIyXkEyXkFqcGdeQXVyMTEyNzgwMDUw._V1_SX300.jpg"/>
</root><dependencies>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>4.0.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.20.0</version>
</dependency>
</dependencies>package edu.kirkwood.model.xml;
import jakarta.xml.bind.annotation.*; // For JAXB
// import com.fasterxml.jackson.data.format.xml.annotation.*; // For Jackson
@XmlAccessorType(XmlAccessType.FIELD) // Tells JAXB to use fields directly
public class MovieSearchResult {
@XmlAttribute(name = "title")
private String title;
@XmlAttribute(name = "year")
private String year;
@XmlAttribute(name = "imdbID")
private String imdbID;
@XmlAttribute(name = "type")
private String type;
@XmlAttribute(name = "poster")
private String poster;
public String getTitle() {
return title;
}
public String getYear() {
return year;
}
public String getImdbID() {
return imdbID;
}
public String getType() {
return type;
}
public String getPoster() {
return poster;
}
@Override
public String toString() {
return "MovieSearchResult{" +
"title='" + title + '\'' +
", year='" + year + '\'' +
", imdbID='" + imdbID + '\'' +
", type='" + type + '\'' +
", poster='" + poster + '\'' +
'}';
}
}package edu.kirkwood.model.xml;
import jakarta.xml.bind.annotation.*; // For JAXB
// import com.fasterxml.jackson.data.format.xml.annotation.*; // For Jackson
import java.util.List;
@XmlRootElement(name = "root")
@XmlAccessorType(XmlAccessType.FIELD) // Tells JAXB to use fields directly
public class OmdbMovieResponse {
@XmlElement(name = "result")
private List<MovieSearchResult> searchResults;
@XmlAttribute(name = "totalResults")
private int totalResults;
@XmlAttribute(name = "response")
private String response;
public List<MovieSearchResult> getSearchResults() {
return searchResults;
}
public int getTotalResults() {
return totalResults;
}
public String getResponse() {
return response;
}
}package edu.kirkwood.dao;
import edu.kirkwood.model.Movie;
import java.util.List;
import java.util.Optional;
/**
* The data access interface for movies.
* This contract defines the operations for any data source.
*/
public interface MovieDAO {
/**
* Finds a single movie by its unique ID.
* @param id The ID of the movie to find.
* @return The movie if found, otherwise empty.
*/
Movie findById(String id);
/**
* Retrieves all movies from the data source that match the title.
* @param title
* @return A list of all movies.
*/
List<Movie> search(String title);
}package edu.kirkwood.dao.impl;
import edu.kirkwood.dao.MovieDAO;
import edu.kirkwood.model.Movie;
import java.util.List;
import java.util.Optional;
public class XmlMovieDAO implements MovieDAO {
private final String url;
public XmlMovieDAO(String url) {
this.url = url;
}
/**
* Logic to parse the XML file and return the movie matching the ID.
* @param id The ID of the movie to find.
* @return The movie if found, otherwise empty.
*/
@Override
public Movie findById(String id) {
System.out.println("Fetching movie " + id + " from XML...");
return null;
}
/**
* Logic to parse the entire XML file and return a list of all movies.
* @param title
* @return A list of all movies.
*/
@Override
public List<Movie> search(String title) {
System.out.println("Searching for " + title + " from XML...");
return List.of();
}
}package edu.kirkwood.dao;
import edu.kirkwood.model.xml.MovieSearchResult;
import java.util.List;
/**
* The data access interface for movies.
* This contract defines the operations for any data source.
*/
public interface MovieDAO<T> {
/**
* Retrieves all movies from the data source that match the title.
* @param title
* @return A list of all movies.
*/
List<MovieSearchResult> search(String title);
}package edu.kirkwood.dao.impl;
import edu.kirkwood.dao.MovieDAO;
import edu.kirkwood.model.xml.MovieSearchResult;
import edu.kirkwood.model.xml.OmdbMovieResponse;
import jakarta.xml.bind.JAXBContext; // JAXB
import jakarta.xml.bind.Unmarshaller; // JAXB
import java.io.StringReader;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
public class XmlMovieDAO implements MovieDAO {
private String apiURL = null;
public XmlMovieDAO(String apiURL) {
this.apiURL = apiURL;
}
}package edu.kirkwood.dao.impl;
import edu.kirkwood.dao.MovieDAO;
import edu.kirkwood.model.xml.MovieSearchResult;
import edu.kirkwood.model.xml.OmdbMovieResponse;
import jakarta.xml.bind.JAXBContext; // JAXB
import jakarta.xml.bind.Unmarshaller; // JAXB
import java.io.StringReader;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
public class XmlMovieDAO implements MovieDAO {
private String apiURL = null;
public XmlMovieDAO(String apiURL) {
this.apiURL = apiURL;
}
/**
* Finds movies by a search term.
* @param searchTerm The movie title to search
* @return List<MovieSearchResult> the list of movies
*/
public List<MovieSearchResult> search(String searchTerm) {
if(apiURL == null) {
throw new IllegalArgumentException("XML API URL is not configured in application.properties");
}
// URL-encode the search term to handle spaces and special characters
String encodedSearchTerm = URLEncoder.encode(searchTerm, StandardCharsets.UTF_8);
apiURL = String.format("%s&s=%s&page=1", apiURL, encodedSearchTerm);
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(apiURL))
.build();
HttpClient httpClient = HttpClient.newHttpClient();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
return Collections.emptyList();
} catch (Exception e) {
e.printStackTrace();
}
return Collections.emptyList();
}
}package edu.kirkwood.dao.impl;
import edu.kirkwood.dao.MovieDAO;
import edu.kirkwood.model.xml.MovieSearchResult;
import edu.kirkwood.model.xml.OmdbMovieResponse;
import jakarta.xml.bind.JAXBContext; // JAXB
import jakarta.xml.bind.Unmarshaller; // JAXB
import java.io.StringReader;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
public class XmlMovieDAO implements MovieDAO {
private String apiURL = null;
public XmlMovieDAO(String apiURL) {
this.apiURL = apiURL;
}
/**
* Finds movies by a search term.
* @param searchTerm The movie title to search
* @return List<MovieSearchResult> the list of movies
*/
public List<MovieSearchResult> search(String searchTerm) {
if(apiURL == null) {
throw new IllegalArgumentException("XML API URL is not configured in application.properties");
}
// URL-encode the search term to handle spaces and special characters
String encodedSearchTerm = URLEncoder.encode(searchTerm, StandardCharsets.UTF_8);
apiURL = String.format("%s&s=%s&page=1", apiURL, encodedSearchTerm);
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(apiURL))
.build();
HttpClient httpClient = HttpClient.newHttpClient();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
return parseXml(response.body());
} catch (Exception e) {
e.printStackTrace();
}
return Collections.emptyList();
}
/**
* Logic to parse the entire XML file and return a list of all movies.
* @param xml The raw XML data
* @return List<MovieSearchResult> the list of movies
*/
private List<MovieSearchResult> parseXml(String xml) throws Exception {
JAXBContext context = JAXBContext.newInstance(OmdbMovieResponse.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
StringReader reader = new StringReader(xml);
OmdbMovieResponse movieResponse = (OmdbMovieResponse) unmarshaller.unmarshal(reader);
return movieResponse.getSearchResults() != null ? movieResponse.getSearchResults() : Collections.emptyList();
}
}src/main/resources/application.properties)Set the desired data source. (XML, JSON, MYSQL, MONGODB), the API key, and parser preference (JAXB, JACKSON)
My API key is 359a6530 in case you don't want to sign up for a key.
datasource.type=XML
xml.apiURL=https://www.omdbapi.com/?r=xml&type=movie&apikey=<your-api-key>props.
.properties file.package edu.kirkwood.dao;
import edu.kirkwood.dao.impl.*;
import java.io.InputStream;
import java.util.Properties;
public class MovieDAOFactory {
private static final Properties props = new Properties();
}A static { ... } block is a code block that only runs only once, the very first time the class is loaded.
This example finds the application.properties file and initializes the static props variable.
InputStream is AutoCloseable, so it can be written in a try-with-resources block.
The getClassLoader() method returns the class loader. The ClassLoader has a method called getResourceAsStream().
package edu.kirkwood.dao;
import edu.kirkwood.dao.impl.*;
import java.io.InputStream;
import java.util.Properties;
public class MovieDAOFactory {
private static final Properties props = new Properties();
static {
try (InputStream input = MovieDAOFactory.class.getClassLoader()
.getResourceAsStream("application.properties")) {
} catch (Exception ex) {
ex.printStackTrace();
}
}
}If the InputStream is null, that means the properties file cannot be found.
If the properties file is found, we pass the InputStream object to the load() method of the Properties class.
package edu.kirkwood.dao;
import edu.kirkwood.dao.impl.*;
import java.io.InputStream;
import java.util.Properties;
public class MovieDAOFactory {
private static final Properties props = new Properties();
static {
try (InputStream input = MovieDAOFactory.class.getClassLoader()
.getResourceAsStream("application.properties")) {
if (input == null) {
System.out.println("Unable to find application.properties");
}
props.load(input);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}package edu.kirkwood.dao;
import edu.kirkwood.dao.impl.*;
import java.io.InputStream;
import java.util.Properties;
public class MovieDAOFactory {
private static final Properties props = new Properties();
static {
try (InputStream input = MovieDAOFactory.class.getClassLoader().getResourceAsStream("application.properties")) {
if (input == null) {
System.out.println("Unable to find application.properties");
}
props.load(input);
} catch (Exception ex) {
ex.printStackTrace();
}
}
public static MovieDAO getMovieDAO() {
String sourceType = props.getProperty("datasource.type");
switch (sourceType.toUpperCase()) {
case "XML":
break;
// other cases for JSON, MYSQL, MongoDB, etc.
default:
throw new IllegalArgumentException("Invalid data source type specified: " + sourceType);
}
}
}package edu.kirkwood.dao;
import edu.kirkwood.dao.impl.*;
import java.io.InputStream;
import java.util.Properties;
public class MovieDAOFactory {
private static final Properties props = new Properties();
static {
try (InputStream input = MovieDAOFactory.class.getClassLoader().getResourceAsStream("application.properties")) {
if (input == null) {
System.out.println("Unable to find application.properties");
}
props.load(input);
} catch (Exception ex) {
ex.printStackTrace();
}
}
public static MovieDAO getMovieDAO() {
String sourceType = props.getProperty("datasource.type");
switch (sourceType.toUpperCase()) {
case "XML":
String apiURL = props.getProperty("xml.apiURL");
if (apiURL == null) {
throw new IllegalArgumentException("XML API URL is not configured in application.properties");
}
return new XmlMovieDAO(apiURL);
// other cases for JSON, MYSQL, MongoDB, etc.
default:
throw new IllegalArgumentException("Invalid data source type specified: " + sourceType);
}
}
}package edu.kirkwood;
import edu.kirkwood.dao.MovieDAO;
import edu.kirkwood.dao.MovieDAOFactory;
import edu.kirkwood.dao.impl.MovieDAOXml;
import edu.kirkwood.model.xml.MovieSearchResult;
import java.util.List;
public class MovieApp {
public static void main(String[] args) {
MovieDAO movieDAO = MovieDAOFactory.getMovieDAO();
String search = "Batman";
if (movieDAO instanceof XmlMovieDAO) {
XmlMovieDAO xmlMovieDao = (XmlMovieDAO) movieDAO;
List<MovieSearchResult> results = xmlMovieDao.search(search);
System.out.println("Found " + results.size() + " results for '" + search + "':");
results.forEach(System.out::println);
}
}
}