Package ke.go.moh.oec.mpi.list

Source Code of ke.go.moh.oec.mpi.list.PersonList

/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is OpenEMRConnect.
*
* The Initial Developer of the Original Code is International Training &
* Education Center for Health (I-TECH) <http://www.go2itech.org/>
*
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(sql):
*
* ***** END LICENSE BLOCK ***** */
package ke.go.moh.oec.mpi.list;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import ke.go.moh.oec.Fingerprint;
import ke.go.moh.oec.Person;
import ke.go.moh.oec.PersonIdentifier;
import ke.go.moh.oec.PersonRequest;
import ke.go.moh.oec.PersonResponse;
import ke.go.moh.oec.lib.Mediator;
import ke.go.moh.oec.mpi.CandidateSet;
import ke.go.moh.oec.mpi.match.DateMatch;
import ke.go.moh.oec.mpi.FindPersonThread;
import ke.go.moh.oec.mpi.LoadPersonThread;
import ke.go.moh.oec.mpi.Mpi;
import ke.go.moh.oec.mpi.Notifier;
import ke.go.moh.oec.mpi.match.PersonMatch;
import ke.go.moh.oec.mpi.Scorecard;
import ke.go.moh.oec.mpi.SearchHistory;
import ke.go.moh.oec.mpi.SiteCandidate;
import ke.go.moh.oec.mpi.Sql;
import ke.go.moh.oec.mpi.ValueMap;

/**
* Holds in memory the information about all persons in the MPI.
* <p>
* The persons are stored in memory in the following ways:
* <p>
* 1. in an ArrayList, so they can be referenced by index. This is required
* for iterating through them to find candidates entries that match search terms.
* <p>
* 2. in a HashMap by person GUID. This allows for fast finding in
* memory a person based on their GUID.
* <p>
* 3. in a HashMap by HDSS ID (if any). This allows for a quick check to see
* if a HDSS ID already exists in the list before adding a new person with
* a HDSS ID.
*
* @author Jim Grace
*/
public class PersonList {

    private List<PersonMatch> personList = new ArrayList<PersonMatch>();
    private Map<String, PersonMatch> personMap = new HashMap<String, PersonMatch>();
    private Map<String, PersonMatch> hdssIdMap = new HashMap<String, PersonMatch>();
    private SiteList siteList;

    public void setSiteList(SiteList siteList) {
        this.siteList = siteList;
    }

    /**
     * Adds a person to our in-memory person list.
     *
     * @param personMatch the person to be added.
     */
    private void add(PersonMatch personMatch) {
        personList.add(personMatch);
        String personGuid = personMatch.getPerson().getPersonGuid();
        personMap.put(personGuid, personMatch);
        List<PersonIdentifier> pil = personMatch.getPerson().getPersonIdentifierList();
        if (pil != null) {
            for (PersonIdentifier pi : pil) {
                if (pi.getIdentifierType() == PersonIdentifier.Type.kisumuHdssId) {
                    hdssIdMap.put(pi.getIdentifier(), personMatch);
                }
            }
        }
    }

    /**
     * Removes a person from our in-memory person list.
     *
     * @param personMatch the person to be removed.
     */
    private void remove(PersonMatch personMatch) {
        personList.remove(personMatch);
        personMap.remove(personMatch.getPerson().getPersonGuid());
        List<PersonIdentifier> pil = personMatch.getPerson().getPersonIdentifierList();
        if (pil != null) {
            for (PersonIdentifier pi : pil) {
                if (pi.getIdentifierType() == PersonIdentifier.Type.kisumuHdssId) {
                    hdssIdMap.put(pi.getIdentifier(), personMatch);
                }
            }
        }
    }

    /**
     * Gets a person from the in-memory list, by list index.
     *
     * @param index index of the person to get.
     * @return person from the in-memory list.
     */
    public PersonMatch get(int index) {
        return personList.get(index);
    }

    /**
     * Gets a person from the in-memory list, by person GUID.
     *
     * @param personGuid GUID of the person to search for.
     * @return person from the in-memory list, or null if the GUID was not found.
     */
    public PersonMatch get(String personGuid) {
        return personMap.get(personGuid);
    }

    /**
     * Loads into the PersonList the entire person table from the MPI, along with
     * any joining person relation data. Uses multiple threads to do this, to
     * minimize loading time.
     */
    public void load() {
        int personCount = 0;
        try {
            Connection conn = Sql.connect();
            String sql = "SELECT count(*) as person_count from person";
            ResultSet rs = Sql.query(conn, sql);
            List<Integer> personIds = new ArrayList<Integer>();
            rs.next();
            personCount = rs.getInt("person_count");
            Sql.close(rs);
            Sql.close(conn);
        } catch (SQLException ex) {
            Logger.getLogger(PersonList.class.getName()).log(Level.SEVERE, null, ex);
        }
        //
        // For quick debugging, set queryLimit to a limite number of rows,
        // to avoide loading the entire database. For production uses, set
        // queryLimit to zero.
        //
        String queryLimitString = Mediator.getProperty("Query.Limit");
        if (queryLimitString != null) {
            int limit = Integer.parseInt(queryLimitString);
            if (limit < personCount) {
                personCount = limit;
            }
        }
        if (personCount > 0) {
            //
            // If somehow there are fewer people in the MPI database than the number
            // of threads we can use, then limit the number of threads so that
            // each thread is loading only one person.
            int threadCount = Mpi.getMaxThreadCount();
            if (threadCount > personCount) {
                threadCount = personCount;
            }
            //
            // Compute the number of people we will load with each thread. Divide
            // the number of people by the number of threads. Round up.
            //
            // For integer division a/b rounded up to the next highest integer,
            // use the formula (a+b-1)/b.
            int countPerThread = (personCount + threadCount - 1) / threadCount;
            //
            // Now we will make a pass through all the person_id values in the database
            // for the purpose of partitioning them for all the threads. We will step through
            // all the person_ids in order, to find out where the cutoff values
            // are between the person_ids that each thread shall load. We put these
            // values into the cutoffs array.
            int[] cutoffs = null;
            try {
                Connection conn = Sql.connect();
                String sql = "SELECT person_id FROM person ORDER BY person_id";
                ResultSet rs = Sql.query(conn, sql);
                cutoffs = new int[threadCount + 1];
                cutoffs[0] = 0;
                int row = 0;
                int nextCutoff = countPerThread;
                int iCutoff = 1;
                while (rs.next() && row++ < personCount) {
                    if (row == nextCutoff || row == personCount) {
                        cutoffs[iCutoff++] = rs.getInt("person_id");
                        nextCutoff += countPerThread;
                    }
                }
                Sql.close(rs);
                Sql.close(conn);
            } catch (SQLException ex) {
                Logger.getLogger(PersonList.class.getName()).log(Level.SEVERE, null, ex);
            }
            //
            // Create all of the threads, and allocate each one the range of person_id
            // values it is to load.
            long startTime = System.currentTimeMillis();
            Thread[] threadArray = new Thread[threadCount];
            LoadPersonThread[] loadPersonThreadArray = new LoadPersonThread[threadCount];
            for (int i = 0; i < threadCount; i++) {
                int minPersonId = cutoffs[i] + 1;
                int maxPersonId = cutoffs[i + 1];
                LoadPersonThread lpt = new LoadPersonThread(i, minPersonId, maxPersonId);
                Thread t = new Thread(lpt);
                loadPersonThreadArray[i] = lpt;
                threadArray[i] = t;
                t.start();
                // Sleep just a little to let the thread start and print any logging messages
                // that it may have. This sleeping will not affect performance noticably, but
                // will give the thread the chance to start so that all threads will start
                // Sleep 10 milliseconds.
                waitAMoment();
            }
            //
            // Wait for all of the threads to complete loading their values.
            for (int i = 0; i < threadCount; i++) {
                Thread t = threadArray[i];
                try {
                    t.join();
                } catch (InterruptedException ex) {
                    Logger.getLogger(PersonList.class.getName()).log(Level.SEVERE, "Error joining FindPersonThread", ex);
                }
                LoadPersonThread lpt = loadPersonThreadArray[i];
                loadPersonThreadArray[i] = null; // Release object memory when we loop, most notably personMatchList
                List<PersonMatch> personMatchList = lpt.getPersonMatchList();
                for (PersonMatch pm : personMatchList) {
                    add(pm);
                }
            }
            double timeInterval = (System.currentTimeMillis() - startTime);
            Mediator.getLogger(PersonList.class.getName()).log(Level.FINE,
                    "All threads finished loading {0} entries in {1} milliseconds.",
                    new Object[]{personList.size(), timeInterval});
        }
    }

    /**
     * Waits a moment. To be used right after starting a thread, to give that
     * thread a moment to start, and start logging its progress. This is useful
     * so that the threads can report the start of their progress in the order
     * in which they are started. The reason this is done in a separate method
     * is to avoid the warning message that comes with sleeping in a loop.
     */
    private void waitAMoment() {
        try {
            Thread.sleep(50); // Sleep 50 milliseconds.
        } catch (InterruptedException ex) {
            Logger.getLogger(PersonList.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Does an efficient result set get string, assuming that the byte coding
     * in the database does not need translation into the character coding of
     * the string. This effectively does the same job as rs.getString(), but
     * without the overhead of doing a generalized character coding translation.
     * <p>
     * In CPU profiling, it was discovered that significant time was being
     * spent translating bytes into characters inside the rs.getString() method.
     * So this method was written to replace it for loading the MPI into memory.
     *
     * @param rs ResultSet
     * @param columnIndex index of the column to get
     * @return string value of the column
     * @throws SQLException
     */
    private String getRsString(ResultSet rs, int columnIndex) throws SQLException {
        String s = null;
        byte[] bytes = rs.getBytes(columnIndex);
        if (bytes != null) {
            char[] chars = new char[bytes.length];
            for (int i = 0; i < bytes.length; i++) {
                chars[i] = (char) bytes[i];
            }
            s = new String(chars);
        }
        return s;
    }

    /**
     * Searches the person list for one or more candidates matching
     * a given set of search terms.
     *
     * @param req Request containing the search terms to look for.
     * @return The response data to the request.
     */
    public Object find(PersonRequest req) {
        PersonResponse resp = new PersonResponse();
        Person p = req.getPerson();
        if (p == null) {
            Logger.getLogger(PersonList.class.getName()).log(Level.SEVERE, "FIND PERSON called with no person data.");
            return resp;
        }
        PersonMatch searchTerms = new PersonMatch(p);
        CandidateSet candidateSet = new CandidateSet();
        Set<SiteCandidate> siteCandidateSet = siteList.findIfNeeded(searchTerms);
        searchTerms.setSiteCandidateSet(siteCandidateSet);
        DateMatch.setToday();
        //
        // Make a special case if we are searching by GUID and not trying to match fingerprints.
        // In this case, just look to see if we have person matching the GUID search term.
        // If we do, then we are done. If not, then we test for matching the usual way...
        //
        PersonMatch guidMatch = null;
        if (p.getPersonGuid() != null
                && (p.getFingerprintList() == null || p.getFingerprintList().isEmpty())) {
            guidMatch = this.get(p.getPersonGuid());
            //TODO: Fix logic.
            if (guidMatch != null) {
                final double GUID_MATCH_SCORE = 1.0;
                final double GUID_MATCH_WEIGHT = 1.0;
                Scorecard s = new Scorecard();
                s.addScore(GUID_MATCH_SCORE, GUID_MATCH_WEIGHT);
                candidateSet.add(guidMatch, s);
                if (Mediator.testLoggerLevel(Level.FINEST)) {
                    Mediator.getLogger(PersonList.class.getName()).log(Level.FINEST,
                            "Score {0},{1} total {2},{3} comparing GUID {4} with {5}",
                            new Object[]{GUID_MATCH_SCORE, GUID_MATCH_WEIGHT, s.getTotalScore(), s.getTotalWeight(),
                                p.getPersonGuid(), guidMatch.getPerson().getPersonGuid()});
                }
            }
        }
        int personMatchCount = personList.size();
        if (guidMatch == null && personMatchCount > 0) { // Skip if matched already, or if MPI is empty
            int threadCount = Mpi.getMaxThreadCount();
            if (threadCount > personMatchCount) {
                threadCount = personMatchCount;
            }
            int countPerThread = (personMatchCount + threadCount - 1) / threadCount;
            long startTime = System.currentTimeMillis();
            List<Thread> threadArray = new ArrayList<Thread>();
            for (int i = 0; i < threadCount; i++) {
                int startIndex = countPerThread * i;
                int endIndex = (countPerThread * (i + 1)) - 1;
                if (endIndex >= personMatchCount) {
                    endIndex = personMatchCount - 1;
                }
                FindPersonThread fpt = new FindPersonThread(this, searchTerms, candidateSet, startIndex, endIndex);
                Thread t = new Thread(fpt);
                threadArray.add(t);
                t.start();
            }
            for (Thread t : threadArray) {
                try {
                    t.join();
                } catch (InterruptedException ex) {
                    Logger.getLogger(PersonList.class.getName()).log(Level.SEVERE, "Error joining FindPersonThread", ex);
                }
            }
            double timeInterval = (System.currentTimeMillis() - startTime);
            Mediator.getLogger(PersonList.class.getName()).log(Level.FINE,
                    "Searched {0} entries in {1} milliseconds.",
                    new Object[]{personMatchCount, timeInterval});
        }
        List<Person> candidateList = candidateSet.export();
        resp.setPersonList(candidateList);
        resp.setSuccessful(true);
        SearchHistory.create(req);
        return resp;
    }

    /**
     * Creates a new person in the database and our in-memory list.
     *
     * @param PersonRequest person search parameters to be stored
     */
    public Object create(PersonRequest req) {
        PersonResponse returnData = null;
        if (req.isResponseRequested()) {    // Has the client requested a response?
            returnData = new PersonResponse();
            returnData.setSuccessful(false); // Until we succeed, assume that we failed.
        }
        Person p = req.getPerson().clone(); // Clone because we may modify our copy below.
        if (p == null) {
            Logger.getLogger(PersonList.class.getName()).log(Level.SEVERE, "CREATE PERSON called with no person data.");
            return returnData;
        }
        //Check to see if this person's hdssid, if available, already exists in the mpi. Log error if it does.
        String existingId = null;
        if (p.getPersonIdentifierList() != null
                && !p.getPersonIdentifierList().isEmpty()) {
            for (PersonIdentifier personIdentifier : p.getPersonIdentifierList()) {
                if (personIdentifier.getIdentifierType() == PersonIdentifier.Type.kisumuHdssId) {
                    String pi = personIdentifier.getIdentifier();
                    if (pi != null && !pi.isEmpty()) {
                        if (hdssIdMap.containsKey(pi)) {
                            existingId = pi;
                            break;
                        }
                    }
                }
            }
        }
        if (existingId != null) {
            Logger.getLogger(PersonList.class.getName()).log(Level.SEVERE,
                    "CREATE PERSON called with existing kisumuhdssid person identifier {0}.", existingId);
            return returnData;
        }
        Connection conn = Sql.connect();
        ResultSet rs = Sql.query(conn, "SELECT UUID() AS uuid");
        String guid = null;
        try {
            rs.next();
            guid = rs.getString("uuid");
            Sql.close(rs);
        } catch (SQLException ex) { // Won't happen
            Logger.getLogger(PersonList.class.getName()).log(Level.SEVERE, null, ex);
        }
        p.setPersonGuid(guid);

        String sex = ValueMap.SEX.getDb().get(p.getSex());
        String villageId = Sql.getVillageId(conn, p.getVillageName());
        String maritalStatusId = Sql.getMaritalStatusId(conn, p.getMaritalStatus());
        String consentSigned = ValueMap.CONSENT_SIGNED.getDb().get(p.getConsentSigned());
        String sql = "INSERT INTO person (person_guid, first_name, middle_name, last_name,\n"
                + "       other_name, clan_name, sex, birthdate, deathdate,\n"
                + "       mothers_first_name, mothers_middle_name, mothers_last_name,\n"
                + "       fathers_first_name, fathers_middle_name, fathers_last_name,\n"
                + "       compoundhead_first_name, compoundhead_middle_name, compoundhead_last_name,\n"
                + "       village_id, marital_status, consent_signed, date_created) values (\n   "
                + Sql.quote(guid) + ", "
                + Sql.quote(p.getFirstName()) + ", "
                + Sql.quote(p.getMiddleName()) + ", "
                + Sql.quote(p.getLastName()) + ",\n   "
                + Sql.quote(p.getOtherName()) + ", "
                + Sql.quote(p.getClanName()) + ", "
                + Sql.quote(sex) + ", "
                + Sql.quote(p.getBirthdate()) + ", "
                + Sql.quote(p.getDeathdate()) + ",\n   "
                + Sql.quote(p.getMothersFirstName()) + ", "
                + Sql.quote(p.getMothersMiddleName()) + ", "
                + Sql.quote(p.getMothersLastName()) + ",\n   "
                + Sql.quote(p.getFathersFirstName()) + ", "
                + Sql.quote(p.getFathersMiddleName()) + ", "
                + Sql.quote(p.getFathersLastName()) + ",\n   "
                + Sql.quote(p.getCompoundHeadFirstName()) + ", "
                + Sql.quote(p.getCompoundHeadMiddleName()) + ", "
                + Sql.quote(p.getCompoundHeadLastName()) + ",\n   "
                + villageId + ", "
                + maritalStatusId + ", "
                + consentSigned + ", "
                + "NOW()"
                + ");";
        Sql.startTransaction(conn);
        boolean successful = Sql.execute(conn, sql);
        if (successful) {
            int dbPersonId = Integer.parseInt(Sql.getLastInsertId(conn));
            PersonIdentifierList.update(conn, dbPersonId, p.getPersonIdentifierList(), null);
            FingerprintList.update(conn, dbPersonId, p.getFingerprintList(), null);
            VisitList.update(conn, Sql.REGULAR_VISIT_TYPE_ID, dbPersonId, p.getLastRegularVisit());
            VisitList.update(conn, Sql.ONE_OFF_VISIT_TYPE_ID, dbPersonId, p.getLastOneOffVisit());
            PersonMatch newPer = new PersonMatch(p.clone()); // Clone to protect from unit test modifications.
            newPer.setDbPersonId(dbPersonId);
            this.add(newPer);
            SearchHistory.update(req, null, null); // Update search history showing that no candidate was selected.
        }
        Sql.commit(conn);
        Sql.close(conn);
        if (returnData != null) {
            List<Person> returnList = new ArrayList<Person>();
            returnList.add(p);
            returnData.setPersonList(returnList);
            returnData.setSuccessful(successful); // We have succeeded.
        }
        return returnData;
    }

    /**
     * Modifies a person entry in the database and in our in-memory list.
     *
     * @param req The modify request.
     */
    public Object modify(PersonRequest req) {
        PersonResponse returnData = null;
        if (req.isResponseRequested()) {    // Has the client requested a response?
            returnData = new PersonResponse();
            returnData.setSuccessful(false); // Until we succeed, assume that we failed.
        }
        Person newPerson = req.getPerson();//person containing modified data
        if (newPerson == null) {
            Logger.getLogger(PersonList.class.getName()).log(Level.SEVERE, "MODIFY PERSON called with no person data.");
            return returnData;
        }
        // For modify, we should have either a local person GUID or a HDSSID to reference the existing (old) entry.
        PersonMatch oldPersonMatch = null;
        String personGuid = newPerson.getPersonGuid();
        if (personGuid != null) {
            oldPersonMatch = this.get(personGuid);
            if (oldPersonMatch == null) {
                Logger.getLogger(PersonList.class.getName()).log(Level.SEVERE, "MODIFY PERSON GUID {0} not found.", personGuid);
                return returnData;
            }
        } else {
            List<PersonIdentifier> piList = newPerson.getPersonIdentifierList();
            if (piList != null && !piList.isEmpty()) {
                for (PersonIdentifier pi : piList) {
                    if (pi.getIdentifierType() == PersonIdentifier.Type.kisumuHdssId) {
                        String hdssId = pi.getIdentifier();
                        if (hdssId != null && !hdssId.isEmpty()) {
                            oldPersonMatch = hdssIdMap.get(hdssId);
                            if (oldPersonMatch != null) {
                                break;
                            }
                        }
                    }
                }
            }
        }
        if (oldPersonMatch == null) {
            Logger.getLogger(PersonList.class.getName()).log(Level.SEVERE, "MODIFY PERSON called with no person GUID or matching HDSSID.");
            return returnData;
        }
        SearchHistory.update(req, oldPersonMatch, newPerson); // Log the search result (if any) BEFORE modifying the person.
        int dbPersonId = oldPersonMatch.getDbPersonId();
        Person oldPerson = oldPersonMatch.getPerson();
        Connection conn = Sql.connect();
        String sex = ValueMap.SEX.getDb().get(newPerson.getSex());
        String villageId = Sql.getVillageId(conn, newPerson.getVillageName());
        String maritalStatusId = Sql.getMaritalStatusId(conn, newPerson.getMaritalStatus());
        String consentSigned = ValueMap.CONSENT_SIGNED.getDb().get(newPerson.getConsentSigned());
        int columnCount = 0;
        String sql = "UPDATE person SET\n";
        if (newPerson.getFirstName() != null) {
            if (newPerson.getFirstName().isEmpty()) {
                sql += (separate(columnCount) + "first_name = NULL\n");
            } else {
                sql += (separate(columnCount) + "first_name = " + Sql.quote(newPerson.getFirstName()) + "\n");
            }
            columnCount++;
        }
        if (newPerson.getMiddleName() != null) {
            if (newPerson.getMiddleName().isEmpty()) {
                sql += (separate(columnCount) + "middle_name = NULL\n");
            } else {
                sql += (separate(columnCount) + "middle_name = " + Sql.quote(newPerson.getMiddleName()) + "\n");
            }
            columnCount++;
        }
        if (newPerson.getLastName() != null) {
            if (newPerson.getLastName().isEmpty()) {
                sql += (separate(columnCount) + "last_name = NULL\n");
            } else {
                sql += (separate(columnCount) + "last_name = " + Sql.quote(newPerson.getLastName()) + "\n");
            }
            columnCount++;
        }
        if (newPerson.getOtherName() != null) {
            if (newPerson.getOtherName().isEmpty()) {
                sql += (separate(columnCount) + "other_name = NULL\n");
            } else {
                sql += (separate(columnCount) + "other_name = " + Sql.quote(newPerson.getOtherName()) + "\n");
            }
            columnCount++;
        }
        if (newPerson.getClanName() != null) {
            if (newPerson.getClanName().isEmpty()) {
                sql += (separate(columnCount) + "clan_name = NULL\n");
            } else {
                sql += (separate(columnCount) + "clan_name = " + Sql.quote(newPerson.getClanName()) + "\n");
            }
            columnCount++;
        }
        if (newPerson.getSex() != null) {
            sql += (separate(columnCount) + "sex = " + Sql.quote(sex) + "\n");
            columnCount++;
        }
        if (newPerson.getBirthdate() != null) {
            sql += (separate(columnCount) + "birthdate = " + Sql.quote(newPerson.getBirthdate()) + "\n");
            columnCount++;
        }
        if (newPerson.getDeathdate() != null) {
            sql += (separate(columnCount) + "deathdate = " + Sql.quote(newPerson.getDeathdate()) + "\n");
            columnCount++;
        }
        if (newPerson.getMothersFirstName() != null) {
            if (newPerson.getMothersFirstName().isEmpty()) {
                sql += (separate(columnCount) + "mothers_first_name = NULL\n");
            } else {
                sql += (separate(columnCount) + "mothers_first_name = " + Sql.quote(newPerson.getMothersFirstName()) + "\n");
            }
            columnCount++;
        }
        if (newPerson.getMothersMiddleName() != null) {
            if (newPerson.getMothersMiddleName().isEmpty()) {
                sql += (separate(columnCount) + "mothers_middle_name = NULL\n");
            } else {
                sql += (separate(columnCount) + "mothers_middle_name = " + Sql.quote(newPerson.getMothersMiddleName()) + "\n");
            }
            columnCount++;
        }
        if (newPerson.getMothersLastName() != null) {
            if (newPerson.getMothersLastName().isEmpty()) {
                sql += (separate(columnCount) + "mothers_last_name = NULL\n");
            } else {
                sql += (separate(columnCount) + "mothers_last_name = " + Sql.quote(newPerson.getMothersLastName()) + "\n");
            }
            columnCount++;
        }
        if (newPerson.getFathersFirstName() != null) {
            if (newPerson.getFathersFirstName().isEmpty()) {
                sql += (separate(columnCount) + "fathers_first_name = NULL\n");
            } else {
                sql += (separate(columnCount) + "fathers_first_name = " + Sql.quote(newPerson.getFathersFirstName()) + "\n");
            }
            columnCount++;
        }
        if (newPerson.getFathersMiddleName() != null) {
            if (newPerson.getFathersMiddleName().isEmpty()) {
                sql += (separate(columnCount) + "fathers_middle_name = NULL\n");
            } else {
                sql += (separate(columnCount) + "fathers_middle_name = " + Sql.quote(newPerson.getFathersMiddleName()) + "\n");
            }
            columnCount++;
        }
        if (newPerson.getFathersLastName() != null) {
            if (newPerson.getFathersLastName().isEmpty()) {
                sql += (separate(columnCount) + "fathers_last_name = NULL\n");
            } else {
                sql += (separate(columnCount) + "fathers_last_name = " + Sql.quote(newPerson.getFathersLastName()) + "\n");
            }
            columnCount++;
        }
        if (newPerson.getCompoundHeadFirstName() != null) {
            if (newPerson.getCompoundHeadFirstName().isEmpty()) {
                sql += (separate(columnCount) + "compoundhead_first_name = NULL\n");
            } else {
                sql += (separate(columnCount) + "compoundhead_first_name = " + Sql.quote(newPerson.getCompoundHeadFirstName()) + "\n");
            }
            columnCount++;
        }
        if (newPerson.getCompoundHeadMiddleName() != null) {
            if (newPerson.getCompoundHeadMiddleName().isEmpty()) {
                sql += (separate(columnCount) + "compoundhead_middle_name = NULL\n");
            } else {
                sql += (separate(columnCount) + "compoundhead_middle_name = " + Sql.quote(newPerson.getCompoundHeadMiddleName()) + "\n");
            }
            columnCount++;
        }
        if (newPerson.getCompoundHeadLastName() != null) {
            if (newPerson.getCompoundHeadLastName().isEmpty()) {
                sql += (separate(columnCount) + "compoundhead_last_name = NULL\n");
            } else {
                sql += (separate(columnCount) + "compoundhead_last_name = " + Sql.quote(newPerson.getCompoundHeadLastName()) + "\n");
            }
            columnCount++;
        }
        if (newPerson.getVillageName() != null) {
            if (newPerson.getVillageName().isEmpty()) {
                sql += (separate(columnCount) + "village_id = NULL\n");
            } else {
                sql += (separate(columnCount) + "village_id = " + Sql.quote(villageId) + "\n");
            }
            columnCount++;
        }
        if (newPerson.getMaritalStatus() != null) {
            sql += (separate(columnCount) + "marital_status = " + Sql.quote(maritalStatusId) + "\n");
            columnCount++;
        }
        if (newPerson.getConsentSigned() != null) {
            sql += (separate(columnCount) + "consent_signed = " + Sql.quote(consentSigned) + "\n");
            columnCount++;
        }
        sql += " WHERE person_id = " + dbPersonId;
        if (columnCount != 0) {//some 'real' mpi data has changed and needs to be updated
            Sql.startTransaction(conn);
            Sql.execute(conn, sql);
            List<PersonIdentifier> pList = PersonIdentifierList.update(conn, dbPersonId, newPerson.getPersonIdentifierList(), oldPerson.getPersonIdentifierList());
            newPerson.setPersonIdentifierList(pList);
            List<Fingerprint> fList = FingerprintList.update(conn, dbPersonId, newPerson.getFingerprintList(), oldPerson.getFingerprintList());
            newPerson.setFingerprintList(fList);
            VisitList.update(conn, Sql.REGULAR_VISIT_TYPE_ID, dbPersonId, newPerson.getLastRegularVisit());
            VisitList.update(conn, Sql.ONE_OFF_VISIT_TYPE_ID, dbPersonId, newPerson.getLastOneOffVisit());
            if (newPerson.getLastRegularVisit() == null) {
                newPerson.setLastRegularVisit(oldPerson.getLastRegularVisit());
            }
            if (newPerson.getLastOneOffVisit() == null) {
                newPerson.setLastOneOffVisit(oldPerson.getLastOneOffVisit());
            }
            Sql.commit(conn);
        }
        Sql.close(conn);
        if (newPerson.getLastMoveDate() != null) {
            newPerson.setPreviousVillageName(oldPerson.getVillageName());
        }
        Person mergedPerson = merge(newPerson, oldPersonMatch.getPerson());//merge old and new person
        PersonMatch newPersonMatch = new PersonMatch(mergedPerson);
        newPersonMatch.setDbPersonId(dbPersonId);
        this.remove(oldPersonMatch); // Remove old person from our in-memory list.
        this.add(newPersonMatch); // Add new person to our in-memory list.
        Notifier.notify(mergedPerson);
        if (returnData != null) {
            List<Person> returnList = new ArrayList<Person>();
            returnList.add(mergedPerson);
            returnData.setPersonList(returnList);
            returnData.setSuccessful(true); // We have succeeded.
        }
        return returnData;
    }

    /**
     * Returns a String containing either a space ' ' or a comma and a space ', ' depending on the
     * number of columns included in the update sql statement.
     *
     * @param columnCount number of columns in the sql statement
     * @return " " if the count is zero, otherwise ", ".
     */
    private String separate(int columnCount) {
        if (columnCount == 0) {
            return " ";
        } else {
            return ", ";
        }
    }

    /**
     * Merges the fields of two Person objects to override null fields where applicable.
     * Where the same field from both Person objects is non-null, the value of newPerson prevails
     * because it is the one most recently updated.
     *
     * @param newPerson new Person object
     * @param oldPerson old Person object
     * @return merged Person object
     */
    private Person merge(Person newPerson, Person oldPerson) {
        Person mergedPerson = new Person();
        Class personClass = Person.class;
        Field[] personFields = personClass.getDeclaredFields();
        for (Field field : personFields) {
            try {
                field.setAccessible(true);
                Object p1FieldValue = field.get(newPerson);
                Object p2FieldValue = field.get(oldPerson);
                if (!(p1FieldValue == null && p2FieldValue == null)) {
                    Object p3FieldValue = null;
                    if (p1FieldValue != null && p2FieldValue != null) {
                        p3FieldValue = p1FieldValue;
                    } else {
                        if (p1FieldValue != null && p2FieldValue == null) {
                            p3FieldValue = p1FieldValue;
                        } else if (p1FieldValue == null && p2FieldValue != null) {
                            p3FieldValue = p2FieldValue;
                        }
                    }
                    field.set(mergedPerson, p3FieldValue);
                }
            } catch (IllegalArgumentException ex) {
                Logger.getLogger(PersonList.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IllegalAccessException ex) {
                Logger.getLogger(PersonList.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        mergedPerson.setPersonIdentifierList(mergePersonIdentifierLists(newPerson.getPersonIdentifierList(),
                oldPerson.getPersonIdentifierList()));
        return mergedPerson;
    }

    /**
     * Copies over the contents of both newPersonIdentifierList and
     * oldPersonIdentifierList into the mergedPersonIdentifierList
     *
     * @param newPersonIdentifierList new PersonIdentifier list to merge
     * @param oldPersonIdentifierList old PersonIdentifier list to merge
     * @return merged PersonIdentifier list
     */
    private List<PersonIdentifier> mergePersonIdentifierLists(List<PersonIdentifier> newPersonIdentifierList,
            List<PersonIdentifier> oldPersonIdentifierList) {
        List<PersonIdentifier> mergedPersonIdentifierList = new ArrayList<PersonIdentifier>();
        boolean newExists = (newPersonIdentifierList != null && !newPersonIdentifierList.isEmpty());
        boolean oldExists = (oldPersonIdentifierList != null && !oldPersonIdentifierList.isEmpty());
        if (newExists && oldExists) {
            mergedPersonIdentifierList.addAll(newPersonIdentifierList);
            for (PersonIdentifier personIdentifier : oldPersonIdentifierList) {
                if (!mergedPersonIdentifierList.contains(personIdentifier)) {
                    mergedPersonIdentifierList.add(personIdentifier);
                }
            }
        } else {
            if (newExists && !oldExists) {
                mergedPersonIdentifierList.addAll(newPersonIdentifierList);
            } else if (!newExists && oldExists) {
                mergedPersonIdentifierList.addAll(oldPersonIdentifierList);
            }
        }
        return mergedPersonIdentifierList;
    }
}
TOP

Related Classes of ke.go.moh.oec.mpi.list.PersonList

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.