org.kuali.rice.kim.impl.identity.PersonServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.rice.kim.impl.identity.PersonServiceImpl.java

Source

/**
 * Copyright 2005-2014 The Kuali Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (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.opensource.org/licenses/ecl2.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.kuali.rice.kim.impl.identity;

import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.kuali.rice.core.api.CoreApiServiceLocator;
import org.kuali.rice.core.api.criteria.CountFlag;
import org.kuali.rice.core.api.criteria.Predicate;
import org.kuali.rice.core.api.criteria.PredicateUtils;
import org.kuali.rice.core.api.criteria.QueryByCriteria;
import org.kuali.rice.kim.api.identity.IdentityService;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.identity.PersonService;
import org.kuali.rice.kim.api.identity.entity.EntityDefault;
import org.kuali.rice.kim.api.identity.entity.EntityDefaultQueryResults;
import org.kuali.rice.kim.api.identity.external.EntityExternalIdentifierType;
import org.kuali.rice.kim.api.identity.principal.Principal;
import org.kuali.rice.kim.api.identity.type.EntityTypeContactInfoDefault;
import org.kuali.rice.kim.api.role.RoleService;
import org.kuali.rice.kim.api.services.KimApiServiceLocator;
import org.kuali.rice.kim.impl.KIMPropertyConstants;
import org.kuali.rice.kns.service.BusinessObjectMetaDataService;
import org.kuali.rice.kns.service.KNSServiceLocator;
import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
import org.kuali.rice.krad.bo.BusinessObject;
import org.kuali.rice.krad.bo.DataObjectRelationship;
import org.kuali.rice.krad.data.DataObjectWrapper;
import org.kuali.rice.krad.data.KradDataServiceLocator;
import org.kuali.rice.krad.lookup.CollectionIncomplete;
import org.kuali.rice.krad.util.KRADConstants;
import org.kuali.rice.krad.util.KRADPropertyConstants;
import org.kuali.rice.krad.util.KRADUtils;
import org.springframework.beans.PropertyAccessorUtils;

/**
 * This is a description of what this class does - kellerj don't forget to fill this in.
 *
 * @author Kuali Rice Team (rice.collab@kuali.org)
 *
 */
public class PersonServiceImpl implements PersonService {

    private static Logger LOG = Logger.getLogger(PersonServiceImpl.class);
    protected static final String ENTITY_EXT_ID_PROPERTY_PREFIX = "externalIdentifiers.";
    protected static final String ENTITY_AFFILIATION_PROPERTY_PREFIX = "affiliations.";
    protected static final String ENTITY_TYPE_PROPERTY_PREFIX = "entityTypeContactInfos.";
    protected static final String ENTITY_EMAIL_PROPERTY_PREFIX = "entityTypeContactInfos.emailAddresses.";
    protected static final String ENTITY_PHONE_PROPERTY_PREFIX = "entityTypeContactInfos.phoneNumbers.";
    protected static final String ENTITY_ADDRESS_PROPERTY_PREFIX = "entityTypeContactInfos.addresses.";
    protected static final String ENTITY_NAME_PROPERTY_PREFIX = "names.";
    protected static final String PRINCIPAL_PROPERTY_PREFIX = "principals.";
    protected static final String ENTITY_EMPLOYEE_ID_PROPERTY_PREFIX = "employmentInformation.";
    // KULRICE-4442 Special handling for extension objects
    protected static final String EXTENSION = "extension";

    private IdentityService identityService;
    private RoleService roleService;
    private BusinessObjectMetaDataService businessObjectMetaDataService;
    private MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService;

    protected List<String> personEntityTypeCodes = new ArrayList<String>(4);
    // String that can be passed to the lookup framework to create an type = X OR type = Y criteria
    private String personEntityTypeLookupCriteria = null;

    protected Map<String, String> baseLookupCriteria = new HashMap<String, String>();
    protected Map<String, String> criteriaConversion = new HashMap<String, String>();
    protected ArrayList<String> personCachePropertyNames = new ArrayList<String>();
    {
        // init the criteria which will need to be applied to every lookup against
        // the identity data tables
        baseLookupCriteria.put(KIMPropertyConstants.Person.ACTIVE, "Y");
        baseLookupCriteria.put(ENTITY_TYPE_PROPERTY_PREFIX + KRADPropertyConstants.ACTIVE, "Y");

        // create the field mappings between the Person object and the KimEntity object
        criteriaConversion.put(KIMPropertyConstants.Person.ENTITY_ID, KIMPropertyConstants.Entity.ID);
        criteriaConversion.put(KIMPropertyConstants.Person.ACTIVE,
                PRINCIPAL_PROPERTY_PREFIX + KRADPropertyConstants.ACTIVE);
        criteriaConversion.put(KIMPropertyConstants.Person.PRINCIPAL_ID,
                PRINCIPAL_PROPERTY_PREFIX + KIMPropertyConstants.Person.PRINCIPAL_ID);
        criteriaConversion.put(KIMPropertyConstants.Person.PRINCIPAL_NAME,
                PRINCIPAL_PROPERTY_PREFIX + KIMPropertyConstants.Person.PRINCIPAL_NAME);
        criteriaConversion.put(KIMPropertyConstants.Person.FIRST_NAME, "names.firstName");
        criteriaConversion.put(KIMPropertyConstants.Person.LAST_NAME, "names.lastName");
        criteriaConversion.put(KIMPropertyConstants.Person.MIDDLE_NAME, "names.middleName");
        criteriaConversion.put(KIMPropertyConstants.Person.EMAIL_ADDRESS,
                "entityTypeContactInfos.emailAddresses.emailAddress");
        criteriaConversion.put(KIMPropertyConstants.Person.PHONE_NUMBER,
                "entityTypeContactInfos.phoneNumbers.phoneNumber");
        criteriaConversion.put(KIMPropertyConstants.Person.ADDRESS_LINE_1,
                "entityTypeContactInfos.addresses.line1");
        criteriaConversion.put(KIMPropertyConstants.Person.ADDRESS_LINE_2,
                "entityTypeContactInfos.addresses.line2");
        criteriaConversion.put(KIMPropertyConstants.Person.ADDRESS_LINE_3,
                "entityTypeContactInfos.addresses.line3");
        criteriaConversion.put(KIMPropertyConstants.Person.CITY, "entityTypeContactInfos.addresses.city");
        criteriaConversion.put(KIMPropertyConstants.Person.STATE_CODE,
                "entityTypeContactInfos.addresses.stateProvinceCode");
        criteriaConversion.put(KIMPropertyConstants.Person.POSTAL_CODE,
                "entityTypeContactInfos.addresses.postalCode");
        criteriaConversion.put(KIMPropertyConstants.Person.COUNTRY_CODE,
                "entityTypeContactInfos.addresses.countryCode");
        criteriaConversion.put(KIMPropertyConstants.Person.CAMPUS_CODE, "affiliations.campusCode");
        criteriaConversion.put(KIMPropertyConstants.Person.AFFILIATION_TYPE_CODE,
                "affiliations.affiliationTypeCode");
        criteriaConversion.put(KIMPropertyConstants.Person.EXTERNAL_IDENTIFIER_TYPE_CODE,
                "externalIdentifiers.externalIdentifierTypeCode");
        criteriaConversion.put(KIMPropertyConstants.Person.EXTERNAL_ID, "externalIdentifiers.externalId");
        criteriaConversion.put(KIMPropertyConstants.Person.EMPLOYEE_TYPE_CODE,
                "employmentInformation.employeeTypeCode");
        criteriaConversion.put(KIMPropertyConstants.Person.EMPLOYEE_STATUS_CODE,
                "employmentInformation.employeeStatusCode");
        criteriaConversion.put(KIMPropertyConstants.Person.EMPLOYEE_ID, "employmentInformation.employeeId");
        criteriaConversion.put(KIMPropertyConstants.Person.BASE_SALARY_AMOUNT,
                "employmentInformation.baseSalaryAmount");
        criteriaConversion.put(KIMPropertyConstants.Person.PRIMARY_DEPARTMENT_CODE,
                "employmentInformation.primaryDepartmentCode");

        personCachePropertyNames.add(KIMPropertyConstants.Person.PRINCIPAL_ID);
        personCachePropertyNames.add(KIMPropertyConstants.Person.PRINCIPAL_NAME);
        personCachePropertyNames.add(KIMPropertyConstants.Person.ENTITY_ID);
        personCachePropertyNames.add(KIMPropertyConstants.Person.FIRST_NAME);
        personCachePropertyNames.add(KIMPropertyConstants.Person.LAST_NAME);
        personCachePropertyNames.add(KIMPropertyConstants.Person.MIDDLE_NAME);
        personCachePropertyNames.add(KIMPropertyConstants.Person.CAMPUS_CODE);
        personCachePropertyNames.add(KIMPropertyConstants.Person.EMPLOYEE_ID);
        personCachePropertyNames.add(KIMPropertyConstants.Person.PRIMARY_DEPARTMENT_CODE);
    }

    /**
     * @see org.kuali.rice.kim.api.identity.PersonService#getPerson(java.lang.String)
     */
    @Override
    public Person getPerson(String principalId) {
        if (StringUtils.isBlank(principalId)) {
            return null;
        }

        // get the corresponding principal
        final Principal principal = getIdentityService().getPrincipal(principalId);
        // get the identity
        if (principal != null) {
            final EntityDefault entity = getIdentityService().getEntityDefault(principal.getEntityId());
            // convert the principal and identity to a Person
            // skip if the person was created from the DB cache
            if (entity != null) {
                return convertEntityToPerson(entity, principal);
            }
        }
        return null;
    }

    protected PersonImpl convertEntityToPerson(EntityDefault entity, Principal principal) {
        try {
            // get the EntityEntityType for the EntityType corresponding to a Person
            for (String entityTypeCode : personEntityTypeCodes) {
                EntityTypeContactInfoDefault entType = entity.getEntityType(entityTypeCode);
                // if no "person" identity type present for the given principal, skip to the next type in the list
                if (entType == null) {
                    continue;
                }
                // attach the principal and identity objects
                // PersonImpl has logic to pull the needed elements from the KimEntity-related classes
                return new PersonImpl(principal, entity, entityTypeCode);
            }
            return null;
        } catch (Exception ex) {
            // allow runtime exceptions to pass through
            if (ex instanceof RuntimeException) {
                throw (RuntimeException) ex;
            }
            throw new RuntimeException("Problem building person object", ex);
        }
    }

    /**
     * @see org.kuali.rice.kim.api.identity.PersonService#getPersonByPrincipalName(java.lang.String)
     */
    @Override
    public Person getPersonByPrincipalName(String principalName) {
        if (StringUtils.isBlank(principalName)) {
            return null;
        }

        // get the corresponding principal
        final Principal principal = getIdentityService().getPrincipalByPrincipalName(principalName);
        // get the identity
        if (principal != null) {
            final EntityDefault entity = getIdentityService().getEntityDefault(principal.getEntityId());

            // convert the principal and identity to a Person
            if (entity != null) {
                return convertEntityToPerson(entity, principal);
            }
        }
        return null;
    }

    @Override
    public Person getPersonByEmployeeId(String employeeId) {
        if (StringUtils.isBlank(employeeId)) {
            return null;
        }

        final List<Person> people = findPeople(
                Collections.singletonMap(KIMPropertyConstants.Person.EMPLOYEE_ID, employeeId));
        if (!people.isEmpty()) {
            return people.get(0);

        }

        // If no person was found above, check for inactive records
        EntityDefault entity = getIdentityService().getEntityDefaultByEmployeeId(employeeId);
        if (entity != null) {
            if (!entity.getPrincipals().isEmpty()) {
                Principal principal = getIdentityService()
                        .getPrincipal(entity.getPrincipals().get(0).getPrincipalId());
                if (principal != null) {
                    return convertEntityToPerson(entity, principal);
                }
            }
        }

        return null;
    }

    /**
     * @see org.kuali.rice.kim.api.identity.PersonService#findPeople(Map)
     */
    @Override
    public List<Person> findPeople(Map<String, String> criteria) {
        return findPeople(criteria, true);
    }

    /**
     * @see org.kuali.rice.kim.api.identity.PersonService#findPeople(java.util.Map, boolean)
     */
    @Override
    public List<Person> findPeople(Map<String, String> criteria, boolean unbounded) {
        List<Person> people = null;
        // protect from NPEs
        if (criteria == null) {
            criteria = Collections.emptyMap();
        }
        // make a copy so it can be modified safely in this method
        criteria = new HashMap<String, String>(criteria);

        // extract the role lookup parameters and then remove them since later code will not know what to do with them
        String roleName = criteria.get("lookupRoleName");
        String namespaceCode = criteria.get("lookupRoleNamespaceCode");
        criteria.remove("lookupRoleName");
        criteria.remove("lookupRoleNamespaceCode");
        if (StringUtils.isNotBlank(namespaceCode) && StringUtils.isNotBlank(roleName)) {
            Integer searchResultsLimit = org.kuali.rice.kns.lookup.LookupUtils
                    .getSearchResultsLimit(PersonImpl.class);
            int searchResultsLimitInt = Integer.MAX_VALUE;
            if (searchResultsLimit != null) {
                searchResultsLimitInt = searchResultsLimit.intValue();
            }
            if (LOG.isDebugEnabled()) {
                LOG.debug("Performing Person search including role filter: " + namespaceCode + "/" + roleName);
            }
            if (criteria.size() == 1 && criteria.containsKey(KIMPropertyConstants.Person.ACTIVE)) { // if only active is specified
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Only active criteria specified, running role search first");
                }
                // in this case, run the role lookup first and pass those results to the person lookup
                Collection<String> principalIds = getRoleService().getRoleMemberPrincipalIds(namespaceCode,
                        roleName, Collections.<String, String>emptyMap());
                StringBuffer sb = new StringBuffer(principalIds.size() * 15);
                Iterator<String> pi = principalIds.iterator();
                while (pi.hasNext()) {
                    sb.append(pi.next());
                    if (pi.hasNext()) {
                        sb.append('|');
                    }
                }
                // add the list of principal IDs to the lookup so that only matching Person objects are returned
                criteria.put(KIMPropertyConstants.Person.PRINCIPAL_ID, sb.toString());
                people = findPeopleInternal(criteria, false); // can allow internal method to filter here since no more filtering necessary
            } else if (!criteria.isEmpty()) { // i.e., person criteria are specified
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Person criteria also specified, running that search first");
                }
                // run the person lookup first
                people = findPeopleInternal(criteria, true); // get all, since may need to be filtered
                // TODO - now check if these people have the given role
                // build a principal list
                List<String> principalIds = peopleToPrincipalIds(people);
                // get sublist of principals that have the given roles
                principalIds = getRoleService().getPrincipalIdSubListWithRole(principalIds, namespaceCode, roleName,
                        Collections.<String, String>emptyMap());
                // re-convert into people objects, wrapping in CollectionIncomplete if needed
                if (!unbounded && principalIds.size() > searchResultsLimitInt) {
                    int actualResultSize = principalIds.size();
                    // trim the list down before converting to people
                    principalIds = new ArrayList<String>(principalIds).subList(0, searchResultsLimitInt); // yes, this is a little wasteful
                    people = getPeople(principalIds); // convert the results to people
                    people = new CollectionIncomplete<Person>(people.subList(0, searchResultsLimitInt),
                            new Long(actualResultSize));
                } else {
                    people = getPeople(principalIds);
                }
            } else { // only role criteria specified
                if (LOG.isDebugEnabled()) {
                    LOG.debug("No Person criteria specified - only using role service.");
                }
                // run the role criteria to get the principals with the role
                Collection<String> principalIds = getRoleService().getRoleMemberPrincipalIds(namespaceCode,
                        roleName, Collections.<String, String>emptyMap());
                if (!unbounded && principalIds.size() > searchResultsLimitInt) {
                    int actualResultSize = principalIds.size();
                    // trim the list down before converting to people
                    principalIds = new ArrayList<String>(principalIds).subList(0, searchResultsLimitInt); // yes, this is a little wasteful
                    people = getPeople(principalIds); // convert the results to people
                    people = new CollectionIncomplete<Person>(people.subList(0, searchResultsLimitInt),
                            new Long(actualResultSize));
                } else {
                    people = getPeople(principalIds); // convert the results to people
                }
            }
        } else {
            if (LOG.isDebugEnabled()) {
                LOG.debug("No Role criteria specified, running person lookup as normal.");
            }
            people = findPeopleInternal(criteria, unbounded);
        }

        // The following change is for KULRICE-5694 - It prevents duplicate rows from being returned for the
        // person inquiry (In this case, duplicate meaning same entityId, principalId, and principalNm).
        // This allows for multiple rows to be returned if an entityID has more then one principal name
        // or more than one principal ID.
        Set<String> peopleNoDupsSet = new HashSet<String>();
        List<Person> peopleNoDupsList = new ArrayList<Person>();

        for (Iterator<Person> iter = people.iterator(); iter.hasNext();) {
            Person person = iter.next();
            if (peopleNoDupsSet.add(person.getEntityId() + person.getPrincipalId() + person.getPrincipalName())) {
                peopleNoDupsList.add(person);
            }
        }

        people.clear();
        people.addAll(peopleNoDupsList);

        return people;
    }

    @SuppressWarnings("unchecked")
    protected List<Person> findPeopleInternal(Map<String, String> criteria, boolean unbounded) {
        // convert the criteria to a form that can be used by the ORM layer

        //TODO convert this to the new criteria predicates
        Map<String, String> entityCriteria = convertPersonPropertiesToEntityProperties(criteria);

        Predicate predicate = PredicateUtils.convertMapToPredicate(entityCriteria);

        QueryByCriteria.Builder queryBuilder = QueryByCriteria.Builder.create();
        queryBuilder.setPredicates(predicate);

        if (!unbounded) {
            Integer searchResultsLimit = org.kuali.rice.kns.lookup.LookupUtils
                    .getSearchResultsLimit(PersonImpl.class);
            if (searchResultsLimit != null && searchResultsLimit >= 0) {
                queryBuilder.setMaxResults(searchResultsLimit);
                queryBuilder.setCountFlag(CountFlag.INCLUDE);
            }
        }

        List<Person> people = new ArrayList<Person>();

        EntityDefaultQueryResults qr = getIdentityService().findEntityDefaults(queryBuilder.build());

        if (qr.getResults().size() > 0) {

            for (EntityDefault e : qr.getResults()) {
                // get to get all principals for the identity as well
                for (Principal p : e.getPrincipals()) {
                    people.add(convertEntityToPerson(e, p));
                }
            }
        } else if (!qr.isMoreResultsAvailable() && entityCriteria.containsKey("principals.principalId")) {
            if (!(entityCriteria.containsKey(KIMPropertyConstants.Person.ACTIVE))
                    || (criteria.get(KIMPropertyConstants.Person.ACTIVE).equals("N"))) {
                String principalId = entityCriteria.get("principals.principalId");
                try {
                    EntityDefault entityDefault = getIdentityService().getEntityDefaultByPrincipalId(principalId);
                    for (Principal p : entityDefault.getPrincipals()) {
                        if (!p.isActive()) {
                            people.add(convertEntityToPerson(entityDefault, p));
                        }
                    }
                } catch (Exception e) {
                    LOG.info("A principal Id of " + principalId + " dose not exist in the system");
                }
            }
        } else if (!qr.isMoreResultsAvailable() && entityCriteria.containsKey("principals.principalName")) {
            if (!(entityCriteria.containsKey(KIMPropertyConstants.Person.ACTIVE))
                    || (criteria.get(KIMPropertyConstants.Person.ACTIVE).equals("N"))) {
                String principalNm = entityCriteria.get("principals.principalName");
                try {
                    EntityDefault entityDefault = getIdentityService().getEntityDefaultByPrincipalName(principalNm);
                    for (Principal p : entityDefault.getPrincipals()) {
                        if (!p.isActive()) {
                            people.add(convertEntityToPerson(entityDefault, p));
                        }
                    }
                } catch (Exception e) {
                    LOG.info("A principal name of " + principalNm + " dose not exist in the system");
                }
            }
        }
        return people;
    }

    public Map<String, String> convertPersonPropertiesToEntityProperties(Map<String, String> criteria) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("convertPersonPropertiesToEntityProperties: " + criteria);
        }
        boolean nameCriteria = false;
        boolean addressCriteria = false;
        boolean externalIdentifierCriteria = false;
        boolean affiliationCriteria = false;
        boolean affiliationDefaultOnlyCriteria = false;
        boolean phoneCriteria = false;
        boolean emailCriteria = false;
        boolean employeeIdCriteria = false;
        // add base lookups for all person lookups
        HashMap<String, String> newCriteria = new HashMap<String, String>();
        newCriteria.putAll(baseLookupCriteria);

        newCriteria.put("entityTypeContactInfos.entityTypeCode", personEntityTypeLookupCriteria);

        if (criteria != null) {
            for (String key : criteria.keySet()) {
                //check active radio button
                if (key.equals(KIMPropertyConstants.Person.ACTIVE)) {
                    newCriteria.put(criteriaConversion.get(KIMPropertyConstants.Person.ACTIVE),
                            criteria.get(KIMPropertyConstants.Person.ACTIVE));
                } else {
                    // The following if statement enables the "both" button to work correctly.
                    if (!(criteria.containsKey(KIMPropertyConstants.Person.ACTIVE))) {
                        newCriteria.remove(KIMPropertyConstants.Person.ACTIVE);
                    }
                }

                // if no value was passed, skip the entry in the Map
                if (StringUtils.isEmpty(criteria.get(key))) {
                    continue;
                }
                // check if the value needs to be encrypted
                // handle encrypted external identifiers
                if (key.equals(KIMPropertyConstants.Person.EXTERNAL_ID)
                        && StringUtils.isNotBlank(criteria.get(key))) {
                    // look for a ext ID type property
                    if (criteria.containsKey(KIMPropertyConstants.Person.EXTERNAL_IDENTIFIER_TYPE_CODE)) {
                        String extIdTypeCode = criteria
                                .get(KIMPropertyConstants.Person.EXTERNAL_IDENTIFIER_TYPE_CODE);
                        if (StringUtils.isNotBlank(extIdTypeCode)) {
                            // if found, load that external ID Type via service
                            EntityExternalIdentifierType extIdType = getIdentityService()
                                    .getExternalIdentifierType(extIdTypeCode);
                            // if that type needs to be encrypted, encrypt the value in the criteria map
                            if (extIdType != null && extIdType.isEncryptionRequired()) {
                                try {
                                    if (CoreApiServiceLocator.getEncryptionService().isEnabled()) {
                                        criteria.put(key, CoreApiServiceLocator.getEncryptionService()
                                                .encrypt(criteria.get(key)));
                                    }
                                } catch (GeneralSecurityException ex) {
                                    LOG.error("Unable to encrypt value for external ID search of type "
                                            + extIdTypeCode, ex);
                                }
                            }
                        }
                    }
                }

                // convert the property to the Entity data model
                String entityProperty = criteriaConversion.get(key);
                if (entityProperty != null) {
                    newCriteria.put(entityProperty, criteria.get(key));
                } else {
                    entityProperty = key;
                    // just pass it through if no translation present
                    newCriteria.put(key, criteria.get(key));
                }
                // check if additional criteria are needed based on the types of properties specified
                if (isNameEntityCriteria(entityProperty)) {
                    nameCriteria = true;
                }
                if (isExternalIdentifierEntityCriteria(entityProperty)) {
                    externalIdentifierCriteria = true;
                }
                if (isAffiliationEntityCriteria(entityProperty)) {
                    affiliationCriteria = true;
                }
                if (isAddressEntityCriteria(entityProperty)) {
                    addressCriteria = true;
                }
                if (isPhoneEntityCriteria(entityProperty)) {
                    phoneCriteria = true;
                }
                if (isEmailEntityCriteria(entityProperty)) {
                    emailCriteria = true;
                }
                if (isEmployeeIdEntityCriteria(entityProperty)) {
                    employeeIdCriteria = true;
                }
                // special handling for the campus code, since that forces the query to look
                // at the default affiliation record only
                if (key.equals("campusCode")) {
                    affiliationDefaultOnlyCriteria = true;
                }
            }

            if (nameCriteria) {
                newCriteria.put(ENTITY_NAME_PROPERTY_PREFIX + "active", "Y");
                newCriteria.put(ENTITY_NAME_PROPERTY_PREFIX + "defaultValue", "Y");
                //newCriteria.put(ENTITY_NAME_PROPERTY_PREFIX + "nameCode", "PRFR");//so we only display 1 result
            }
            if (addressCriteria) {
                newCriteria.put(ENTITY_ADDRESS_PROPERTY_PREFIX + "active", "Y");
                newCriteria.put(ENTITY_ADDRESS_PROPERTY_PREFIX + "defaultValue", "Y");
            }
            if (phoneCriteria) {
                newCriteria.put(ENTITY_PHONE_PROPERTY_PREFIX + "active", "Y");
                newCriteria.put(ENTITY_PHONE_PROPERTY_PREFIX + "defaultValue", "Y");
            }
            if (emailCriteria) {
                newCriteria.put(ENTITY_EMAIL_PROPERTY_PREFIX + "active", "Y");
                newCriteria.put(ENTITY_EMAIL_PROPERTY_PREFIX + "defaultValue", "Y");
            }
            if (employeeIdCriteria) {
                newCriteria.put(ENTITY_EMPLOYEE_ID_PROPERTY_PREFIX + "active", "Y");
                newCriteria.put(ENTITY_EMPLOYEE_ID_PROPERTY_PREFIX + "primary", "Y");
            }
            if (affiliationCriteria) {
                newCriteria.put(ENTITY_AFFILIATION_PROPERTY_PREFIX + "active", "Y");
            }
            if (affiliationDefaultOnlyCriteria) {
                newCriteria.put(ENTITY_AFFILIATION_PROPERTY_PREFIX + "defaultValue", "Y");
            }
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("Converted: " + newCriteria);
        }
        return newCriteria;
    }

    protected boolean isNameEntityCriteria(String propertyName) {
        return propertyName.startsWith(ENTITY_NAME_PROPERTY_PREFIX);
    }

    protected boolean isAddressEntityCriteria(String propertyName) {
        return propertyName.startsWith(ENTITY_ADDRESS_PROPERTY_PREFIX);
    }

    protected boolean isPhoneEntityCriteria(String propertyName) {
        return propertyName.startsWith(ENTITY_PHONE_PROPERTY_PREFIX);
    }

    protected boolean isEmailEntityCriteria(String propertyName) {
        return propertyName.startsWith(ENTITY_EMAIL_PROPERTY_PREFIX);
    }

    protected boolean isEmployeeIdEntityCriteria(String propertyName) {
        return propertyName.startsWith(ENTITY_EMPLOYEE_ID_PROPERTY_PREFIX);
    }

    protected boolean isAffiliationEntityCriteria(String propertyName) {
        return propertyName.startsWith(ENTITY_AFFILIATION_PROPERTY_PREFIX);
    }

    protected boolean isExternalIdentifierEntityCriteria(String propertyName) {
        return propertyName.startsWith(ENTITY_EXT_ID_PROPERTY_PREFIX);
    }

    /**
     * Get the entityTypeCode that can be associated with a Person.  This will determine
     * where EntityType-related data is pulled from within the KimEntity object.  The codes
     * in the list will be examined in the order present.
     */
    public List<String> getPersonEntityTypeCodes() {
        return this.personEntityTypeCodes;
    }

    public void setPersonEntityTypeCodes(List<String> personEntityTypeCodes) {
        this.personEntityTypeCodes = personEntityTypeCodes;
        personEntityTypeLookupCriteria = null;
        for (String entityTypeCode : personEntityTypeCodes) {
            if (personEntityTypeLookupCriteria == null) {
                personEntityTypeLookupCriteria = entityTypeCode;
            } else {
                personEntityTypeLookupCriteria = personEntityTypeLookupCriteria + "|" + entityTypeCode;
            }
        }
    }

    protected List<Person> getPeople(Collection<String> principalIds) {
        List<Person> people = new ArrayList<Person>(principalIds.size());
        for (String principalId : principalIds) {
            people.add(getPerson(principalId));
        }
        return people;
    }

    protected List<String> peopleToPrincipalIds(List<Person> people) {
        List<String> principalIds = new ArrayList<String>();

        for (Person person : people) {
            principalIds.add(person.getPrincipalId());
        }

        return principalIds;
    }

    /**
     * @see org.kuali.rice.kim.api.identity.PersonService#getPersonByExternalIdentifier(java.lang.String, java.lang.String)
     */
    @Override
    public List<Person> getPersonByExternalIdentifier(String externalIdentifierTypeCode, String externalId) {
        if (StringUtils.isBlank(externalIdentifierTypeCode) || StringUtils.isBlank(externalId)) {
            return null;
        }
        Map<String, String> criteria = new HashMap<String, String>(2);
        criteria.put(KIMPropertyConstants.Person.EXTERNAL_IDENTIFIER_TYPE_CODE, externalIdentifierTypeCode);
        criteria.put(KIMPropertyConstants.Person.EXTERNAL_ID, externalId);
        return findPeople(criteria);
    }

    /**
     * @see org.kuali.rice.kim.api.identity.PersonService#updatePersonIfNecessary(java.lang.String, org.kuali.rice.kim.api.identity.Person)
     */
    @Override
    public Person updatePersonIfNecessary(String sourcePrincipalId, Person currentPerson) {
        if (currentPerson == null // no person set
                || !StringUtils.equals(sourcePrincipalId, currentPerson.getPrincipalId()) // principal ID mismatch
                || currentPerson.getEntityId() == null) { // syntheticially created Person object
            Person person = getPerson(sourcePrincipalId);
            // if a synthetically created person object is present, leave it - required for property derivation and the UI layer for
            // setting the principal name
            if (person == null) {
                if (currentPerson != null && currentPerson.getEntityId() == null) {
                    return currentPerson;
                }
            }
            // if both are null, create an empty object for property derivation
            if (person == null && currentPerson == null) {
                try {
                    return new PersonImpl();
                } catch (Exception ex) {
                    LOG.error("unable to instantiate an object of type: " + getPersonImplementationClass()
                            + " - returning null", ex);
                    return null;
                }
            }
            return person;
        }
        // otherwise, no need to change the given object
        return currentPerson;
    }

    /**
     * Builds a map containing entries from the passed in Map that do NOT represent properties on an embedded
     * Person object.
     */
    private Map<String, String> getNonPersonSearchCriteria(Object bo, Map<String, String> fieldValues) {
        Map<String, String> nonUniversalUserSearchCriteria = new HashMap<String, String>();
        for (String propertyName : fieldValues.keySet()) {
            if (!isPersonProperty(bo, propertyName)) {
                nonUniversalUserSearchCriteria.put(propertyName, fieldValues.get(propertyName));
            }
        }
        return nonUniversalUserSearchCriteria;
    }

    private boolean isPersonProperty(Object bo, String propertyName) {
        try {
            if (PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName) // is a nested property
                    && !StringUtils.contains(propertyName, "add.")) {// exclude add line properties (due to path parsing problems in PropertyUtils.getPropertyType)
                int lastIndex = PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(propertyName);
                String propertyTypeName = lastIndex != -1 ? StringUtils.substring(propertyName, 0, lastIndex)
                        : StringUtils.EMPTY;
                Class<?> type = PropertyUtils.getPropertyType(bo, propertyTypeName);
                // property type indicates a Person object
                if (type != null) {
                    return Person.class.isAssignableFrom(type);
                }
                LOG.warn("Unable to determine type of nested property: " + bo.getClass().getName() + " / "
                        + propertyName);
            }
        } catch (Exception ex) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Unable to determine if property on " + bo.getClass().getName() + " to a person object: "
                        + propertyName, ex);
            }
        }
        return false;
    }

    /**
     * @see org.kuali.rice.kim.api.identity.PersonService#resolvePrincipalNamesToPrincipalIds(org.kuali.rice.krad.bo.BusinessObject, java.util.Map)
     */
    @Override
    @SuppressWarnings("unchecked")
    public Map<String, String> resolvePrincipalNamesToPrincipalIds(BusinessObject businessObject,
            Map<String, String> fieldValues) {
        if (fieldValues == null) {
            return null;
        }
        if (businessObject == null) {
            return fieldValues;
        }
        StringBuffer resolvedPrincipalIdPropertyName = new StringBuffer();
        // save off all criteria which are not references to Person properties
        // leave person properties out so they can be resolved and replaced by this method
        Map<String, String> processedFieldValues = getNonPersonSearchCriteria(businessObject, fieldValues);
        for (String propertyName : fieldValues.keySet()) {
            if (!StringUtils.isBlank(fieldValues.get(propertyName)) // property has a value
                    && isPersonProperty(businessObject, propertyName) // is a property on a Person object
            ) {
                // strip off the prefix on the property
                int lastPropertyIndex = PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(propertyName);
                String personPropertyName = lastPropertyIndex != -1
                        ? StringUtils.substring(propertyName, lastPropertyIndex + 1)
                        : propertyName;
                // special case - the user ID
                if (StringUtils.equals(KIMPropertyConstants.Person.PRINCIPAL_NAME, personPropertyName)) {
                    Class targetBusinessObjectClass = null;
                    BusinessObject targetBusinessObject = null;
                    resolvedPrincipalIdPropertyName.setLength(0); // clear the buffer without requiring a new object allocation on each iteration
                    // get the property name up until the ".principalName"
                    // this should be a reference to the Person object attached to the BusinessObject
                    String personReferenceObjectPropertyName = lastPropertyIndex != -1
                            ? StringUtils.substring(propertyName, 0, lastPropertyIndex)
                            : StringUtils.EMPTY;
                    // check if the person was nested within another BO under the master BO.  If so, go up one more level
                    // otherwise, use the passed in BO class as the target class
                    if (PropertyAccessorUtils.isNestedOrIndexedProperty(personReferenceObjectPropertyName)) {
                        int lastTargetIndex = PropertyAccessorUtils
                                .getLastNestedPropertySeparatorIndex(personReferenceObjectPropertyName);
                        String targetBusinessObjectPropertyName = lastTargetIndex != -1
                                ? StringUtils.substring(personReferenceObjectPropertyName, 0, lastTargetIndex)
                                : StringUtils.EMPTY;
                        DataObjectWrapper<BusinessObject> wrapper = KradDataServiceLocator.getDataObjectService()
                                .wrap(businessObject);
                        targetBusinessObject = (BusinessObject) wrapper
                                .getPropertyValueNullSafe(targetBusinessObjectPropertyName);
                        if (targetBusinessObject != null) {
                            targetBusinessObjectClass = targetBusinessObject.getClass();
                            resolvedPrincipalIdPropertyName.append(targetBusinessObjectPropertyName).append(".");
                        } else {
                            LOG.error("Could not find target property '" + propertyName + "' in class "
                                    + businessObject.getClass().getName() + ". Property value was null.");
                        }
                    } else { // not a nested Person property
                        targetBusinessObjectClass = businessObject.getClass();
                        targetBusinessObject = businessObject;
                    }

                    if (targetBusinessObjectClass != null) {
                        // use the relationship metadata in the KNS to determine the property on the
                        // host business object to put back into the map now that the principal ID
                        // (the value stored in application tables) has been resolved
                        int lastIndex = PropertyAccessorUtils
                                .getLastNestedPropertySeparatorIndex(personReferenceObjectPropertyName);
                        String propName = lastIndex != -1
                                ? StringUtils.substring(personReferenceObjectPropertyName, lastIndex + 1)
                                : personReferenceObjectPropertyName;
                        DataObjectRelationship rel = getBusinessObjectMetaDataService()
                                .getBusinessObjectRelationship(targetBusinessObject, propName);
                        if (rel != null) {
                            String sourcePrimitivePropertyName = rel
                                    .getParentAttributeForChildAttribute(KIMPropertyConstants.Person.PRINCIPAL_ID);
                            resolvedPrincipalIdPropertyName.append(sourcePrimitivePropertyName);
                            // get the principal - for translation of the principalName to principalId
                            String principalName = fieldValues.get(propertyName);
                            Principal principal = getIdentityService().getPrincipalByPrincipalName(principalName);
                            if (principal != null) {
                                processedFieldValues.put(resolvedPrincipalIdPropertyName.toString(),
                                        principal.getPrincipalId());
                            } else {
                                processedFieldValues.put(resolvedPrincipalIdPropertyName.toString(), null);
                                try {
                                    // if the principalName is bad, then we need to clear out the Person object
                                    // and base principalId property
                                    // so that their values are no longer accidentally used or re-populate
                                    // the object
                                    KRADUtils.setObjectProperty(targetBusinessObject,
                                            resolvedPrincipalIdPropertyName.toString(), null);
                                    KRADUtils.setObjectProperty(targetBusinessObject, propName, null);
                                    KRADUtils.setObjectProperty(targetBusinessObject, propName + ".principalName",
                                            principalName);
                                } catch (Exception ex) {
                                    LOG.error(
                                            "Unable to blank out the person object after finding that the person with the given principalName does not exist.",
                                            ex);
                                }
                            }
                        } else {
                            LOG.error("Missing relationship for " + propName + " on "
                                    + targetBusinessObjectClass.getName());
                        }
                    } else { // no target BO class - the code below probably will not work
                        processedFieldValues.put(resolvedPrincipalIdPropertyName.toString(), null);
                    }
                }
                // if the property does not seem to match the definition of a Person property but it
                // does end in principalName then...
                // this is to handle the case where the user ID is on an ADD line - a case excluded from isPersonProperty()
            } else if (propertyName.endsWith("." + KIMPropertyConstants.Person.PRINCIPAL_NAME)) {
                // if we're adding to a collection and we've got the principalName; let's populate universalUser
                String principalName = fieldValues.get(propertyName);
                if (StringUtils.isNotEmpty(principalName)) {
                    String containerPropertyName = propertyName;
                    if (containerPropertyName.startsWith(KRADConstants.MAINTENANCE_ADD_PREFIX)) {
                        containerPropertyName = StringUtils.substringAfter(propertyName,
                                KRADConstants.MAINTENANCE_ADD_PREFIX);
                    }
                    // get the class of the object that is referenced by the property name
                    // if this is not true then there's a principalName collection or primitive attribute
                    // directly on the BO on the add line, so we just ignore that since something is wrong here
                    if (PropertyAccessorUtils.isNestedOrIndexedProperty(containerPropertyName)) {
                        // the first part of the property is the collection name
                        String collectionName = StringUtils.substringBefore(containerPropertyName, ".");
                        // what is the class held by that collection?
                        // JHK: I don't like this.  This assumes that this method is only used by the maintenance
                        // document service.  If that will always be the case, this method should be moved over there.
                        Class<? extends BusinessObject> collectionBusinessObjectClass = getMaintenanceDocumentDictionaryService()
                                .getCollectionBusinessObjectClass(getMaintenanceDocumentDictionaryService()
                                        .getDocumentTypeName(businessObject.getClass()), collectionName);
                        if (collectionBusinessObjectClass != null) {
                            // we are adding to a collection; get the relationships for that object;
                            // is there one for personUniversalIdentifier?
                            List<DataObjectRelationship> relationships = getBusinessObjectMetaDataService()
                                    .getBusinessObjectRelationships(collectionBusinessObjectClass);
                            // JHK: this seems like a hack - looking at all relationships for a BO does not guarantee that we get the right one
                            // JHK: why not inspect the objects like above?  Is it the property path problems because of the .add. portion?
                            for (DataObjectRelationship rel : relationships) {
                                String parentAttribute = rel.getParentAttributeForChildAttribute(
                                        KIMPropertyConstants.Person.PRINCIPAL_ID);
                                if (parentAttribute == null) {
                                    continue;
                                }
                                // there is a relationship for personUserIdentifier; use that to find the universal user
                                processedFieldValues.remove(propertyName);
                                String fieldPrefix = StringUtils
                                        .substringBeforeLast(StringUtils.substringBeforeLast(propertyName,
                                                "." + KIMPropertyConstants.Person.PRINCIPAL_NAME), ".");
                                String relatedPrincipalIdPropertyName = fieldPrefix + "." + parentAttribute;
                                // KR-683 Special handling for extension objects
                                if (EXTENSION.equals(StringUtils.substringAfterLast(fieldPrefix, "."))
                                        && EXTENSION.equals(StringUtils.substringBefore(parentAttribute, "."))) {
                                    relatedPrincipalIdPropertyName = fieldPrefix + "."
                                            + StringUtils.substringAfter(parentAttribute, ".");
                                }
                                String currRelatedPersonPrincipalId = processedFieldValues
                                        .get(relatedPrincipalIdPropertyName);
                                if (StringUtils.isBlank(currRelatedPersonPrincipalId)) {
                                    Principal principal = getIdentityService()
                                            .getPrincipalByPrincipalName(principalName);
                                    if (principal != null) {
                                        processedFieldValues.put(relatedPrincipalIdPropertyName,
                                                principal.getPrincipalId());
                                    } else {
                                        processedFieldValues.put(relatedPrincipalIdPropertyName, null);
                                    }
                                }
                            } // relationship loop
                        } else {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug(
                                        "Unable to determine class for collection referenced as part of property: "
                                                + containerPropertyName + " on "
                                                + businessObject.getClass().getName());
                            }
                        }
                    } else {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("Non-nested property ending with 'principalName': " + containerPropertyName
                                    + " on " + businessObject.getClass().getName());
                        }
                    }
                }
            }
        }
        return processedFieldValues;
    }

    // OTHER METHODS

    protected IdentityService getIdentityService() {
        if (identityService == null) {
            identityService = KimApiServiceLocator.getIdentityService();
        }
        return identityService;
    }

    protected RoleService getRoleService() {
        if (roleService == null) {
            roleService = KimApiServiceLocator.getRoleService();
        }
        return roleService;
    }

    @Override
    public Class<? extends Person> getPersonImplementationClass() {
        return PersonImpl.class;
    }

    protected BusinessObjectMetaDataService getBusinessObjectMetaDataService() {
        if (businessObjectMetaDataService == null) {
            businessObjectMetaDataService = KNSServiceLocator.getBusinessObjectMetaDataService();
        }
        return businessObjectMetaDataService;
    }

    protected MaintenanceDocumentDictionaryService getMaintenanceDocumentDictionaryService() {
        if (maintenanceDocumentDictionaryService == null) {
            maintenanceDocumentDictionaryService = KNSServiceLocator.getMaintenanceDocumentDictionaryService();
        }
        return maintenanceDocumentDictionaryService;
    }
}