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}