package edu.byu.cs.roots.opg.nfs;
import java.io.IOException;
import java.io.StringReader;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.familysearch.ws.client.familytree.v2.schema.EventAssertion;
import org.familysearch.ws.client.familytree.v2.schema.EventValue;
import org.familysearch.ws.client.familytree.v2.schema.FamilyReference;
import org.familysearch.ws.client.familytree.v2.schema.FamilyTree;
import org.familysearch.ws.client.familytree.v2.schema.GenderAssertion;
import org.familysearch.ws.client.familytree.v2.schema.GenderType;
import org.familysearch.ws.client.familytree.v2.schema.NameAssertion;
import org.familysearch.ws.client.familytree.v2.schema.NameForm;
import org.familysearch.ws.client.familytree.v2.schema.NamePiece;
import org.familysearch.ws.client.familytree.v2.schema.NamePieceType;
import org.familysearch.ws.client.familytree.v2.schema.NameType;
import org.familysearch.ws.client.familytree.v2.schema.OrdinanceAssertion;
import org.familysearch.ws.client.familytree.v2.schema.OrdinanceType;
import org.familysearch.ws.client.familytree.v2.schema.ParentReference;
import org.familysearch.ws.client.familytree.v2.schema.ParentsReference;
import org.familysearch.ws.client.familytree.v2.schema.Person;
import org.familysearch.ws.client.familytree.v2.schema.PersonReference;
import edu.byu.cs.roots.opg.io.GedcomRecord;
import edu.byu.cs.roots.opg.model.Event;
import edu.byu.cs.roots.opg.model.EventType;
import edu.byu.cs.roots.opg.model.Family;
import edu.byu.cs.roots.opg.model.Gender;
import edu.byu.cs.roots.opg.model.Individual;
public class GetIndividualInfoThread extends Thread{
private String baseURL, ID;
private GedcomRecord gedRecord;
private RelationMap relMap;
private int choiceGens;
private boolean choiceAllDescendants, choiceRootDescendants;
private NFSDownloadThread parent;
public GetIndividualInfoThread(NFSDownloadThread parent, GedcomRecord gedRecord, RelationMap relMap, String baseURL, String ID, int choiceGens, boolean choiceAllDescendants, boolean choiceRootDescendants){
this.setDaemon(true);
this.baseURL = baseURL;
this.ID = ID;
this.gedRecord = gedRecord;
this.relMap = relMap;
this.choiceGens = choiceGens;
this.choiceAllDescendants = choiceAllDescendants;
this.choiceRootDescendants = choiceRootDescendants;
this.parent = parent;
}
public void run(){
this.setName("Get Info: " + ID);
String requestUrl = baseURL+"/familytree/v2/person/";
requestUrl += ID;
//request all the information about the person
requestUrl += "?personas=mine&names=summary&events=summary&ordinances=all&families=summary&parents=summary&children=all";
parent.updatePublished("Getting information for: " + ID);
System.out.println("Getting information for: " + ID);
BasicResponseHandler responseHandler = new BasicResponseHandler();
String responseBody;
FamilyTree extract = null;
try {
responseBody = parent.httpclient.execute(new HttpGet(requestUrl), responseHandler);
extract = (FamilyTree) JAXBContext.newInstance(FamilyTree.class).createUnmarshaller().unmarshal(new StringReader(responseBody));
} catch (HttpResponseException e) {
if (e.getStatusCode() == 503){
//Throttled
parent.updatePublished("Throttled!");
}
e.printStackTrace();
} catch (JAXBException e){
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
String[] IDS = ID.split(",");
int IDcount = 0;
for (Person person: extract.getPersons()) {
Individual indiNew;
//change ID so it is the correct one if making multiple calls!
if(!gedRecord.containsIndividual(IDS[IDcount])) {
indiNew = new Individual(IDS[IDcount]);
gedRecord.addIndividual(indiNew.id, indiNew);
} else
indiNew = gedRecord.getIndividual(IDS[IDcount]);
indiNew.version = person.getVersion();
indiNew = getNameValues(indiNew, person);
indiNew = getGender(indiNew, person);
indiNew = getEventValues(indiNew, person);
indiNew = getFamilyValues(indiNew, person);
indiNew = getParentValues(indiNew, person);
indiNew = getOrdinanceValues(indiNew, person);
indiNew = assignIndiAsParent(indiNew, person);
//by this time they should already be in the relMap!!
/*if (!relMap.contains(indiNew.id))
System.out.println(indiNew.id + " IS NOT IN THE RELMAP!");
else*/
relMap.nowCompleted(indiNew.id);
IDcount++;
parent.updatePublished("Processed: " + indiNew.givenName + " (" + indiNew.id + ")");
System.out.println("Processed: " + indiNew.givenName + " (" + indiNew.id + ")");
}
}
/**
* Iterates through the name values and assigns them to the correct spots.
* because new FamilySearch does not use middle names, no middle names are assigned,
* and most of the name is stored in given names.
*/
private Individual getNameValues(Individual indi, Person p) {
if (p.getAssertions().getNames() == null)
return indi;
indi = initializeNames(indi);
for (NameAssertion n: p.getAssertions().getNames()) {
if(n.getValue().getType() == NameType.Name) {
for (NameForm name: n.getValue().getForms()) {
String given = "";
String surname = "";
for (NamePiece na: name.getPieces()) {
if (na.getType() == NamePieceType.Given)
given += na.getValue() + " ";
else if (na.getType() == NamePieceType.Family)
surname += na.getValue() + " ";
else if (na.getType() == NamePieceType.Prefix)
indi.namePrefix = na.getValue();
else if (na.getType() == NamePieceType.Suffix)
indi.nameSuffix = na.getValue();
}
given = given.trim();
indi.givenName = given;
surname = surname.trim();
indi.surname = surname;
}
}
}
return indi;
}
/**
* goes through each name string and initializes them to zero. This makes sure that
* there are no null pointer exceptions when the program later tries to use the informaiton
* @param indi
* @return
*/
private Individual initializeNames(Individual indi) {
indi.givenName = "";
indi.middleName = "";
indi.surname = "";
indi.nameSuffix = "";
indi.surnamePrefix = "";
indi.namePrefix = "";
indi.nameSuffix = "";
return indi;
}
/**
* gets the gender of the individual. if there are multiple genders, then it will return
* the first one.
* @param indi
* @param p
* @return
*/
private Individual getGender(Individual indi, Person p) {
if (p.getAssertions().getGenders() == null)
return indi;
GenderAssertion gen = p.getAssertions().getGenders().get(0);
if (gen.getValue().getType() == GenderType.Male)
indi.gender = Gender.MALE;
else if (gen.getValue().getType() == GenderType.Female)
indi.gender = Gender.FEMALE;
else
indi.gender = Gender.UNKNOWN;
return indi;
}
/**
* gets the birth and death dates and places of the individual.
* The informaiton is left null if there is nothing stored in new FamilySearch
* @param indi
* @param p
* @return
*/
private Individual getEventValues(Individual indi, Person p) {
if (p.getAssertions().getEvents() == null)
return indi;
for (EventAssertion event: p.getAssertions().getEvents()) {
Event ev = null;
EventValue evalue = event.getValue();
if (evalue.getType() == org.familysearch.ws.client.familytree.v2.schema.EventType.Birth) {
ev = new Event(EventType.BIRTH);
indi.birth = ev;
}
else if (evalue.getType() == org.familysearch.ws.client.familytree.v2.schema.EventType.Death) {
ev = new Event(EventType.DEATH);
indi.death = ev;
}
if (ev != null) {
if (evalue.getDate() != null)
ev.date = evalue.getDate().getNormalized();
if (evalue.getPlace() != null && evalue.getPlace().getNormalized() != null)
ev.place = evalue.getPlace().getNormalized().getValue();
}
}
if (indi.birth != null)
indi.birth.parseDateParts();
if (indi.death != null)
indi.death.parseDateParts();
return indi;
}
/**
* Creates the Family for the person if their spouse has not already established the family.
* stores the children's ids (but doesn't store them in the RelativeMap, we don't want to get
* all the children) and also gets the spouse's id (creates a new individual if it is not already
* created) and the marriage information (date and place).
*
* if this is the main person, add the children into the RelationMap.
* @param indi
* @param p
* @return
*/
private Individual getFamilyValues(Individual indi, Person p) {
for (FamilyReference fam: p.getFamilies()) {
/* this makes sure that only the first spouse of the family creates a new Family
* the other parent is added into the Family in the GedcomRecord. If the second
* spouse tries to create the family first, the method determines if the first
* spouse is in the relMap or not, and then adds them if it needs to.
*/
if (!(indi.gender == Gender.MALE && fam.getParents().get(0).getGender() == GenderType.Male)
&& !(indi.gender == Gender.FEMALE && fam.getParents().get(0).getGender() == GenderType.Female)) {
if (!relMap.contains(fam.getParents().get(0).getId()))
relMap.add(fam.getParents().get(0).getId(), relMap.getGeneration(indi.id), false);
return indi;
}
String famID;
int localFamCount = parent.getFamCount();
if (localFamCount < 10)
famID = "0F0" + localFamCount;
else if (localFamCount > 99) {
int beg = 0;
int end = localFamCount;
while(end > 99){
beg++;
end = end - 100;
}
String middle = "F";
if (end < 10)
middle = "F0";
famID = beg + middle + end;
}
else
famID = "0F" + localFamCount;
Family newFamily = new Family(famID);
parent.incFamCount();
indi.famsIds.add(famID);
if (fam.getChildren() != null) {
for (PersonReference child : fam.getChildren()) {
String childId = child.getId();
if (relMap.contains(childId))
{
System.out.println("Adding "+indi.id+" as parent to "+childId);
relMap.add(indi.id, relMap.getGeneration(childId) + 1, false);
}
//Here's where descendants get downloaded
else if (relMap.contains(indi.id))
{
if (choiceAllDescendants){
relMap.add(childId, relMap.getGeneration(indi.id), false);
}
else if (choiceRootDescendants){
if (relMap.isRootDescendant(indi.id))
{
relMap.add(childId, relMap.getGeneration(indi.id), false);
relMap.addRootDescendant(childId);
}
}
}
newFamily.childrenXRefIds.add(childId);
indi.famcIds.add(childId);
}
}
for(PersonReference parent: fam.getParents()) {
//if the spouse is not in the relMap, add them at the same generation level
if (!indi.id.equals(parent.getId()) && !relMap.contains(parent.getId())) {
System.out.println("Adding "+parent.getId()+" as spouse");
relMap.add(parent.getId(), relMap.getGeneration(indi.id), false);
}
if (parent.getGender() == GenderType.Male && relMap.getGeneration(indi.id) < choiceGens) {
newFamily.husbandId = parent.getId();
if (gedRecord.getIndividuals().containsKey(parent.getId()))
newFamily.husband = gedRecord.getIndividuals().get(parent.getId());
else {
gedRecord.addIndividual(parent.getId(), new Individual(parent.getId()));
newFamily.husband = gedRecord.getIndividual(parent.getId());
}
} else if (relMap.getGeneration(indi.id) < choiceGens){
newFamily.wifeId = parent.getId();
if (gedRecord.getIndividuals().containsKey(parent.getId()))
newFamily.wife = gedRecord.getIndividuals().get(parent.getId());
else {
gedRecord.addIndividual (parent.getId(), new Individual(parent.getId()));
newFamily.wife = gedRecord.getIndividual(parent.getId());
}
}
}
if (fam.getMarriage() != null) {
EventValue evalue = fam.getMarriage().getValue();
if (evalue.getType() == org.familysearch.ws.client.familytree.v2.schema.EventType.Marriage) {
Event marr = new Event(EventType.MARRIAGE);
if (evalue.getDate() != null)
marr.date = evalue.getDate().getNormalized();
if (evalue.getPlace() != null && evalue.getPlace().getNormalized() != null)
marr.place = evalue.getPlace().getNormalized().getValue();
newFamily.marriage = marr;
}
//currently, i do not know how to get the divorce. Technically, i could just get ALL
//the events, however, if a person has a lot of events on their pedigree, this could
//slow down the download EXTREMELY! But maybe at some point they will make the information
//readily available and easy for us to grab. Till then, we will not show it!
else if (evalue.getType() == org.familysearch.ws.client.familytree.v2.schema.EventType.Divorce)
newFamily.divorce = true;
}
if (indi.fams.size() > 0) {
for (Family famCheck : indi.fams){
if (((newFamily.husband != null && famCheck.husband != null && newFamily.husband.id.equals(famCheck.husband.id)) ||
(newFamily.husband == null && famCheck.husband == null)) &&
((newFamily.wife != null && famCheck.wife != null &&newFamily.wife.id.equals(famCheck.wife.id)) ||
(newFamily.wife == null && famCheck.wife == null))){
parent.decFamCount();
return indi;
}
}
}
gedRecord.addFamily(newFamily.id, newFamily);
indi.fams.add(newFamily);
}
return indi;
}
/**
* does not store information about the ordinance, only if they have been performed/completed
* yet. The ordinances it cares about is Baptism, Endowment, Sealing to Spouse, and Sealing
* to Parents.
* this method is done after the getFamilyValues to make sure the individual already has a family
* value created.
* @param indi
* @param p
* @return
*/
//TODO because the new.familysearch will not let me put fake ordinance in it's devnet site, there
//is no way to test this and make sure they are correct. Because of this, we will need to this this
//first thing when we can run it on the actual site. VERY important that we get this going together!
private Individual getOrdinanceValues(Individual indi, Person p) {
if (p.getAssertions().getOrdinances() == null)
return indi;
for (OrdinanceAssertion ordinance: p.getAssertions().getOrdinances()) {
if (ordinance.getValue().getType() == OrdinanceType.Baptism) {
indi.baptism = true;
indi.baptismComplete = true;
}
else if (ordinance.getValue().getType() == OrdinanceType.Born_in_Covenant
|| ordinance.getValue().getType() == OrdinanceType.Sealing_to_Parents) {
indi.sealingToParents = true;
indi.sealingToParentsComplete = true;
}
else if (ordinance.getValue().getType() == OrdinanceType.Endowment) {
indi.endowment = true;
indi.endowmentComplete = true;
}
else if (ordinance.getValue().getType() == OrdinanceType.Sealing_to_Spouse) {
indi.sealingToSpouse = true;
indi.sealingToSpouseComplete = true;
for (int i = 0; i < indi.fams.size(); i++) {
Family parentID = indi.fams.get(i);
if (parentID.husbandId == indi.id || parentID.wifeId == indi.id) {
parentID.sealing = true;
parentID.sealingComplete = true;
}
}
}
/*where do we get the information if the marriage was annuled?
we also need a boolean, so if this comes before the sealing,
it won't be marked as true
else if (ordinance.getValue().getType() == OrdinanceType.Marriage_Annuled) {
indi.sealingToSpouse = false;
indi.sealingToSpouseComplete = false;
for (int i = 0; i < indi.fams.size(); i++) {
Family parentID = indi.fams.get(i);
if (parentID.husbandId == indi.id || parentID.wifeId == indi.id) {
parentID.sealing = false;
parentID.sealingComplete = false;
//is this needed? (could not be...tricky to say when i cannot exactly text this)
for (int j = 0; j < parentID.children.size(); j++) {
Individual child = parentID.children.get(j);
child.sealingToParents = false;
child.sealingToParents.complete = false;
}
}
}
}*/
}
return indi;
}
/**
* Stores/creates individuals for the person's parents.
* @param indi
* @param p
* @return
*/
private Individual getParentValues(Individual indi, Person p) {
if (p.getParents() == null)
return indi;
for (ParentsReference parents: p.getParents()) {
for (ParentReference parent: parents.getParents()) {
//check to see if the parents are done
if (relMap.contains(parent.getId()) && !relMap.contains(indi.id))
relMap.add(indi.id, relMap.getGeneration(parent.getId())-1, false);
//adds the parent into relMap
if (!relMap.contains(parent.getId()) && relMap.getGeneration(indi.id) < choiceGens)
{
System.out.println("Adding "+parent.getId()+" as parent again");
relMap.add(parent.getId(), relMap.getGeneration(indi.id)+1, false);
}
if(relMap.getGeneration(indi.id) < choiceGens){
if (parent.getGender() == GenderType.Male && !gedRecord.containsIndividual(parent.getId())) {
gedRecord.addIndividual(parent.getId(), new Individual(parent.getId()));
indi.father = gedRecord.getIndividual(parent.getId());
}
else if (parent.getGender() == GenderType.Male && gedRecord.containsIndividual(parent.getId()))
indi.father = gedRecord.getIndividual(parent.getId());
else if (parent.getGender() == GenderType.Female && !gedRecord.containsIndividual(parent.getId())) {
gedRecord.addIndividual(parent.getId(), new Individual(parent.getId()));
indi.mother = gedRecord.getIndividual (parent.getId());
}
else if (parent.getGender() == GenderType.Female && gedRecord.containsIndividual(parent.getId()))
indi.mother = gedRecord.getIndividual(parent.getId());
}
//instead of relying on getUserPedigree(), this will automatically add the parents
//into the waitingQueue, which will mean less calls for pedigree's.
// if (!(relMap.contains(parent.getId()) && relMap.isCompleted(parent.getId()))) {
// if (!waitingQueue.contains(parent.getId()) && relMap.getGeneration(indi.id) < options.getChoiceGens()){
// waitingQueue.add(parent.getId());
// System.out.println("Adding: " +parent.getId() + " as parent");
// }
//
// }
//else if (relMap.getGeneration(indi.id) >= MAX_GENS && !afterNine)
//afterNineQueue.add(parent.getId());
}
}
return indi;
}
private Individual assignIndiAsParent(Individual indi, Person p){
for(FamilyReference fam : p.getFamilies()){
for(PersonReference child : fam.getChildren()){
if(gedRecord.containsIndividual(child.getId())){
if(indi.gender == Gender.MALE){
gedRecord.getIndividual(child.getId()).father = indi;
}
else if(indi.gender == Gender.FEMALE){
gedRecord.getIndividual(child.getId()).mother = indi;
}
}
}
}
return indi;
}
}