001package edu.pdx.cs410J.family;
002
003import java.io.File;
004import java.io.IOException;
005import java.io.PrintStream;
006import java.rmi.Naming;
007import java.rmi.RMISecurityManager;
008import java.rmi.RemoteException;
009import java.rmi.server.UnicastRemoteObject;
010import java.util.*;
011
012/**
013 * This class is a remote family tree whose contents are read from and
014 * saved to an XML file.  It extends <code>UnicastRemoteObject</code>
015 * because it is going to be bound into the RMI registry.
016 */
017@SuppressWarnings("serial")
018public class XmlRemoteFamilyTree extends UnicastRemoteObject 
019  implements RemoteFamilyTree {
020
021  /** The underlying family tree whose contents this remote family
022   * tree serves up. */
023  private transient FamilyTree tree;
024
025  /** The XML file that serves as the source of this family tree */
026  private transient File xmlFile;
027
028  /** The highest id in the family tree */
029  private int highestId;
030
031  /** Maps ids to their RemotePerson.  This way there is always a
032   * one-to-one correspondence between a Person and a RemotePerson */
033  private Map<Integer, RemotePerson> remotePersons = new TreeMap<Integer, RemotePerson>();
034
035  /** Maps a Long that represents the husband and wife to their
036   * RemoteMarriage */
037  private Map<Long, RemoteMarriage> remoteMarriages = new TreeMap<Long, RemoteMarriage>();
038
039  ///////////////////////  Constructors  ///////////////////////
040
041  /**
042   * Creates a new <code>XmlRemoteFamilyTree</code> that gets its data
043   * from a given XML file.
044   *
045   * @throws FamilyTreeException
046   *         A problem occurred while parsing the XML file
047   */
048  public XmlRemoteFamilyTree(File xmlFile) 
049    throws RemoteException, IOException, FamilyTreeException {
050    super();   // Register this object with RMI runtime
051
052    if (xmlFile.exists()) {
053      out.println("Reading Family Tree from " + xmlFile);
054      XmlParser parser = new XmlParser(xmlFile);
055      this.tree = parser.parse();
056
057      // Compute the highest id of any person
058      Iterator iter = this.tree.getPeople().iterator();
059      while (iter.hasNext()) {
060        Person person = (Person) iter.next();
061        if (person.getId() > this.highestId) {
062          this.highestId = person.getId();
063        }
064      }
065
066    } else {
067      this.tree = new FamilyTree();
068      this.highestId = 0;
069    }
070
071    this.xmlFile = xmlFile;
072  }
073
074  //////////////////////  Instance Methods  //////////////////////
075
076  /**
077   * Helper method that manages the cache of RemotePersons
078   */
079  private RemotePerson getRemotePerson(Person person) 
080    throws RemoteException {
081
082    if (person == null) {
083      return null;
084    }
085
086    int id = person.getId();
087
088    RemotePerson rPerson = 
089      (RemotePerson) this.remotePersons.get(new Integer(id));
090    if (rPerson == null) {
091      // Is there a person with that id
092      if (!this.tree.containsPerson(id)) {
093        return null;
094      }
095
096      rPerson = new RemotePersonImpl(this, person);
097      this.remotePersons.put(new Integer(person.getId()), rPerson);
098    }
099    
100    return rPerson;
101  }
102
103  public RemotePerson createPerson(Person.Gender gender) throws RemoteException {
104    Person person = new Person(++this.highestId, gender);
105    this.tree.addPerson(person);
106    return getRemotePerson(person);
107  }
108
109  public RemotePerson getPerson(int id) throws RemoteException {
110    return getRemotePerson(this.tree.getPerson(id));
111  }
112
113  public RemotePerson getPerson(String firstName, String lastName) 
114    throws RemoteException {
115    Person person = null;
116
117    Iterator people = this.tree.getPeople().iterator();
118    while (people.hasNext()) {
119      Person p = (Person) people.next();
120      if (p.getFirstName().equals(firstName) &&
121          p.getLastName().equals(lastName)) {
122        if (person == null) {
123          person = p;
124
125        } else {
126          String s = "Multiple people named \"" + firstName + " " +
127            lastName + " exist: " + p + " AND " + person;
128          throw new IllegalArgumentException(s);
129        }
130      }
131    }
132
133    if (person == null) {
134      return null;
135
136    } else {
137      return getPerson(person.getId());
138    }
139  }
140
141  public RemoteMarriage getMarriage(int husbandId, int wifeId) 
142    throws RemoteException {
143    
144    // Make sure that both the husband and the wife exist
145    if (!this.tree.containsPerson(husbandId)) {
146      String s = "Could not find person with id " + husbandId;
147      throw new IllegalArgumentException(s);
148
149    } else if (!this.tree.containsPerson(wifeId)) {
150      String s = "Could not find person with id " + wifeId;
151      throw new IllegalArgumentException(s);
152    }
153
154    Person husband = this.tree.getPerson(husbandId);
155    Iterator marriages = husband.getMarriages().iterator();
156    while (marriages.hasNext()) {
157      Marriage marriage = (Marriage) marriages.next();
158      if (marriage.getWife().getId() == wifeId) {
159        return getRemoteMarriage(marriage);
160      }
161    }
162
163    return null;
164  }
165
166  /**
167   * Helper method that maintains the map of RemoteMarriages
168   */
169  private RemoteMarriage getRemoteMarriage(Marriage marriage) 
170    throws RemoteException {
171
172    long key = (((long) marriage.getHusband().getId()) << 32) | 
173      ((long) marriage.getWife().getId());
174
175    RemoteMarriage rMarriage =
176      (RemoteMarriage) this.remoteMarriages.get(new Long(key));
177    if (rMarriage == null) {
178      rMarriage = new RemoteMarriageImpl(marriage);
179      this.remoteMarriages.put(new Long(key), rMarriage);
180    }
181
182    return rMarriage;
183  }
184
185  public RemoteMarriage createMarriage(int husbandId, int wifeId) 
186    throws RemoteException {
187
188    // Make sure that both the husband and the wife exist
189    if (!this.tree.containsPerson(husbandId)) {
190      String s = "Could not find person with id " + husbandId;
191      throw new IllegalArgumentException(s);
192
193    } else if (!this.tree.containsPerson(wifeId)) {
194      String s = "Could not find person with id " + wifeId;
195      throw new IllegalArgumentException(s);
196    }
197
198    Person husband = this.tree.getPerson(husbandId);
199    Person wife = this.tree.getPerson(wifeId);
200
201    Marriage marriage = new Marriage(husband, wife);
202    husband.addMarriage(marriage);
203    wife.addMarriage(marriage);
204    return getRemoteMarriage(marriage);
205  }
206
207  public Collection<RemotePerson> getLiving() throws RemoteException {
208    Collection<RemotePerson> living = new ArrayList<RemotePerson>();
209
210    Iterator people = this.tree.getPeople().iterator();
211    while (people.hasNext()) {
212      Person person = (Person) people.next();
213      if (person.getDateOfBirth() != null &&
214          person.getDateOfDeath() == null) {
215        living.add(getRemotePerson(person));
216      }
217    }
218
219    return living;
220  }
221
222  public Collection<RemotePerson> getLiving(Date date) throws RemoteException {
223    Collection<RemotePerson> alive = new ArrayList<RemotePerson>();
224
225    Iterator people = this.tree.getPeople().iterator();
226    while (people.hasNext()) {
227      Person person = (Person) people.next();
228      Date dob = person.getDateOfBirth();
229      Date dod = person.getDateOfDeath();
230
231      if (dob != null && date.before(dob)) {
232        continue;
233      }
234
235      if (dod != null && date.after(dod)) {
236        continue;
237      }
238
239      if (dob == null && dod == null) {
240        continue;
241      }
242
243      alive.add(getRemotePerson(person));
244    }
245
246    return alive;
247  } 
248
249  public void shutdown() throws IOException, RemoteException {
250    // Write the contents of this family tree back to the XML file
251    out.println("Writing family tree to " + this.xmlFile.getPath());
252    XmlDumper dumper = new XmlDumper(this.xmlFile.getPath());
253    dumper.dump(this.tree);
254
255    UnicastRemoteObject.unexportObject(this, false /* force */);
256  }
257
258  //////////////////////////  Main Program  //////////////////////////
259
260  private static PrintStream out = System.out;
261  private static PrintStream err = System.err;
262
263  /**
264   * Prints usage information about this program
265   */
266  private static void usage(String s) {
267    err.println("\n** " + s + "\n");
268    err.println("usage: java XmlRemoteFamilyTree [-start xmlFile " +
269                "| -stop] familyName host port");
270    err.println("");
271    err.println("This program creates a new XmlRemoteFamilyTree " +
272                "that reads its contents from a given XML file and " +
273                "binds it into the RMI registry.  If the XML file " +
274                "doesn't exist, a new one will be created");
275    System.exit(1);
276  }
277
278  public static void main(String[] args) {
279    String xmlFileName = null;
280    String familyName = null;
281    String host = null;
282    int port = -1;
283    String command = null;
284
285    for (int i = 0; i < args.length; i++) {
286      if (args[i].equals("-start")) {
287        if (++i >= args.length) {
288          usage("Missing XML file");
289        }
290        xmlFileName = args[i];
291
292        command = "START";
293
294      } else if (args[i].equals("-stop")) {
295        command = "STOP";
296
297      } else if (familyName == null) {
298        familyName = args[i];
299
300      } else if (host == null) {
301        host = args[i];
302
303      } else if (port == -1) {
304        try {
305          port = Integer.parseInt(args[i]);
306
307        } catch (NumberFormatException ex) {
308          usage("Invalid port: " + args[i]);
309        }
310
311      } else {
312        usage("Spurious command line: " + args[i]);
313      }
314    }
315
316    if (command == null) {
317      usage("Missing command");
318    }
319
320    if (familyName == null) {
321      usage("Missing family name");
322    }
323
324    if (host == null) {
325      usage("Missing host name");
326    }
327
328    if (port == -1) {
329      usage("Missing port number");
330    }
331
332    if (command.equals("START")) {
333      // Install an RMISecurityManager, if there is not a
334      // SecurityManager already installed
335      if (System.getSecurityManager() == null) {
336        System.setSecurityManager(new RMISecurityManager());
337      }
338
339      String name = "rmi://" + host + ":" + port + "/" + familyName;
340
341      try {
342        XmlRemoteFamilyTree tree =
343          new XmlRemoteFamilyTree(new File(xmlFileName));
344        Naming.rebind(name, tree);
345        out.println("Successfully bound XmlRemoteFamilyTree");
346
347      } catch (Exception ex) {
348        ex.printStackTrace(System.err);
349      }
350
351
352    } else if (command.equals("STOP")) {
353      // Install an RMISecurityManager, if there is not a
354      // SecurityManager already installed
355      if (System.getSecurityManager() == null) {
356        System.setSecurityManager(new RMISecurityManager());
357      }
358
359      String name = "rmi://" + host + ":" + port + "/" + familyName;
360
361      try {
362        RemoteFamilyTree tree = 
363          (RemoteFamilyTree) Naming.lookup(name);
364        tree.shutdown();
365        Naming.unbind(name);
366
367      } catch (Exception ex) {
368        ex.printStackTrace(err);
369      }
370      
371
372    } else {
373      String s = "Unknown command: " + command;
374      throw new IllegalStateException(s);
375    }
376  }
377
378}