001package edu.pdx.cs410J.rmi; 002 003import java.rmi.AlreadyBoundException; 004import java.rmi.RemoteException; 005import java.rmi.registry.LocateRegistry; 006import java.rmi.registry.Registry; 007import java.rmi.server.UnicastRemoteObject; 008import java.util.*; 009import java.util.stream.Collectors; 010 011/** 012 * This class provides an implementation of the remote {@link 013 * MovieDatabase} interface. Note that this class does not extened 014 * {@link UnicastRemoteObject}. Therefore, we have to invoke {@link 015 * UnicastRemoteObject#exportObject} in the constructor. 016 */ 017public class MovieDatabaseImpl implements MovieDatabase { 018 019 /** A map that maps id's to their movies. This allows for a 020 * fast lookup of movies by their id. */ 021 private Map<Long, Movie> movies; 022 023 //////////////////////// Constructors ///////////////////////// 024 025 /** 026 * Creates a new <code>MovieDatabaseImpl</code>. 027 */ 028 public MovieDatabaseImpl() { 029 // Sort movies by their id, so the lookup is O(lg n) 030 this.movies = new TreeMap<>(Long::compareTo); 031 032 System.out.println("Starting Movie Database"); 033 } 034 035 /////////////////////// Remote Methods //////////////////////// 036 037 /** 038 * Creates a new <code>Movie</code> object on the server. It 039 * returns the id of the movie that was created. 040 * 041 * @param title 042 * The title of the movie 043 * @param year 044 * The year in which the movie was released 045 */ 046 @Override 047 public long createMovie(String title, int year) { 048 Movie movie = new Movie(title, year); 049 long id = movie.getId(); 050 this.movies.put(id, movie); 051 System.out.println("Created a new movie " + movie); 052 return id; 053 } 054 055 /** 056 * Returns the <code>Movie</code> with the given id. 057 */ 058 @Override 059 public Movie getMovie(long id) { 060 return this.movies.get(id); 061 } 062 063 /** 064 * Makes note of a character in a given movie played by a given 065 * actor. 066 * 067 * @throws IllegalArgumentException 068 * There is no movie with <code>movieId</code> or the 069 * character is already played by someone else 070 */ 071 @Override 072 public void noteCharacter(long movieId, String character, long actorId) { 073 Movie movie = getExistingMovie(movieId); 074 movie.addCharacter(character, actorId); 075 } 076 077 private Movie getExistingMovie(long movieId) { 078 // Note local call of remote method 079 Movie movie = this.getMovie(movieId); 080 if (movie == null) { 081 String s = "There is no movie with id " + movieId; 082 throw new IllegalArgumentException(s); 083 } 084 return movie; 085 } 086 087 /** 088 * Returns the movie in which a given actor acted. The movies are 089 * sorted by release date. 090 */ 091 @Override 092 public SortedSet<Movie> getFilmography(final long actorId) { 093 094 Query query = movie -> movie.getActors().contains(actorId); 095 096 Comparator<Movie> sorter = new SortMoviesByReleaseDate(); 097 098 return executeQuery(query, sorter); 099 } 100 101 /** 102 * A comparator that sorts movies based on the year in which they 103 * were released. It must be serializable so that it may be sent to 104 * the client. It must be static so that it doesn't have a 105 * reference to its outer class. 106 */ 107 static class SortMoviesByReleaseDate 108 implements Comparator<Movie>, java.io.Serializable { 109 110 @Override 111 public int compare(Movie movie1, Movie movie2) { 112 int year1 = movie1.getYear(); 113 int year2 = movie2.getYear(); 114 return Integer.compare(year1, year2); 115 } 116 } 117 118 /** 119 * Performs a query on the database. The movies that match the 120 * query are sorted using the given comparator. 121 */ 122 @Override 123 public SortedSet<Movie> executeQuery(Query query, Comparator<Movie> sorter) { 124 125 return this.movies.values().stream() 126 .filter(query::satisfies) 127 .collect(Collectors.toCollection(() -> new TreeSet<>(sorter))); 128 } 129 130 /** 131 * Unregisters this <code>MovieDatabaseImpl</code> with the RMI 132 * registry. 133 */ 134 @Override 135 public void shutdown() throws RemoteException { 136 System.out.println("Shutting down Movie Database"); 137 UnicastRemoteObject.unexportObject(this, false /* force */); 138 System.exit(0); 139 } 140 141 @Override 142 public Collection<Movie> getMovies() { 143 return new HashSet<>(this.movies.values()); 144 } 145 146 @Override 147 public void deleteMovie(long movieId) { 148 Movie movie = getExistingMovie(movieId); 149 this.movies.remove(movie.getId()); 150 } 151 152 /////////////////////// Main Program ///////////////////////// 153 154 /** 155 * This main program registers an instance of MovieDatabaseImpl in 156 * an RMI registry. 157 */ 158 public static void main(String[] args) { 159 int port = Integer.parseInt(args[0]); 160 161 try { 162 MovieDatabase database = (MovieDatabase) UnicastRemoteObject.exportObject(new MovieDatabaseImpl(), port); 163 Registry registry = LocateRegistry.createRegistry(port); 164 registry.bind(MovieDatabase.RMI_OBJECT_NAME, database); 165 166 } catch (RemoteException | AlreadyBoundException ex) { 167 ex.printStackTrace(System.err); 168 } 169 170 } 171 172}