View Javadoc
1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kim.impl.identity;
17  
18  import java.security.GeneralSecurityException;
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.HashSet;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import org.apache.commons.beanutils.PropertyUtils;
30  import org.apache.commons.lang.StringUtils;
31  import org.apache.log4j.Logger;
32  import org.kuali.rice.core.api.CoreApiServiceLocator;
33  import org.kuali.rice.core.api.criteria.CountFlag;
34  import org.kuali.rice.core.api.criteria.Predicate;
35  import org.kuali.rice.core.api.criteria.PredicateUtils;
36  import org.kuali.rice.core.api.criteria.QueryByCriteria;
37  import org.kuali.rice.kim.api.identity.IdentityService;
38  import org.kuali.rice.kim.api.identity.Person;
39  import org.kuali.rice.kim.api.identity.PersonService;
40  import org.kuali.rice.kim.api.identity.entity.EntityDefault;
41  import org.kuali.rice.kim.api.identity.entity.EntityDefaultQueryResults;
42  import org.kuali.rice.kim.api.identity.external.EntityExternalIdentifierType;
43  import org.kuali.rice.kim.api.identity.principal.Principal;
44  import org.kuali.rice.kim.api.identity.type.EntityTypeContactInfoDefault;
45  import org.kuali.rice.kim.api.role.RoleService;
46  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
47  import org.kuali.rice.kim.impl.KIMPropertyConstants;
48  import org.kuali.rice.kim.impl.identity.principal.PrincipalBo;
49  import org.kuali.rice.kns.service.BusinessObjectMetaDataService;
50  import org.kuali.rice.kns.service.KNSServiceLocator;
51  import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
52  import org.kuali.rice.krad.bo.BusinessObject;
53  import org.kuali.rice.krad.bo.DataObjectRelationship;
54  import org.kuali.rice.krad.data.DataObjectWrapper;
55  import org.kuali.rice.krad.data.KradDataServiceLocator;
56  import org.kuali.rice.krad.lookup.CollectionIncomplete;
57  import org.kuali.rice.krad.util.KRADConstants;
58  import org.kuali.rice.krad.util.KRADPropertyConstants;
59  import org.kuali.rice.krad.util.KRADUtils;
60  import org.springframework.beans.PropertyAccessorUtils;
61  
62  /**
63   * This is a description of what this class does - kellerj don't forget to fill this in.
64   *
65   * @author Kuali Rice Team (rice.collab@kuali.org)
66   *
67   */
68  public class PersonServiceImpl implements PersonService {
69  
70  	private static Logger LOG = Logger.getLogger( PersonServiceImpl.class );
71  	protected static final String ENTITY_EXT_ID_PROPERTY_PREFIX = "externalIdentifiers.";
72  	protected static final String ENTITY_AFFILIATION_PROPERTY_PREFIX = "affiliations.";
73  	protected static final String ENTITY_TYPE_PROPERTY_PREFIX = "entityTypeContactInfos.";
74  	protected static final String ENTITY_EMAIL_PROPERTY_PREFIX = "entityTypeContactInfos.emailAddresses.";
75  	protected static final String ENTITY_PHONE_PROPERTY_PREFIX = "entityTypeContactInfos.phoneNumbers.";
76  	protected static final String ENTITY_ADDRESS_PROPERTY_PREFIX = "entityTypeContactInfos.addresses.";
77  	protected static final String ENTITY_NAME_PROPERTY_PREFIX = "names.";
78  	protected static final String PRINCIPAL_PROPERTY_PREFIX = "principals.";
79  	protected static final String ENTITY_EMPLOYEE_ID_PROPERTY_PREFIX = "employmentInformation.";
80  	// KULRICE-4442 Special handling for extension objects
81  	protected static final String EXTENSION = "extension";
82  
83  	private IdentityService identityService;
84  	private RoleService roleService;
85  	private BusinessObjectMetaDataService businessObjectMetaDataService;
86  	private MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService;
87  
88  	protected List<String> personEntityTypeCodes = new ArrayList<String>( 4 );
89  	// String that can be passed to the lookup framework to create an type = X OR type = Y criteria
90  	private String personEntityTypeLookupCriteria = null;
91  
92  	protected Map<String,String> baseLookupCriteria = new HashMap<String,String>();
93  	protected Map<String,String> criteriaConversion = new HashMap<String,String>();
94  	protected ArrayList<String> personCachePropertyNames = new ArrayList<String>();
95  	{
96  		// init the criteria which will need to be applied to every lookup against
97  		// the identity data tables
98  		baseLookupCriteria.put( KIMPropertyConstants.Person.ACTIVE, "Y" );
99  		baseLookupCriteria.put( ENTITY_TYPE_PROPERTY_PREFIX + KRADPropertyConstants.ACTIVE, "Y" );
100 
101 		// create the field mappings between the Person object and the KimEntity object
102 		criteriaConversion.put( KIMPropertyConstants.Person.ENTITY_ID, KIMPropertyConstants.Entity.ID);
103 		criteriaConversion.put( KIMPropertyConstants.Person.ACTIVE, PRINCIPAL_PROPERTY_PREFIX + KRADPropertyConstants.ACTIVE );
104 		criteriaConversion.put( KIMPropertyConstants.Person.PRINCIPAL_ID, PRINCIPAL_PROPERTY_PREFIX + KIMPropertyConstants.Person.PRINCIPAL_ID );
105 		criteriaConversion.put( KIMPropertyConstants.Person.PRINCIPAL_NAME, PRINCIPAL_PROPERTY_PREFIX + KIMPropertyConstants.Person.PRINCIPAL_NAME );
106 		criteriaConversion.put( KIMPropertyConstants.Person.FIRST_NAME, "names.firstName" );
107 		criteriaConversion.put( KIMPropertyConstants.Person.LAST_NAME, "names.lastName" );
108 		criteriaConversion.put( KIMPropertyConstants.Person.MIDDLE_NAME, "names.middleName" );
109 		criteriaConversion.put( KIMPropertyConstants.Person.EMAIL_ADDRESS, "entityTypeContactInfos.emailAddresses.emailAddress" );
110 		criteriaConversion.put( KIMPropertyConstants.Person.PHONE_NUMBER, "entityTypeContactInfos.phoneNumbers.phoneNumber" );
111 		criteriaConversion.put( KIMPropertyConstants.Person.ADDRESS_LINE_1, "entityTypeContactInfos.addresses.line1" );
112 		criteriaConversion.put( KIMPropertyConstants.Person.ADDRESS_LINE_2, "entityTypeContactInfos.addresses.line2" );
113 		criteriaConversion.put( KIMPropertyConstants.Person.ADDRESS_LINE_3, "entityTypeContactInfos.addresses.line3" );
114 		criteriaConversion.put( KIMPropertyConstants.Person.CITY, "entityTypeContactInfos.addresses.city" );
115 		criteriaConversion.put( KIMPropertyConstants.Person.STATE_CODE, "entityTypeContactInfos.addresses.stateProvinceCode" );
116 		criteriaConversion.put( KIMPropertyConstants.Person.POSTAL_CODE, "entityTypeContactInfos.addresses.postalCode" );
117 		criteriaConversion.put( KIMPropertyConstants.Person.COUNTRY_CODE, "entityTypeContactInfos.addresses.countryCode" );
118 		criteriaConversion.put( KIMPropertyConstants.Person.CAMPUS_CODE, "affiliations.campusCode" );
119 		criteriaConversion.put( KIMPropertyConstants.Person.AFFILIATION_TYPE_CODE, "affiliations.affiliationTypeCode" );
120 		criteriaConversion.put( KIMPropertyConstants.Person.EXTERNAL_IDENTIFIER_TYPE_CODE, "externalIdentifiers.externalIdentifierTypeCode" );
121 		criteriaConversion.put( KIMPropertyConstants.Person.EXTERNAL_ID, "externalIdentifiers.externalId" );
122 		criteriaConversion.put( KIMPropertyConstants.Person.EMPLOYEE_TYPE_CODE, "employmentInformation.employeeTypeCode" );
123 		criteriaConversion.put( KIMPropertyConstants.Person.EMPLOYEE_STATUS_CODE, "employmentInformation.employeeStatusCode" );
124 		criteriaConversion.put( KIMPropertyConstants.Person.EMPLOYEE_ID, "employmentInformation.employeeId" );
125 		criteriaConversion.put( KIMPropertyConstants.Person.BASE_SALARY_AMOUNT, "employmentInformation.baseSalaryAmount" );
126 		criteriaConversion.put( KIMPropertyConstants.Person.PRIMARY_DEPARTMENT_CODE, "employmentInformation.primaryDepartmentCode" );
127 
128 		personCachePropertyNames.add( KIMPropertyConstants.Person.PRINCIPAL_ID );
129 		personCachePropertyNames.add( KIMPropertyConstants.Person.PRINCIPAL_NAME );
130 		personCachePropertyNames.add( KIMPropertyConstants.Person.ENTITY_ID );
131 		personCachePropertyNames.add( KIMPropertyConstants.Person.FIRST_NAME );
132 		personCachePropertyNames.add( KIMPropertyConstants.Person.LAST_NAME );
133 		personCachePropertyNames.add( KIMPropertyConstants.Person.MIDDLE_NAME );
134 		personCachePropertyNames.add( KIMPropertyConstants.Person.CAMPUS_CODE );
135 		personCachePropertyNames.add( KIMPropertyConstants.Person.EMPLOYEE_ID );
136 		personCachePropertyNames.add( KIMPropertyConstants.Person.PRIMARY_DEPARTMENT_CODE );
137 	}
138 
139 
140 	/**
141 	 * @see org.kuali.rice.kim.api.identity.PersonService#getPerson(java.lang.String)
142 	 */
143 	@Override
144     public Person getPerson(String principalId) {
145 		if ( StringUtils.isBlank(principalId) ) {
146 			return null;
147 		}
148 
149 		// get the corresponding principal
150 		final Principal principal = getIdentityService().getPrincipal( principalId );
151 		// get the identity
152 		if ( principal != null ) {
153 			final EntityDefault entity = getIdentityService().getEntityDefault(principal.getEntityId());
154          	// convert the principal and identity to a Person
155             // skip if the person was created from the DB cache
156             if (entity != null ) {
157                 return convertEntityToPerson( entity, principal );
158             }
159 		}
160 		return null;
161 	}
162 
163 	protected PersonImpl convertEntityToPerson( EntityDefault entity, Principal principal ) {
164 		try {
165 			// get the EntityEntityType for the EntityType corresponding to a Person
166 			for ( String entityTypeCode : personEntityTypeCodes ) {
167 				EntityTypeContactInfoDefault entType = entity.getEntityType( entityTypeCode );
168 				// if no "person" identity type present for the given principal, skip to the next type in the list
169 				if ( entType == null ) {
170 					continue;
171 				}
172 				// attach the principal and identity objects
173 				// PersonImpl has logic to pull the needed elements from the KimEntity-related classes
174 				return new PersonImpl( principal, entity, entityTypeCode );
175 			}
176 			return null;
177 		} catch ( Exception ex ) {
178 			// allow runtime exceptions to pass through
179 			if ( ex instanceof RuntimeException ) {
180 				throw (RuntimeException)ex;
181 			}
182 			throw new RuntimeException( "Problem building person object", ex );
183 		}
184 	}
185 
186 
187 	/**
188 	 * @see org.kuali.rice.kim.api.identity.PersonService#getPersonByPrincipalName(java.lang.String)
189 	 */
190 	@Override
191     public Person getPersonByPrincipalName(String principalName) {
192 		if ( StringUtils.isBlank(principalName) ) {
193 			return null;
194 		}
195 
196 		// get the corresponding principal
197 		final Principal principal = getIdentityService().getPrincipalByPrincipalName( principalName );
198 		// get the identity
199 		if ( principal != null ) {
200             final EntityDefault entity = getIdentityService().getEntityDefault(principal.getEntityId());
201 
202             // convert the principal and identity to a Person
203             if ( entity != null ) {
204                 return convertEntityToPerson( entity, principal );
205             }
206 		}
207 		return null;
208 	}
209 
210 	@Override
211     public Person getPersonByEmployeeId(String employeeId) {
212 		if ( StringUtils.isBlank( employeeId  ) ) {
213 			return null;
214 		}
215 
216 		final List<Person> people = findPeople( Collections.singletonMap(KIMPropertyConstants.Person.EMPLOYEE_ID, employeeId) );
217 		if ( !people.isEmpty() ) {
218 			return people.get(0);
219 
220 		}
221 
222 	    // If no person was found above, check for inactive records
223         EntityDefault entity = getIdentityService().getEntityDefaultByEmployeeId(employeeId);
224         if (entity != null) {
225             if ( !entity.getPrincipals().isEmpty() ) {
226                 Principal principal = getIdentityService().getPrincipal(entity.getPrincipals().get(0).getPrincipalId());
227                 if (principal != null) {
228                     return convertEntityToPerson( entity, principal );
229                 }
230             }
231         }
232 
233 		return null;
234 	}
235 
236 	/**
237 	 * @see org.kuali.rice.kim.api.identity.PersonService#findPeople(Map)
238 	 */
239 	@Override
240     public List<Person> findPeople(Map<String, String> criteria) {
241 		return findPeople(criteria, true);
242 	}
243 
244 	/**
245 	 * @see org.kuali.rice.kim.api.identity.PersonService#findPeople(java.util.Map, boolean)
246 	 */
247 	@Override
248     public List<Person> findPeople(Map<String, String> criteria, boolean unbounded) {
249 		List<Person> people = null;
250 		// protect from NPEs
251 		if ( criteria == null ) {
252 			criteria = Collections.emptyMap();
253 		}
254 		// make a copy so it can be modified safely in this method
255 		criteria = new HashMap<String, String>( criteria );
256 
257 		// extract the role lookup parameters and then remove them since later code will not know what to do with them
258 		String roleName = criteria.get( "lookupRoleName" );
259 		String namespaceCode = criteria.get( "lookupRoleNamespaceCode" );
260 		criteria.remove("lookupRoleName");
261 		criteria.remove("lookupRoleNamespaceCode");
262 		if ( StringUtils.isNotBlank(namespaceCode) && StringUtils.isNotBlank(roleName) ) {
263 			Integer searchResultsLimit = org.kuali.rice.kns.lookup.LookupUtils.getSearchResultsLimit(PersonImpl.class);
264 			int searchResultsLimitInt = Integer.MAX_VALUE;
265 			if (searchResultsLimit != null) {
266 				searchResultsLimitInt = searchResultsLimit.intValue();
267 			}
268 			if ( LOG.isDebugEnabled() ) {
269 				LOG.debug("Performing Person search including role filter: " + namespaceCode + "/" + roleName );
270 			}
271 			if ( criteria.size() == 1 && criteria.containsKey(KIMPropertyConstants.Person.ACTIVE) ) { // if only active is specified
272 				if ( LOG.isDebugEnabled() ) {
273 					LOG.debug( "Only active criteria specified, running role search first" );
274 				}
275 				// in this case, run the role lookup first and pass those results to the person lookup
276 				Collection<String> principalIds = getRoleService().getRoleMemberPrincipalIds(namespaceCode, roleName,  Collections.<String, String>emptyMap());
277 				StringBuffer sb = new StringBuffer(principalIds.size()*15);
278 				Iterator<String> pi = principalIds.iterator();
279 				while ( pi.hasNext() ) {
280 					sb.append( pi.next() );
281 					if ( pi.hasNext() ) {
282                         sb.append( '|' );
283                     }
284 				}
285 				// add the list of principal IDs to the lookup so that only matching Person objects are returned
286 				criteria.put( KIMPropertyConstants.Person.PRINCIPAL_ID, sb.toString() );
287 				people = findPeopleInternal(criteria, false); // can allow internal method to filter here since no more filtering necessary
288 			} else if ( !criteria.isEmpty() ) { // i.e., person criteria are specified
289 				if ( LOG.isDebugEnabled() ) {
290 					LOG.debug( "Person criteria also specified, running that search first" );
291 				}
292 				// run the person lookup first
293 				people = findPeopleInternal(criteria, true); // get all, since may need to be filtered
294 				// TODO - now check if these people have the given role
295 				// build a principal list
296 				List<String> principalIds = peopleToPrincipalIds( people );
297 				// get sublist of principals that have the given roles
298 				principalIds = getRoleService().getPrincipalIdSubListWithRole(principalIds, namespaceCode, roleName,  Collections.<String, String>emptyMap());
299 				// re-convert into people objects, wrapping in CollectionIncomplete if needed
300 				if ( !unbounded && principalIds.size() > searchResultsLimitInt ) {
301 					int actualResultSize = principalIds.size();
302 					// trim the list down before converting to people
303 					principalIds = new ArrayList<String>(principalIds).subList(0, searchResultsLimitInt); // yes, this is a little wasteful
304 					people = getPeople(principalIds); // convert the results to people
305 					people = new CollectionIncomplete<Person>( people.subList(0, searchResultsLimitInt), new Long(actualResultSize) );
306 				} else {
307 					people = getPeople(principalIds);
308 				}
309 			} else { // only role criteria specified
310 				if ( LOG.isDebugEnabled() ) {
311 					LOG.debug( "No Person criteria specified - only using role service." );
312 				}
313 				// run the role criteria to get the principals with the role
314 				Collection<String> principalIds = getRoleService().getRoleMemberPrincipalIds(namespaceCode, roleName,  Collections.<String, String>emptyMap());
315 				if ( !unbounded && principalIds.size() > searchResultsLimitInt ) {
316 					int actualResultSize = principalIds.size();
317 					// trim the list down before converting to people
318 					principalIds = new ArrayList<String>(principalIds).subList(0, searchResultsLimitInt); // yes, this is a little wasteful
319 					people = getPeople(principalIds); // convert the results to people
320 					people = new CollectionIncomplete<Person>( people.subList(0, searchResultsLimitInt), new Long(actualResultSize) );
321 				} else {
322 					people = getPeople(principalIds); // convert the results to people
323 				}
324 			}
325 		} else {
326 			if ( LOG.isDebugEnabled() ) {
327 				LOG.debug( "No Role criteria specified, running person lookup as normal." );
328 			}
329 			people = findPeopleInternal(criteria, unbounded);
330 		}
331 
332 		// The following change is for KULRICE-5694 - It prevents duplicate rows from being returned for the
333 		// person inquiry (In this case, duplicate meaning same entityId, principalId, and principalNm).
334 		// This allows for multiple rows to be returned if an entityID has more then one principal name
335 		// or more than one principal ID.
336         Set<String> peopleNoDupsSet = new HashSet<String>();
337         List<Person> peopleNoDupsList = new ArrayList<Person>();
338 
339 	    for (Iterator<Person> iter = people.iterator(); iter.hasNext(); ) {
340 	        Person person = iter.next();
341 	        if (peopleNoDupsSet.add(person.getEntityId() + person.getPrincipalId() + person.getPrincipalName())) {
342 	            peopleNoDupsList.add(person);
343 	        }
344 	    }
345 
346 	    people.clear();
347 	    people.addAll(peopleNoDupsList);
348 
349 	    return people;
350 	}
351 
352 	@SuppressWarnings("unchecked")
353 	protected List<Person> findPeopleInternal(Map<String,String> criteria, boolean unbounded ) {
354 		// convert the criteria to a form that can be used by the ORM layer
355 
356         //TODO convert this to the new criteria predicates
357 		Map<String,String> entityCriteria = convertPersonPropertiesToEntityProperties( criteria );
358 
359         Predicate predicate = PredicateUtils.convertMapToPredicate(entityCriteria);
360 
361         QueryByCriteria.Builder queryBuilder = QueryByCriteria.Builder.create();
362         queryBuilder.setPredicates(predicate);
363 
364 		if (!unbounded) {
365 			Integer searchResultsLimit = org.kuali.rice.kns.lookup.LookupUtils.getSearchResultsLimit(PersonImpl.class);
366     		if (searchResultsLimit != null && searchResultsLimit >= 0) {
367     			queryBuilder.setMaxResults(searchResultsLimit);
368     			queryBuilder.setCountFlag(CountFlag.INCLUDE);
369 			}
370 		}
371 
372 		List<Person> people = new ArrayList<Person>();
373 
374 		EntityDefaultQueryResults qr = getIdentityService().findEntityDefaults( queryBuilder.build() );
375 
376         if (qr.getResults().size() > 0) {
377 
378             for ( EntityDefault e : qr.getResults() ) {
379 			    // get to get all principals for the identity as well
380                 if (e.getPrincipals().isEmpty()) {
381                     PrincipalBo principalBo = new PrincipalBo();
382                     principalBo.setActive(false);
383                     principalBo.setEntityId(e.getEntityId());
384                     principalBo.setPrincipalName("No Principal Name");
385                     // If the principal ID is not set, the person inquiry will not work
386                     principalBo.setPrincipalId(e.getEntityId());
387                     people.add( convertEntityToPerson( e, PrincipalBo.to(principalBo) ) );
388                 } else {
389                     for ( Principal p : e.getPrincipals() ) {
390                         people.add( convertEntityToPerson( e, p ) );
391                     }
392                 }
393 		    }
394         } else if (!qr.isMoreResultsAvailable() && entityCriteria.containsKey("principals.principalId")) {
395             HashMap<String,String> newEntityCriteria = new HashMap<String,String>();
396             String principalID = entityCriteria.get("principals.principalId");
397             newEntityCriteria.put( KIMPropertyConstants.Person.ACTIVE, "Y" );
398             newEntityCriteria.put(KIMPropertyConstants.Entity.ID, principalID);
399 
400             Predicate newPredicate = PredicateUtils.convertMapToPredicate(newEntityCriteria);
401             QueryByCriteria.Builder newQueryBuilder = QueryByCriteria.Builder.create();
402             newQueryBuilder.setMaxResults(1);
403             newQueryBuilder.setCountFlag(CountFlag.INCLUDE);
404             newQueryBuilder.setPredicates(newPredicate);
405             EntityDefaultQueryResults newQr = getIdentityService().findEntityDefaults( newQueryBuilder.build() );
406             String activeCriteria = entityCriteria.get(KIMPropertyConstants.Entity.ACTIVE);
407             if (activeCriteria != null && activeCriteria.equalsIgnoreCase(KRADConstants.YES_INDICATOR_VALUE)) {
408                 if (newQr.getResults().size() > 0) {
409                     for ( EntityDefault e : newQr.getResults() ) {
410                         if (e.getPrincipals().isEmpty()) {
411                             PrincipalBo principalBo = new PrincipalBo();
412                             principalBo.setActive(false);
413                             principalBo.setEntityId(e.getEntityId());
414                             principalBo.setPrincipalName("No Principal Name");
415                             // if the principal ID is not set, the person inquiry will not work
416                             principalBo.setPrincipalId(e.getEntityId());
417                             people.add( convertEntityToPerson( e, PrincipalBo.to(principalBo) ) );
418                         }
419                     }
420                 }
421             } else if (!(entityCriteria.containsKey(KIMPropertyConstants.Person.ACTIVE)) || (criteria.get(KIMPropertyConstants.Person.ACTIVE).equals("N"))) {
422                 if (newQr.getResults().isEmpty()) {
423 
424                     String principalId =  entityCriteria.get("principals.principalId");
425                     try {
426                         EntityDefault entityDefault = getIdentityService().getEntityDefaultByPrincipalId(principalId);
427                         for ( Principal p : entityDefault.getPrincipals() ) {
428                             if (!p.isActive()){
429                                 people.add( convertEntityToPerson(entityDefault, p ) );
430                             }
431                         }
432                     } catch ( Exception e ) {
433                         LOG.info( "A principal Id of " + principalId + " dose not exist in the system");
434                     }
435                 }
436             }
437         } else if (!qr.isMoreResultsAvailable() &&  entityCriteria.containsKey("principals.principalName")) {
438             if (!(entityCriteria.containsKey(KIMPropertyConstants.Person.ACTIVE)) || (criteria.get(KIMPropertyConstants.Person.ACTIVE).equals("N"))) {
439 
440                 HashMap<String,String> newEntityCriteria = new HashMap<String,String>();
441                 String principalName = entityCriteria.get("principals.principalName");
442                 newEntityCriteria.put( KIMPropertyConstants.Person.ACTIVE, KRADConstants.YES_INDICATOR_VALUE );
443                 newEntityCriteria.put("principals.principalName", principalName);
444 
445                 Predicate newPredicate = PredicateUtils.convertMapToPredicate(newEntityCriteria);
446                 QueryByCriteria.Builder newQueryBuilder = QueryByCriteria.Builder.create();
447                 newQueryBuilder.setMaxResults(1);
448                 newQueryBuilder.setCountFlag(CountFlag.INCLUDE);
449                 newQueryBuilder.setPredicates(newPredicate);
450                 EntityDefaultQueryResults newQr = getIdentityService().findEntityDefaults( newQueryBuilder.build() );
451 
452                 if (newQr.getResults().isEmpty()) {
453                     String principalNm =  entityCriteria.get("principals.principalName");
454                     try {
455                         EntityDefault entityDefault = getIdentityService().getEntityDefaultByPrincipalName(principalNm);
456                         for ( Principal p : entityDefault.getPrincipals() ) {
457                             if (!p.isActive()){
458                                 people.add( convertEntityToPerson(entityDefault, p ) );
459                             }
460                         }
461                     } catch ( Exception e ) {
462                         LOG.info( "A principal name of " + principalNm + " dose not exist in the system");
463                     }
464                 }
465             }
466         }
467         return people;
468 	}
469 
470 	public Map<String,String> convertPersonPropertiesToEntityProperties( Map<String,String> criteria ) {
471 		if ( LOG.isDebugEnabled() ) {
472 			LOG.debug( "convertPersonPropertiesToEntityProperties: " + criteria );
473 		}
474 		boolean nameCriteria = false;
475 		boolean addressCriteria = false;
476 		boolean externalIdentifierCriteria = false;
477 		boolean affiliationCriteria = false;
478 		boolean affiliationDefaultOnlyCriteria = false;
479 		boolean phoneCriteria = false;
480 		boolean emailCriteria = false;
481 		boolean employeeIdCriteria = false;
482 		// add base lookups for all person lookups
483 		HashMap<String,String> newCriteria = new HashMap<String,String>();
484 		newCriteria.putAll( baseLookupCriteria );
485 
486 		newCriteria.put( "entityTypeContactInfos.entityTypeCode", personEntityTypeLookupCriteria );
487 
488         if ( criteria != null ) {
489 			for ( String key : criteria.keySet() ) {
490 			    //check active radio button
491 	            // The following if statement enables the "both" button to work correctly.
492 	            if (!(criteria.containsKey(KIMPropertyConstants.Person.ACTIVE))) {
493 	                newCriteria.remove( KIMPropertyConstants.Person.ACTIVE );
494 	            }
495 
496 
497 				// if no value was passed, skip the entry in the Map
498 				if ( StringUtils.isEmpty( criteria.get(key) ) ) {
499 					continue;
500 				}
501 				// check if the value needs to be encrypted
502 				// handle encrypted external identifiers
503 				if ( key.equals( KIMPropertyConstants.Person.EXTERNAL_ID ) && StringUtils.isNotBlank(criteria.get(key)) ) {
504 					// look for a ext ID type property
505 					if ( criteria.containsKey( KIMPropertyConstants.Person.EXTERNAL_IDENTIFIER_TYPE_CODE ) ) {
506 						String extIdTypeCode = criteria.get(KIMPropertyConstants.Person.EXTERNAL_IDENTIFIER_TYPE_CODE);
507 						if ( StringUtils.isNotBlank(extIdTypeCode) ) {
508 							// if found, load that external ID Type via service
509 							EntityExternalIdentifierType extIdType = getIdentityService().getExternalIdentifierType(extIdTypeCode);
510 							// if that type needs to be encrypted, encrypt the value in the criteria map
511 							if ( extIdType != null && extIdType.isEncryptionRequired() ) {
512 								try {
513                                     if(CoreApiServiceLocator.getEncryptionService().isEnabled()) {
514 									    criteria.put(key,
515 										    	CoreApiServiceLocator.getEncryptionService().encrypt(criteria.get(key))
516 										    	);
517                                     }
518 								} catch (GeneralSecurityException ex) {
519 									LOG.error("Unable to encrypt value for external ID search of type " + extIdTypeCode, ex );
520 								}
521 							}
522 						}
523 					}
524 				}
525 
526 				// convert the property to the Entity data model
527 				String entityProperty = criteriaConversion.get( key );
528 				if ( entityProperty != null ) {
529 					newCriteria.put( entityProperty, criteria.get( key ) );
530 				} else {
531 					entityProperty = key;
532 					// just pass it through if no translation present
533 					newCriteria.put( key, criteria.get( key ) );
534 				}
535 				// check if additional criteria are needed based on the types of properties specified
536 				if ( isNameEntityCriteria( entityProperty ) ) {
537 					nameCriteria = true;
538 				}
539 				if ( isExternalIdentifierEntityCriteria( entityProperty ) ) {
540 					externalIdentifierCriteria = true;
541 				}
542 				if ( isAffiliationEntityCriteria( entityProperty ) ) {
543 					affiliationCriteria = true;
544 				}
545 				if ( isAddressEntityCriteria( entityProperty ) ) {
546 					addressCriteria = true;
547 				}
548 				if ( isPhoneEntityCriteria( entityProperty ) ) {
549 					phoneCriteria = true;
550 				}
551 				if ( isEmailEntityCriteria( entityProperty ) ) {
552 					emailCriteria = true;
553 				}
554 				if ( isEmployeeIdEntityCriteria( entityProperty ) ) {
555 					employeeIdCriteria = true;
556 				}
557 				// special handling for the campus code, since that forces the query to look
558 				// at the default affiliation record only
559 				if ( key.equals( "campusCode" ) ) {
560 					affiliationDefaultOnlyCriteria = true;
561 				}
562 			}
563 
564 			if ( nameCriteria ) {
565 				newCriteria.put( ENTITY_NAME_PROPERTY_PREFIX + "active", "Y" );
566 				newCriteria.put( ENTITY_NAME_PROPERTY_PREFIX + "defaultValue", "Y" );
567 				//newCriteria.put(ENTITY_NAME_PROPERTY_PREFIX + "nameCode", "PRFR");//so we only display 1 result
568 			}
569 			if ( addressCriteria ) {
570 				newCriteria.put( ENTITY_ADDRESS_PROPERTY_PREFIX + "active", "Y" );
571 				newCriteria.put( ENTITY_ADDRESS_PROPERTY_PREFIX + "defaultValue", "Y" );
572 			}
573 			if ( phoneCriteria ) {
574 				newCriteria.put( ENTITY_PHONE_PROPERTY_PREFIX + "active", "Y" );
575 				newCriteria.put( ENTITY_PHONE_PROPERTY_PREFIX + "defaultValue", "Y" );
576 			}
577 			if ( emailCriteria ) {
578 				newCriteria.put( ENTITY_EMAIL_PROPERTY_PREFIX + "active", "Y" );
579 				newCriteria.put( ENTITY_EMAIL_PROPERTY_PREFIX + "defaultValue", "Y" );
580 			}
581 			if ( employeeIdCriteria ) {
582 				newCriteria.put( ENTITY_EMPLOYEE_ID_PROPERTY_PREFIX + "active", "Y" );
583 				newCriteria.put( ENTITY_EMPLOYEE_ID_PROPERTY_PREFIX + "primary", "Y" );
584                 //KULRICE-12405: There is no reason to verify the person is a system or person when searching by only empl ID.
585                 // Do not check the KRIM_ENTITY_ENT_TYP_T table if the employeeId is the only criteria passed in.
586                 if (criteria.size() == 1) {
587                     newCriteria.remove("entityTypeContactInfos.active");
588                     newCriteria.remove("entityTypeContactInfos.entityTypeCode");
589                 }
590 			}
591 			if ( affiliationCriteria ) {
592 				newCriteria.put( ENTITY_AFFILIATION_PROPERTY_PREFIX + "active", "Y" );
593 			}
594 			if ( affiliationDefaultOnlyCriteria ) {
595 				newCriteria.put( ENTITY_AFFILIATION_PROPERTY_PREFIX + "defaultValue", "Y" );
596 			}
597         }
598 
599 		if ( LOG.isDebugEnabled() ) {
600 			LOG.debug("Converted: " + newCriteria);
601 		}
602 		return newCriteria;
603 	}
604 
605 	protected boolean isNameEntityCriteria( String propertyName ) {
606 		return propertyName.startsWith( ENTITY_NAME_PROPERTY_PREFIX );
607 	}
608 	protected boolean isAddressEntityCriteria( String propertyName ) {
609 		return propertyName.startsWith( ENTITY_ADDRESS_PROPERTY_PREFIX );
610 	}
611 	protected boolean isPhoneEntityCriteria( String propertyName ) {
612 		return propertyName.startsWith( ENTITY_PHONE_PROPERTY_PREFIX );
613 	}
614 	protected boolean isEmailEntityCriteria( String propertyName ) {
615 		return propertyName.startsWith( ENTITY_EMAIL_PROPERTY_PREFIX );
616 	}
617 	protected boolean isEmployeeIdEntityCriteria( String propertyName ) {
618 		return propertyName.startsWith( ENTITY_EMPLOYEE_ID_PROPERTY_PREFIX );
619 	}
620 	protected boolean isAffiliationEntityCriteria( String propertyName ) {
621 		return propertyName.startsWith( ENTITY_AFFILIATION_PROPERTY_PREFIX );
622 	}
623 	protected boolean isExternalIdentifierEntityCriteria( String propertyName ) {
624 		return propertyName.startsWith( ENTITY_EXT_ID_PROPERTY_PREFIX );
625 	}
626 
627 	/**
628 	 * Get the entityTypeCode that can be associated with a Person.  This will determine
629 	 * where EntityType-related data is pulled from within the KimEntity object.  The codes
630 	 * in the list will be examined in the order present.
631 	 */
632 	public List<String> getPersonEntityTypeCodes() {
633 		return this.personEntityTypeCodes;
634 	}
635 
636 	public void setPersonEntityTypeCodes(List<String> personEntityTypeCodes) {
637 		this.personEntityTypeCodes = personEntityTypeCodes;
638 		personEntityTypeLookupCriteria = null;
639 		for ( String entityTypeCode : personEntityTypeCodes ) {
640 			if ( personEntityTypeLookupCriteria == null ) {
641 				personEntityTypeLookupCriteria = entityTypeCode;
642 			} else {
643 				personEntityTypeLookupCriteria = personEntityTypeLookupCriteria + "|" + entityTypeCode;
644 			}
645 		}
646 	}
647 
648 
649 	protected List<Person> getPeople( Collection<String> principalIds ) {
650 		List<Person> people = new ArrayList<Person>( principalIds.size() );
651 		for ( String principalId : principalIds ) {
652 			people.add( getPerson(principalId) );
653 		}
654 		return people;
655 	}
656 
657 	protected List<String> peopleToPrincipalIds( List<Person> people ) {
658 		List<String> principalIds = new ArrayList<String>();
659 
660 		for ( Person person : people ) {
661 			principalIds.add( person.getPrincipalId() );
662 		}
663 
664 		return principalIds;
665 	}
666 
667 	/**
668 	 * @see org.kuali.rice.kim.api.identity.PersonService#getPersonByExternalIdentifier(java.lang.String, java.lang.String)
669 	 */
670 	@Override
671     public List<Person> getPersonByExternalIdentifier(String externalIdentifierTypeCode, String externalId) {
672 		if (StringUtils.isBlank( externalIdentifierTypeCode ) || StringUtils.isBlank( externalId ) ) {
673 			return null;
674 		}
675 		Map<String,String> criteria = new HashMap<String,String>( 2 );
676 		criteria.put( KIMPropertyConstants.Person.EXTERNAL_IDENTIFIER_TYPE_CODE, externalIdentifierTypeCode );
677 		criteria.put( KIMPropertyConstants.Person.EXTERNAL_ID, externalId );
678 		return findPeople( criteria );
679 	}
680 
681 	/**
682 	 * @see org.kuali.rice.kim.api.identity.PersonService#updatePersonIfNecessary(java.lang.String, org.kuali.rice.kim.api.identity.Person)
683 	 */
684     @Override
685     public Person updatePersonIfNecessary(String sourcePrincipalId, Person currentPerson ) {
686         if (currentPerson  == null // no person set
687                 || !StringUtils.equals(sourcePrincipalId, currentPerson.getPrincipalId() ) // principal ID mismatch
688                 || currentPerson.getEntityId() == null ) { // syntheticially created Person object
689             Person person = getPerson( sourcePrincipalId );
690             // if a synthetically created person object is present, leave it - required for property derivation and the UI layer for
691             // setting the principal name
692             if ( person == null ) {
693                 if ( currentPerson != null && currentPerson.getEntityId() == null ) {
694                     return currentPerson;
695                 }
696             }
697             // if both are null, create an empty object for property derivation
698             if ( person == null && currentPerson == null ) {
699             	try {
700             		return new PersonImpl();
701             	} catch ( Exception ex ) {
702             		LOG.error( "unable to instantiate an object of type: " + getPersonImplementationClass() + " - returning null", ex );
703             		return null;
704             	}
705             }
706             return person;
707         }
708         // otherwise, no need to change the given object
709         return currentPerson;
710     }
711 
712     /**
713      * Builds a map containing entries from the passed in Map that do NOT represent properties on an embedded
714      * Person object.
715      */
716     private Map<String,String> getNonPersonSearchCriteria( Object bo, Map<String,String> fieldValues) {
717         Map<String,String> nonUniversalUserSearchCriteria = new HashMap<String,String>();
718         for ( String propertyName : fieldValues.keySet() ) {
719             if (!isPersonProperty(bo, propertyName)) {
720                 nonUniversalUserSearchCriteria.put(propertyName, fieldValues.get(propertyName));
721             }
722         }
723         return nonUniversalUserSearchCriteria;
724     }
725 
726 
727     private boolean isPersonProperty(Object bo, String propertyName) {
728         try {
729         	if (PropertyAccessorUtils.isNestedOrIndexedProperty( propertyName ) // is a nested property
730             		&& !StringUtils.contains(propertyName, "add.") ) {// exclude add line properties (due to path parsing problems in PropertyUtils.getPropertyType)
731                 int lastIndex = PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(propertyName);
732                 String propertyTypeName = lastIndex != -1 ? StringUtils.substring(propertyName, 0, lastIndex) : StringUtils.EMPTY;
733         		Class<?> type = PropertyUtils.getPropertyType(bo, propertyTypeName);
734         		// property type indicates a Person object
735         		if ( type != null ) {
736         			return Person.class.isAssignableFrom(type);
737         		}
738         		LOG.warn( "Unable to determine type of nested property: " + bo.getClass().getName() + " / " + propertyName );
739         	}
740         } catch (Exception ex) {
741         	if ( LOG.isDebugEnabled() ) {
742         		LOG.debug("Unable to determine if property on " + bo.getClass().getName() + " to a person object: " + propertyName, ex );
743         	}
744         }
745         return false;
746     }
747 
748     /**
749      * @see org.kuali.rice.kim.api.identity.PersonService#resolvePrincipalNamesToPrincipalIds(org.kuali.rice.krad.bo.BusinessObject, java.util.Map)
750      */
751     @Override
752     @SuppressWarnings("unchecked")
753 	public Map<String,String> resolvePrincipalNamesToPrincipalIds(BusinessObject businessObject, Map<String,String> fieldValues) {
754     	if ( fieldValues == null ) {
755     		return null;
756     	}
757     	if ( businessObject == null ) {
758     		return fieldValues;
759     	}
760     	StringBuffer resolvedPrincipalIdPropertyName = new StringBuffer();
761     	// save off all criteria which are not references to Person properties
762     	// leave person properties out so they can be resolved and replaced by this method
763         Map<String,String> processedFieldValues = getNonPersonSearchCriteria(businessObject, fieldValues);
764         for ( String propertyName : fieldValues.keySet() ) {
765             if (	!StringUtils.isBlank(fieldValues.get(propertyName))  // property has a value
766             		&& isPersonProperty(businessObject, propertyName) // is a property on a Person object
767             		) {
768             	// strip off the prefix on the property
769                 int lastPropertyIndex = PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(propertyName);
770                 String personPropertyName = lastPropertyIndex != -1 ? StringUtils.substring(propertyName, lastPropertyIndex + 1) : propertyName;
771                 // special case - the user ID
772                 if ( StringUtils.equals( KIMPropertyConstants.Person.PRINCIPAL_NAME, personPropertyName) ) {
773                     Class targetBusinessObjectClass = null;
774                     BusinessObject targetBusinessObject = null;
775                     resolvedPrincipalIdPropertyName.setLength( 0 ); // clear the buffer without requiring a new object allocation on each iteration
776                 	// get the property name up until the ".principalName"
777                 	// this should be a reference to the Person object attached to the BusinessObject
778                     String personReferenceObjectPropertyName =  lastPropertyIndex != -1 ? StringUtils.substring(propertyName, 0, lastPropertyIndex) : StringUtils.EMPTY;
779                 	// check if the person was nested within another BO under the master BO.  If so, go up one more level
780                 	// otherwise, use the passed in BO class as the target class
781                     if ( PropertyAccessorUtils.isNestedOrIndexedProperty(personReferenceObjectPropertyName) ) {
782                         int lastTargetIndex = PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(personReferenceObjectPropertyName);
783                         String targetBusinessObjectPropertyName = lastTargetIndex != -1 ? StringUtils.substring(personReferenceObjectPropertyName, 0, lastTargetIndex) : StringUtils.EMPTY;
784                         DataObjectWrapper<BusinessObject> wrapper = KradDataServiceLocator.getDataObjectService().wrap(businessObject);
785                         targetBusinessObject = (BusinessObject) wrapper.getPropertyValueNullSafe(targetBusinessObjectPropertyName);
786                         if (targetBusinessObject != null) {
787                             targetBusinessObjectClass = targetBusinessObject.getClass();
788                             resolvedPrincipalIdPropertyName.append(targetBusinessObjectPropertyName).append(".");
789                         } else {
790                             LOG.error("Could not find target property '"+propertyName+"' in class "+businessObject.getClass().getName()+". Property value was null.");
791                         }
792                     } else { // not a nested Person property
793                         targetBusinessObjectClass = businessObject.getClass();
794                         targetBusinessObject = businessObject;
795                     }
796 
797                     if (targetBusinessObjectClass != null) {
798                     	// use the relationship metadata in the KNS to determine the property on the
799                     	// host business object to put back into the map now that the principal ID
800                     	// (the value stored in application tables) has been resolved
801                         int lastIndex = PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(personReferenceObjectPropertyName);
802                         String propName = lastIndex != -1 ? StringUtils.substring(personReferenceObjectPropertyName, lastIndex + 1) : personReferenceObjectPropertyName;
803                         DataObjectRelationship rel = getBusinessObjectMetaDataService().getBusinessObjectRelationship( targetBusinessObject, propName );
804                         if ( rel != null ) {
805                             String sourcePrimitivePropertyName = rel.getParentAttributeForChildAttribute(KIMPropertyConstants.Person.PRINCIPAL_ID);
806                             resolvedPrincipalIdPropertyName.append(sourcePrimitivePropertyName);
807                         	// get the principal - for translation of the principalName to principalId
808                             String principalName = fieldValues.get( propertyName );
809                         	Principal principal = getIdentityService().getPrincipalByPrincipalName( principalName );
810                             if (principal != null ) {
811                                 processedFieldValues.put(resolvedPrincipalIdPropertyName.toString(), principal.getPrincipalId());
812                             } else {
813                                 processedFieldValues.put(resolvedPrincipalIdPropertyName.toString(), null);
814                                 try {
815                                     // if the principalName is bad, then we need to clear out the Person object
816                                     // and base principalId property
817                                     // so that their values are no longer accidentally used or re-populate
818                                     // the object
819                                     KRADUtils.setObjectProperty(targetBusinessObject,
820                                             resolvedPrincipalIdPropertyName.toString(), null);
821                                     KRADUtils.setObjectProperty(targetBusinessObject, propName, null );
822                                     KRADUtils.setObjectProperty(targetBusinessObject, propName + ".principalName", principalName );
823                                 } catch ( Exception ex ) {
824                                     LOG.error( "Unable to blank out the person object after finding that the person with the given principalName does not exist.", ex );
825                                 }
826                             }
827                         } else {
828                         	LOG.error( "Missing relationship for " + propName + " on " + targetBusinessObjectClass.getName() );
829                         }
830                     } else { // no target BO class - the code below probably will not work
831                         processedFieldValues.put(resolvedPrincipalIdPropertyName.toString(), null);
832                     }
833                 }
834             // if the property does not seem to match the definition of a Person property but it
835             // does end in principalName then...
836             // this is to handle the case where the user ID is on an ADD line - a case excluded from isPersonProperty()
837             } else if (propertyName.endsWith("." + KIMPropertyConstants.Person.PRINCIPAL_NAME)){
838                 // if we're adding to a collection and we've got the principalName; let's populate universalUser
839                 String principalName = fieldValues.get(propertyName);
840                 if ( StringUtils.isNotEmpty( principalName ) ) {
841                     String containerPropertyName = propertyName;
842                     if (containerPropertyName.startsWith(KRADConstants.MAINTENANCE_ADD_PREFIX)) {
843                         containerPropertyName = StringUtils.substringAfter( propertyName, KRADConstants.MAINTENANCE_ADD_PREFIX );
844                     }
845                     // get the class of the object that is referenced by the property name
846                     // if this is not true then there's a principalName collection or primitive attribute
847                     // directly on the BO on the add line, so we just ignore that since something is wrong here
848                     if ( PropertyAccessorUtils.isNestedOrIndexedProperty(containerPropertyName) ) {
849                     	// the first part of the property is the collection name
850                         String collectionName = StringUtils.substringBefore( containerPropertyName, "." );
851                         // what is the class held by that collection?
852                         // JHK: I don't like this.  This assumes that this method is only used by the maintenance
853                         // document service.  If that will always be the case, this method should be moved over there.
854                         Class<? extends BusinessObject> collectionBusinessObjectClass = getMaintenanceDocumentDictionaryService()
855                         		.getCollectionBusinessObjectClass(
856                         				getMaintenanceDocumentDictionaryService()
857                         						.getDocumentTypeName(businessObject.getClass()), collectionName);
858                         if (collectionBusinessObjectClass != null) {
859                             // we are adding to a collection; get the relationships for that object;
860                         	// is there one for personUniversalIdentifier?
861                             List<DataObjectRelationship> relationships =
862                             		getBusinessObjectMetaDataService().getBusinessObjectRelationships( collectionBusinessObjectClass );
863                             // JHK: this seems like a hack - looking at all relationships for a BO does not guarantee that we get the right one
864                             // JHK: why not inspect the objects like above?  Is it the property path problems because of the .add. portion?
865                             for ( DataObjectRelationship rel : relationships ) {
866                             	String parentAttribute = rel.getParentAttributeForChildAttribute( KIMPropertyConstants.Person.PRINCIPAL_ID );
867                             	if ( parentAttribute == null ) {
868                             		continue;
869                             	}
870                                 // there is a relationship for personUserIdentifier; use that to find the universal user
871                             	processedFieldValues.remove( propertyName );
872                         		String fieldPrefix = StringUtils.substringBeforeLast( StringUtils.substringBeforeLast( propertyName, "." + KIMPropertyConstants.Person.PRINCIPAL_NAME ), "." );
873                                 String relatedPrincipalIdPropertyName = fieldPrefix + "." + parentAttribute;
874                                 // KR-683 Special handling for extension objects
875                          	 	if(EXTENSION.equals(StringUtils.substringAfterLast(fieldPrefix, ".")) && EXTENSION.equals(StringUtils.substringBefore(parentAttribute, ".")))
876                          	 	{
877                          	 		relatedPrincipalIdPropertyName = fieldPrefix + "." + StringUtils.substringAfter(parentAttribute, ".");
878                          	 	}
879                                 String currRelatedPersonPrincipalId = processedFieldValues.get(relatedPrincipalIdPropertyName);
880                                 if ( StringUtils.isBlank( currRelatedPersonPrincipalId ) ) {
881                                 	Principal principal = getIdentityService().getPrincipalByPrincipalName( principalName );
882                                 	if ( principal != null ) {
883                                 		processedFieldValues.put(relatedPrincipalIdPropertyName, principal.getPrincipalId());
884                                 	} else {
885                                 		processedFieldValues.put(relatedPrincipalIdPropertyName, null);
886                                 	}
887                                 }
888                             } // relationship loop
889                         } else {
890                         	if ( LOG.isDebugEnabled() ) {
891                         		LOG.debug( "Unable to determine class for collection referenced as part of property: " + containerPropertyName + " on " + businessObject.getClass().getName() );
892                         	}
893                         }
894                     } else {
895                     	if ( LOG.isDebugEnabled() ) {
896                     		LOG.debug( "Non-nested property ending with 'principalName': " + containerPropertyName + " on " + businessObject.getClass().getName() );
897                     	}
898                     }
899                 }
900             }
901         }
902         return processedFieldValues;
903     }
904 
905 	// OTHER METHODS
906 
907 	protected IdentityService getIdentityService() {
908 		if ( identityService == null ) {
909 			identityService = KimApiServiceLocator.getIdentityService();
910 		}
911 		return identityService;
912 	}
913 
914 	protected RoleService getRoleService() {
915 		if ( roleService == null ) {
916 			roleService = KimApiServiceLocator.getRoleService();
917 		}
918 		return roleService;
919 	}
920 
921 
922 	@Override
923     public Class<? extends Person> getPersonImplementationClass() {
924 		return PersonImpl.class;
925 	}
926 
927 	protected BusinessObjectMetaDataService getBusinessObjectMetaDataService() {
928 		if ( businessObjectMetaDataService == null ) {
929 			businessObjectMetaDataService = KNSServiceLocator.getBusinessObjectMetaDataService();
930 		}
931 		return businessObjectMetaDataService;
932 	}
933 
934 	protected MaintenanceDocumentDictionaryService getMaintenanceDocumentDictionaryService() {
935 		if ( maintenanceDocumentDictionaryService == null ) {
936 			maintenanceDocumentDictionaryService = KNSServiceLocator.getMaintenanceDocumentDictionaryService();
937 		}
938 		return maintenanceDocumentDictionaryService;
939 	}
940 }